@@ -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> |