@@ -0,0 +1 @@ | |||||
*.sw? |
@@ -0,0 +1,278 @@ | |||||
<?php | |||||
namespace App\Http\Controllers; | |||||
use Illuminate\Http\Request; | |||||
use Stripe\Stripe; | |||||
use Stripe\Customer; | |||||
use Stripe\PaymentIntent; | |||||
use Stripe\PaymentMethod; | |||||
use Illuminate\Support\Facades\Log; | |||||
use Illuminate\Support\Facades\Auth; | |||||
use App\Models\Transaction; | |||||
class BillingController extends Controller | |||||
{ | |||||
protected $stripe; | |||||
protected $user; | |||||
public function __construct() { | |||||
$this->stripe = new \Stripe\StripeClient(config('services.stripe.secret')); | |||||
Stripe::setApiKey(config('services.stripe.secret')); | |||||
$this->user = Auth::user(); | |||||
} | |||||
protected function attempt($packs) { | |||||
$user = Auth::user(); | |||||
foreach($packs as $value) { | |||||
if ($value < 0) { | |||||
abort(422); | |||||
} | |||||
} | |||||
$amount = $packs[ 'credits10' ]*1099 + | |||||
$packs[ 'credits50' ]*5499 + $packs[ 'credits100' ]*10999 | |||||
+ $packs[ 'credits1000' ]*101000; | |||||
$transaction = new Transaction; | |||||
$transaction->credits = $packs['credits10']*1000 + | |||||
$packs['credits50']*5000 + | |||||
$packs['credits100']*10000 + | |||||
$packs['credits1000']*100000; | |||||
$transaction->credits_extra = | |||||
$packs['credits50']*500 + | |||||
$packs['credits100']*1000 + | |||||
$packs['credits1000']*15000; | |||||
$transaction->user_id = $user->id; | |||||
$transaction->charge = $amount; | |||||
$transaction->status = 'processing'; | |||||
$transaction->completed = false; | |||||
$transaction->save(); | |||||
return $transaction; | |||||
} | |||||
//Expects an array 'packs' representing the amount of each multiple of credits. | |||||
//Should validate that all amounts are positive integers in a reasonable range | |||||
public function stripeSecret(Request $request) { | |||||
$user = Auth::user(); | |||||
$transaction = $this->attempt($request->packs); | |||||
$intent = PaymentIntent::create([ | |||||
'amount' => $amount, | |||||
'currency' => 'usd', | |||||
'customer' => $user->customer_id, | |||||
'description' => "You have received $total_credits credits.", | |||||
'receipt_email' => Auth::user()->email, | |||||
'metadata' => ['transaction_id' => $transaction->id] | |||||
]); | |||||
$transaction->intent_id = $intent->id; | |||||
//Save the card as a default if none is set and it was selected | |||||
if ($user->payment_method == null && $request->card) { | |||||
$this->changeDefaultCard($request->card); | |||||
} | |||||
$transaction->save(); | |||||
return $intent->client_secret; | |||||
} | |||||
public function getCards() { | |||||
return PaymentMethod::all([ | |||||
'customer' => Auth::user()->customer_id, | |||||
'type' => 'card' | |||||
]); | |||||
} | |||||
//Adds correct credit amount to the charged user, precise to two decimal places | |||||
public function chargeEvent(Request $request) { | |||||
$event = \Stripe\Event::constructFrom($request->all()); | |||||
$charge = $event->data->object; | |||||
$transaction = Transaction::where('intent_id', $charge->payment_intent)->first(); | |||||
if ($event->type == 'charge.succeeded') { | |||||
$this->creditUser($transaction->id); | |||||
} else { | |||||
$transaction->status = $charge->status; | |||||
$transaction->save(); | |||||
} | |||||
} | |||||
public function changeDefaultCard(String $card) { | |||||
$user = Auth::user(); | |||||
$user->payment_method = $card; | |||||
$user->save(); | |||||
$cards = $this->getCards(); | |||||
return PaymentMethod::all([ | |||||
'customer' => Auth::user()->customer_id, | |||||
'type' => 'card' | |||||
]); | |||||
} | |||||
public function deleteCard(Request $request) { | |||||
$this->stripe->paymentMethods->detach($request->card); | |||||
$user = Auth::user(); | |||||
if ($request->card == $user->payment_method) { | |||||
$user->payment_method = null; | |||||
$user->save(); | |||||
} | |||||
return ($this->getCards()); | |||||
} | |||||
//Receives a request with a packs. It is an array of each type of credit | |||||
//amount to be bought | |||||
public function payeer(Request $request) { | |||||
$user = Auth::user(); | |||||
$transaction = $this->attempt($request->packs); | |||||
$shopid = config('services.payeer.id'); | |||||
$secret = config('services.payeer.secret'); | |||||
$param_key = config('services.payeer.param_key'); | |||||
$total = $transaction->credits/100 + $transaction->credits_extra/100; | |||||
$description = base64_encode("You will receive $total credits."); | |||||
$arHash = [$shopid, $transaction->id, $transaction->charge/100, 'USD', | |||||
$description]; | |||||
$params = ['reference' => ['transaction_id' => $transaction->id]]; | |||||
$key = md5($param_key.$transaction->id); | |||||
$encodedParams = @urlencode(base64_encode(openssl_encrypt( | |||||
json_encode($params), 'AES-256-CBC', $key, OPENSSL_RAW_DATA | |||||
))); | |||||
$arHash[] = $encodedParams; | |||||
$arHash[] = $secret; | |||||
$signature = strtoupper(hash('sha256', implode(':', $arHash))); | |||||
$user->paying = true; $user->save(); | |||||
return [ 'signature' => $signature, 'params' => $encodedParams, 'shop' | |||||
=> $shopid, 'transaction' => $transaction->id, 'amount' => | |||||
$transaction->charge/100, 'description' => $description ]; | |||||
} | |||||
//This needs to check the ip of the sender | |||||
public function processPayeer(Request $request) { | |||||
$allowed = ['185.71.65.92', '185.71.65.189', '149.202.17.210']; | |||||
$ipAddress = $request->ip(); | |||||
if (!in_array($ipAddress, $allowed)){ | |||||
abort(401); | |||||
} | |||||
Log::debug('Processing Payeer payment'); | |||||
Log::debug($request); | |||||
$secret = config('services.payeer.secret'); | |||||
$arHash = [$request->m_operation_id, | |||||
$request->m_operation_ps, | |||||
$request->m_operation_day, | |||||
$request->m_operation_pay_date, | |||||
$request->m_shop, | |||||
$request->m_orderid, | |||||
$request->m_amount, | |||||
$request->m_curr, | |||||
$request->m_desc, | |||||
$request->m_status | |||||
]; | |||||
if (isset($request->m_params)) { | |||||
$arHash[] = $request->m_params; | |||||
} | |||||
$arHash[] = $secret; | |||||
$signature = strtoupper(hash('sha256', implode(':', $arHash))); | |||||
if ($signature == $request->m_sign && $request->m_status == 'success'){ | |||||
$this->creditUser((int) $request->m_orderid); | |||||
return $request->m_orderid.'|success'; | |||||
} else { | |||||
$transaction = Transaction::find($request->orderid); | |||||
$transaction->status = 'error'; | |||||
$transaction->save(); | |||||
return $request->m_orderid.'|error'; | |||||
} | |||||
} | |||||
//Credits the user of a given transaction id | |||||
public function creditUser($transaction_id) { | |||||
$transaction = Transaction::find($transaction_id); | |||||
if ($transaction->completed) { | |||||
abort(422, 'Bad transaction ID'); | |||||
} | |||||
$user = $transaction->user; | |||||
$user->credits = $user->credits + $transaction->credits + | |||||
$transaction->credits_extra; | |||||
$transaction->status = 'completed'; | |||||
$transaction->completed = true; | |||||
$user->save(); | |||||
$transaction->save(); | |||||
} | |||||
public function pm(Request $request) { | |||||
$user = Auth::user(); | |||||
$account = config('services.pm.account'); | |||||
$transaction = $this->attempt($request->packs); | |||||
$total = $transaction->credits/100 + | |||||
$transaction->credits_extra/100; | |||||
$description = "You will receive $total credits."; | |||||
$user->paying = true; $user->save(); | |||||
return ['account' => $account, 'transaction' => $transaction->id, | |||||
'amount' => $transaction->charge/100, 'description' => $description]; | |||||
} | |||||
//Handler run after PM payment succeds | |||||
public function processPM(Request $request) { | |||||
$allowed = ['77.109.141.170', '91.205.41.208', '94.242.216.60', | |||||
'78.41.203.75']; | |||||
$transaction = Transaction::find($request->PAYMENT_ID); | |||||
$secret = config('services.pm.secret'); | |||||
//Check that sender is PM and account the amount was paid to is mine. | |||||
if (!in_array($request->ip(), $allowed)){ | |||||
abort(401); | |||||
} else if ($request->PAYEE_ACCOUNT != config('services.pm.account')) { | |||||
abort(422); | |||||
} else if (!$transaction->complete) { | |||||
abort(422); | |||||
} | |||||
Log::debug('Processing PM payment'); | |||||
Log::debug($request); | |||||
//Would need to be changed if baggage fields are used | |||||
$arHash = [$request->PAYMENT_ID, | |||||
$request->PAYEE_ACCOUNT, | |||||
$request->PAYMENT_AMOUNT, | |||||
$request->PAYMENT_UNITS, | |||||
$request->PAYMENT_BATCH_NUMBER, | |||||
$request->PAYER_ACCOUNT, | |||||
strtoupper(md5($secret)), | |||||
$request->TIMESTAMPGMT, | |||||
]; | |||||
$signature = strtoupper(md5(implode(':', $arHash))); | |||||
if ($signature == $request->V2_HASH){ | |||||
$this->creditUser((int) $transaction->id); | |||||
} else { | |||||
abort(422, 'Bad hash'); | |||||
} | |||||
} | |||||
public function completePM(Request $request) { | |||||
return redirect('/panel/#transaction-complete'); | |||||
} | |||||
public function failPM(Request $request) { | |||||
return redirect('/panel/#transaction-failed'); | |||||
} | |||||
} |
@@ -0,0 +1,6 @@ | |||||
##Code Examples | |||||
Some sample code taken from sites I've completed. They're written in | |||||
Javascript, PHP, and SCSS. The examples are incomplete and meant for | |||||
demonstrative purposes only as I'm not comfortable share complete code of a | |||||
live, potentialy profitable project. |
@@ -0,0 +1,745 @@ | |||||
@use "vars"; | |||||
@font-face { | |||||
font-family: "PatuaOne"; | |||||
src: url("../PatuaOne-Regular.ttf") format("opentype"); | |||||
font-style: normal; | |||||
font-weight: normal; | |||||
} | |||||
html body { | |||||
font-family: 'PatuaOne', 'Times New Roman'; | |||||
margin: 0; | |||||
} | |||||
section { | |||||
position: relative; | |||||
padding-top: 3em; | |||||
padding-bottom: 3em; | |||||
overflow: hidden; | |||||
padding-right: 15px; | |||||
padding-left: 15px; | |||||
} | |||||
button { | |||||
font-family: 'PatuaOne', 'Times New Roman'; | |||||
} | |||||
textarea { | |||||
border: 1px solid grey; | |||||
} | |||||
h1 { | |||||
color: white; | |||||
} | |||||
h2 { | |||||
color: vars.getColor("brand-orange"); | |||||
text-align: center; | |||||
} | |||||
h3 { | |||||
color: vars.getColor("faded-text"); | |||||
// text-transform: uppercase; | |||||
} | |||||
ul { | |||||
color: vars.getColor("faded-text"); | |||||
list-style-type: none; | |||||
padding: 0; | |||||
} | |||||
input, select { | |||||
border: 2px solid black; | |||||
border-radius: 20px; | |||||
padding: 4px; | |||||
} | |||||
.icon { | |||||
width: 25px; | |||||
margin: 10px; | |||||
} | |||||
.note-grey { | |||||
color: grey; | |||||
font-size: 0.8em; | |||||
max-width: 70%; | |||||
} | |||||
.services-cards li { | |||||
padding-bottom: 8px; | |||||
&:before { | |||||
width: 30px; | |||||
height: 30px; | |||||
content: url("../images/green-check.svg"); | |||||
display: inline; | |||||
margin-right: 5px; | |||||
// top: 30px; | |||||
} | |||||
} | |||||
a { | |||||
text-decoration: none; | |||||
} | |||||
nav { | |||||
position: absolute; | |||||
width: 100%; | |||||
top: 10px; | |||||
display: flex; | |||||
flex-flow: row wrap; | |||||
align-items: center; | |||||
justify-content: space-between; | |||||
// background-color: transparent; | |||||
} | |||||
nav h1 { | |||||
margin: 4px 0 10px 6px; | |||||
display: inline-block; | |||||
} | |||||
nav.info-page h1 { | |||||
margin: 4px 0 10px 6px; | |||||
display: inline-block; | |||||
color: vars.getColor('brand-orange'); | |||||
} | |||||
.formal-font { | |||||
font-family: "FreeSans"; | |||||
} | |||||
nav #logo { | |||||
margin-right: 50px; | |||||
} | |||||
nav form { | |||||
display: flex; | |||||
flex-wrap: wrap; | |||||
margin-right: 20px; | |||||
} | |||||
nav form div { | |||||
margin: 0 10px; | |||||
} | |||||
nav form button { | |||||
min-width: 5em; | |||||
} | |||||
nav form p { | |||||
margin-top: 0; | |||||
} | |||||
nav form input,label { | |||||
opacity: 0.7; | |||||
} | |||||
nav .nav-toggle { | |||||
position: absolute; | |||||
right: 10px; | |||||
top: 5px; | |||||
display: none; | |||||
height: 45px; | |||||
width: 45px; | |||||
margin-left: auto; | |||||
background: url("../images/menu-icon.svg"); | |||||
background-size: 100%; | |||||
background-size: cover; | |||||
color: white; | |||||
} | |||||
nav .nav-toggle.toggled { | |||||
background: url("../images/cancel-icon2.svg"); | |||||
width: 25px; | |||||
height: 25px; | |||||
background-size: 100%; | |||||
background-size: cover; | |||||
top: 10px; | |||||
right: 15px; | |||||
color: white; | |||||
} | |||||
div.landing-hero { | |||||
top: 0; | |||||
height: 50em; | |||||
background-color: #ff4e00; | |||||
// background-image: linear-gradient(to right bottom, #ff4e00, #fc6200, #f87200, #f58100, #f18e00, #f19507, #f09c0f, #f0a317, #f3a620, #f6a927, #f8ad2e, #fbb034); | |||||
background-image: linear-gradient(315deg, #ff4e00 0%, #ec9f05 74%); | |||||
z-index: 0; | |||||
} | |||||
div.hero-filter { | |||||
width: 100%; | |||||
height: 100%; | |||||
} | |||||
div.hero-filter h2 { | |||||
color: black; | |||||
opacity: 0.7; | |||||
max-width: 10em; | |||||
font-size: 2.4em; | |||||
position: absolute; | |||||
top: 3em; | |||||
margin-left: 5%; | |||||
} | |||||
div.hero-filter p { | |||||
position: absolute; | |||||
top: 12em; | |||||
margin-left: 5%; | |||||
font-size: 20px; | |||||
width: 10em; | |||||
opacity: 0.7; | |||||
} | |||||
div.errors { | |||||
background-color: #ececec; | |||||
color: vars.getColor("red-alert"); | |||||
border: 2px solid vars.getColor("red-alert"); | |||||
border-radius: 4px; | |||||
min-height: 50px; | |||||
position: absolute; | |||||
left: 50%; | |||||
margin-right: -50%; | |||||
top: 100px; | |||||
min-width: 30em; | |||||
transform: translate(-50%, -50%); | |||||
} | |||||
.errors ul { | |||||
margin: auto; | |||||
} | |||||
form.login { | |||||
// font-weight: bold; | |||||
color: white; | |||||
} | |||||
.cancel-icon { | |||||
width: 30px; | |||||
} | |||||
form.login input { | |||||
margin-left: 2px; | |||||
color: white; | |||||
background-color: transparent; | |||||
border: 2px solid white; | |||||
border-radius: 2px; | |||||
outline-color: orange | |||||
} | |||||
form.login .login-btn { | |||||
@include vars.special-button("medium-blue", "dark-blue"); | |||||
} | |||||
.hero-filter .register-btn { | |||||
@include vars.transparent-button; | |||||
display: block; | |||||
position: relative; | |||||
width: 7em; | |||||
top: 60%; | |||||
margin-left: auto; | |||||
margin-right: auto; | |||||
} | |||||
div.blue-background { | |||||
background-image: url("../images/black-circle.svg"); | |||||
background-size: 10px; | |||||
background-color: #0d324d; | |||||
} | |||||
section.about-us { | |||||
padding: 70px 0; | |||||
} | |||||
section.about-us p { | |||||
max-width: 600px; | |||||
margin-left: auto; | |||||
margin-right: auto; | |||||
border-top: 2px solid vars.getColor("grey"); | |||||
border-bottom: 2px solid vars.getColor("grey"); | |||||
padding: 15px; | |||||
background-color: #0d324d; | |||||
color: white; | |||||
} | |||||
section.services-cards { | |||||
// margin-bottom: 0; | |||||
background-color: lightgrey; | |||||
padding-bottom: 4em; | |||||
background: linear-gradient(to bottom right, transparent 0%, transparent 50%, #e9e9e9 50%, vars.getColor("light-grey") 100%); | |||||
} | |||||
section.services-cards .cards{ | |||||
display: flex; | |||||
flex-flow: wrap; | |||||
margin-top: 2em; | |||||
gap: 25px; | |||||
justify-content: center; | |||||
max-width: 1050px; | |||||
margin-left: auto; | |||||
margin-right: auto; | |||||
text-align: center; | |||||
} | |||||
section.services-cards h2 { | |||||
text-align: center; | |||||
padding: 10px; | |||||
margin-top: 0; | |||||
padding-top: 30px; | |||||
color: white; | |||||
} | |||||
section.services-cards .card { | |||||
width: 280px; | |||||
height: 350px; | |||||
padding: 18px 9px; | |||||
border-radius: 4px; | |||||
background: white; | |||||
box-shadow: 7px 10px 8px rgb(156 166 175 / 22%); | |||||
transition: transform 0.2s; | |||||
&:hover { | |||||
transform: translateY(-20px); | |||||
} | |||||
} | |||||
section.services-cards img { | |||||
width: 50px; | |||||
height: 50px; | |||||
} | |||||
section.services-cards button { | |||||
@include vars.brand-button("orange"); | |||||
} | |||||
section.features-info { | |||||
// height: fit-content; | |||||
height: 70em; | |||||
background: radial-gradient(ellipse at left, #fff, vars.getColor("light-grey") 80%); | |||||
} | |||||
.circle-prop { | |||||
height: 70em; | |||||
overflow: hidden; | |||||
width: 100%; | |||||
position: relative; | |||||
// transform: translateX(-10em); | |||||
} | |||||
.circle-prop svg { | |||||
width: 50%; | |||||
} | |||||
.features-info img { | |||||
width: 600px; | |||||
position: absolute; | |||||
top: 15%; | |||||
right: 4%; | |||||
} | |||||
div.info-cards { | |||||
position: absolute; | |||||
top: 10em; | |||||
margin-left: 20%; | |||||
width: 70%; | |||||
} | |||||
.info-card { | |||||
margin-bottom: 6em; | |||||
&:nth-of-type(2) { | |||||
margin-left: 13%; | |||||
} | |||||
&:nth-of-type(3) { | |||||
margin-left: 10%; | |||||
} | |||||
&:nth-of-type(4) { | |||||
margin-left: 5%; | |||||
} | |||||
p { | |||||
max-width: 20em; | |||||
background: white; | |||||
padding: 5px; | |||||
border-radius: 4px; | |||||
text-align: center; | |||||
color: vars.getColor("faded-text2"); | |||||
// border: 2px solid vars.getColor("medium-orange"); | |||||
@include vars.hovering; | |||||
} | |||||
} | |||||
section.panel-infos { | |||||
min-height: 40em; | |||||
margin-top: 5em; | |||||
margin-bottom: 5em; | |||||
display: flex; | |||||
flex-flow: wrap; | |||||
align-items: center; | |||||
justify-content: center; | |||||
gap: 20px; | |||||
.panel-preview { | |||||
// box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px; | |||||
@include vars.hovering; | |||||
padding: 0; | |||||
} | |||||
} | |||||
.panel-infos h2 { | |||||
width: 100%; | |||||
} | |||||
.panel-info { | |||||
max-width: 25em; | |||||
margin-top: 4em; | |||||
} | |||||
.panel-info img { | |||||
width: 50px; | |||||
height: 50px; | |||||
} | |||||
.panel-info p { | |||||
color: vars.getColor("faded-text2"); | |||||
} | |||||
section.benefits-info div { | |||||
margin: 1em auto; | |||||
padding: 1em 1em; | |||||
border-top: 2px solid vars.getColor("faded-text2"); | |||||
border-bottom: 2px solid vars.getColor("faded-text2"); | |||||
max-width: 40em; | |||||
color: vars.getColor("faded-text"); | |||||
} | |||||
.benefits-info h2 { | |||||
margin-bottom: 2em; | |||||
} | |||||
section.faq-info { | |||||
margin-bottom: 6em; | |||||
margin-top: 3em; | |||||
min-height: 30em; | |||||
} | |||||
.faq-info h2 { | |||||
margin-bottom: 2em; | |||||
} | |||||
section.faq-info .collapsibles { | |||||
display: flex; | |||||
flex-flow: wrap; | |||||
gap: 2em; | |||||
justify-content: center; | |||||
max-width: 65em; | |||||
width: fit-content; | |||||
margin-left: auto; | |||||
margin-right: auto; | |||||
align-text: center; | |||||
} | |||||
.faq-info .collapsible { | |||||
text-align: center; | |||||
width: 20em; | |||||
&:not(.active) .content { | |||||
display: none; | |||||
} | |||||
} | |||||
.collapsible button { | |||||
width: 95%; | |||||
display: block; | |||||
margin-right: auto; | |||||
height: 2.5em; | |||||
border: 2px solid vars.getColor("brand-orange"); | |||||
@include vars.button("light-grey"); | |||||
font-size: 17px; | |||||
// font-weight: bold; | |||||
} | |||||
.collapsible button:after { | |||||
content: '\02795'; /* Unicode character for "plus" sign (+) */ | |||||
font-size: 13px; | |||||
color: white; | |||||
float: right; | |||||
margin-left: 5px; | |||||
} | |||||
.collapsible .content { | |||||
// background-color: vars.getColor("faded-text2"); | |||||
border: 2px solid black; | |||||
border-radius: 4px; | |||||
padding: 4px; | |||||
// transition: all 0.2s; | |||||
// height: 1px; | |||||
} | |||||
footer { | |||||
height: 6em; | |||||
background-color: vars.getColor("dark-grey"); | |||||
padding: 20px; | |||||
color: vars.getColor("faded-text2"); | |||||
} | |||||
footer .foot-links { | |||||
width: 20em; | |||||
margin: 2em auto; | |||||
text-align: center; | |||||
} | |||||
footer a { | |||||
margin: 0 8px; | |||||
color: vars.getColor("faded-text2"); | |||||
&:hover { | |||||
color: vars.getColor("faded-text"); | |||||
} | |||||
} | |||||
.hidden { | |||||
display: none; | |||||
} | |||||
.loading-icon { | |||||
display: block; | |||||
width: 50px; | |||||
height: 50px; | |||||
margin: auto; | |||||
} | |||||
.register-area p { | |||||
text-align: center; | |||||
} | |||||
.register-area .loading-icon { | |||||
margin-top: 100px; | |||||
} | |||||
div.register-area { | |||||
overflow: hidden; | |||||
height: 0px; | |||||
width: 100%; | |||||
z-index: 1; | |||||
@include vars.hovering3; | |||||
margin-bottom: 2em; | |||||
position: fixed; | |||||
border-radius: 0; | |||||
padding: 0; | |||||
opacity: 0; | |||||
transition: all 0.2s; | |||||
background: white; | |||||
h3 { | |||||
text-align: center; | |||||
} | |||||
label,input { | |||||
color: black; | |||||
display: block; | |||||
border-color: black; | |||||
width: 95%; | |||||
} | |||||
div { | |||||
margin-top: 20px; | |||||
margin-bottom: 20px; | |||||
width: 100%; | |||||
} | |||||
form { | |||||
width: 160px; | |||||
margin: 20px auto; | |||||
} | |||||
.submit-btn { | |||||
width: 100%; | |||||
background-color: #3bb78f; | |||||
@include vars.special-button("light-green", "dark-green"); | |||||
} | |||||
.cancel-button { | |||||
width: 20px; | |||||
height: 20px; | |||||
background: url("../images/close-icon-black.svg"); | |||||
background-size: cover; | |||||
position: absolute; | |||||
top: 20px; | |||||
right: 20px; | |||||
} | |||||
} | |||||
section.features-info { | |||||
// min-height: 90em; | |||||
} | |||||
div.register-area.active { | |||||
height: fit-content; | |||||
min-height: 20em; | |||||
opacity: 1; | |||||
} | |||||
.medium-icon { | |||||
width: 50px; | |||||
height: 50px; | |||||
margin-left: auto; | |||||
margin-right: auto; | |||||
display: block; | |||||
top: 50px; | |||||
} | |||||
// Make small screens more usable | |||||
@media (max-width: 720px) { | |||||
nav { display: block; } | |||||
nav form { | |||||
// display: none; | |||||
width: 190px; | |||||
margin-left: auto; | |||||
margin-right: auto; | |||||
align-items: center; | |||||
margin-top: 5em; | |||||
opacity: 0; | |||||
transition: transform 0.3s; | |||||
transform: translateY(-50px); | |||||
} | |||||
nav form.active { | |||||
display: block; | |||||
transform: translateX(0%); | |||||
opacity: 1; | |||||
// background: white; | |||||
// color: black; | |||||
} | |||||
nav form div { | |||||
margin: 10px auto; | |||||
} | |||||
nav div label,input { | |||||
display: block; | |||||
} | |||||
nav .nav-toggle { | |||||
display: block; | |||||
} | |||||
div.register-area.active { | |||||
display: initial; | |||||
} | |||||
div.register-area h1 { | |||||
font-size: 1.5rem; | |||||
display: inline-block; | |||||
color: vars.getColor("brand-orange"); | |||||
} | |||||
div.register-area h3 { | |||||
text-align: center; | |||||
} | |||||
div.register-area .login { | |||||
color: vars.getColor("brand-orange"); | |||||
display: block; | |||||
margin-left: auto; | |||||
margin-right: auto; | |||||
// margin-top: 1em; | |||||
width: 10em; | |||||
// top: 40px; | |||||
// position: absolute; | |||||
} | |||||
div.register-area .login input { | |||||
border: 2px solid black; | |||||
border-color: black; | |||||
color: black; | |||||
} | |||||
div.register-area button { | |||||
margin-top: 1em; | |||||
width: 6em; | |||||
} | |||||
div.register-area div { | |||||
margin-top: 10px; | |||||
margin-bottom: 5px; | |||||
margin-left: auto; | |||||
margin-right: auto; | |||||
} | |||||
.landing-hero div.hero-filter h2 { | |||||
top: 4em; | |||||
} | |||||
.landing-hero div.hero-filter p { | |||||
top: 15em; | |||||
} | |||||
.landing-hero div.hero-filter .register-btn { | |||||
top: 75%; | |||||
} | |||||
} | |||||
.info-heading { | |||||
text-align: center; | |||||
margin-top: 3em; | |||||
margin-left: auto; | |||||
margin-right: auto; | |||||
max-width: 35em; | |||||
padding: 3em; | |||||
height: 30em; | |||||
button { | |||||
display: block; | |||||
margin: 3em auto; | |||||
width: 6em; | |||||
@include vars.special-button("light-green", "dark-green"); | |||||
} | |||||
} | |||||
main.panel { | |||||
// min-height: 750px; | |||||
height: 100vh; | |||||
// max-height: 100vh; | |||||
background-image: linear-gradient(315deg, #ff4e00 0%, #ec9f05 74%); | |||||
} | |||||
#panel { | |||||
top: 50px; | |||||
padding: 0 5%; | |||||
margin: auto; | |||||
position: relative; | |||||
max-width: 800px; | |||||
height: 90%; | |||||
display: flex; | |||||
gap: 10px; | |||||
} | |||||
#sidebar { | |||||
position: relative; | |||||
width: 6%; | |||||
max-width: 55px; | |||||
height: 50%; | |||||
color: white; | |||||
background: transparent; | |||||
display: block; | |||||
flex-shrink: 0; | |||||
a { | |||||
display: block; | |||||
margin: 20px 0; | |||||
} | |||||
svg { | |||||
width: 100%; | |||||
height: 35px; | |||||
color: white; | |||||
} | |||||
a.selected svg { | |||||
color: vars.getColor("brand-orange"); | |||||
} | |||||
} | |||||
@@ -0,0 +1,129 @@ | |||||
<template> | |||||
<template v-if="!loading"> | |||||
<sidebar :role="user.role" :active="active"></sidebar> | |||||
<transition name="fade" mode="out-in"> | |||||
<div v-if="active === ''" id="main"> | |||||
<section class="welcome-pane"><h3>Welcome, {{user.name}}!</h3></section> | |||||
<section class="credits-pane"><img src="../../images/coin-stack.svg" | |||||
alt="wallet" class="icon"/><p>Credits: | |||||
{{(user.credits/100).toLocaleString('en')}}</p></section> | |||||
<section class="alerts-pane"><h4>News and Announcements</h4> | |||||
<p>We've just launched. Thanks for joining us! Some features are still | |||||
being tested.</p> | |||||
</section> | |||||
<section class="recent-pane"><h4>Recent Activity</h4> | |||||
<table> | |||||
<thead><th>Date</th><th>Name</th><th>Status</th></thead> | |||||
<tbody> | |||||
<tr v-bind:key='order.id' v-for='order in orders.slice(0, 10)'> | |||||
<template v-if="order.status != 'pending'"> | |||||
<td>{{order.updated_at}}</td> | |||||
<td>{{order.service.name}}</td> | |||||
<td :class="order.status" | |||||
class="status"><span>{{order.status.charAt(0).toUpperCase() + | |||||
order.status.slice(1)}}</span></td> | |||||
</template> | |||||
</tr> | |||||
</tbody> | |||||
</table> | |||||
</section> | |||||
</div> | |||||
<past-orders :token="token" :orders="orders" v-else-if="active === '#orders'" | |||||
id="main"> | |||||
</past-orders> | |||||
<new-order :preferred="user.payment_method" :token="token" :active="active" | |||||
:credits="user.credits" v-else-if="active === | |||||
'#new-order' || active === '#credits'" id="main" @update-user='getUser' | |||||
@update-orders='getOrders'> | |||||
</new-order> | |||||
<div id="main" v-else-if="active === '#exit'"> | |||||
<section class="logout-pane"> | |||||
<h3>Are you sure you want to logout?</h3> | |||||
<a href="/logout">Logout</a> | |||||
</section> | |||||
</div> | |||||
<settings :token="token" :user="user" class="settings-page" id="main" | |||||
v-else-if="active === '#settings'"> | |||||
</settings> | |||||
<transaction-end @purchase-complete="getUser" :token="token" :user="user" | |||||
:active="active" v-else-if="active == | |||||
'#transaction-complete' || active == '#transaction-failed'"> | |||||
</transaction-end> | |||||
<support v-else-if="active == '#support'" :user="user" :token="token"></support> | |||||
</transition> | |||||
</template> | |||||
</template> | |||||
<script> | |||||
import Sidebar from './sidebar.vue' | |||||
import Settings from './settings.vue' | |||||
import PastOrders from './orders.vue' | |||||
import NewOrder from './services.vue' | |||||
import TransactionEnd from './transaction-endpoint.vue' | |||||
import Support from './support.vue' | |||||
function getServices() { | |||||
return fetch("/panel/services", { | |||||
method: 'GET', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token} | |||||
}).then(response => { | |||||
response.json().then(data => {this.services = data}) | |||||
}) | |||||
} | |||||
function getUser() { | |||||
return fetch("/panel/user", { | |||||
method: 'GET', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
}).then(response => { | |||||
return response.json() | |||||
}).then(data => { | |||||
this.user = data | |||||
}) | |||||
} | |||||
function getOrders() { | |||||
return fetch("/panel/orders", { | |||||
method: 'GET', | |||||
headers: {'Content-Type': 'application/json', | |||||
'Accept': 'application/json', | |||||
'X-XSRF-TOKEN': this.token}, | |||||
}).then(response => { | |||||
return response.json() | |||||
}).then(data => { | |||||
this.orders = data | |||||
}) | |||||
} | |||||
export default { | |||||
components: { | |||||
Sidebar, Settings, PastOrders, NewOrder, | |||||
TransactionEnd, Support | |||||
}, | |||||
data() { | |||||
return {active: window.location.hash, user: null, | |||||
token: null, orders: null, loading: true,} | |||||
}, | |||||
methods: {getUser, getServices, getOrders, }, | |||||
created() { | |||||
let loaders = [] | |||||
loaders.push(this.getUser()) | |||||
loaders.push(this.getServices()) | |||||
loaders.push(this.getOrders()) | |||||
Promise.all(loaders).then(() => { | |||||
this.loading = false | |||||
}) | |||||
} | |||||
} | |||||
</script> |