From ab67f6726ff4c45f9fc3eeff31a1a7bc9265c255 Mon Sep 17 00:00:00 2001
From: Immanuel Onyeka <immanuel@onyeka.ca>
Date: Sun, 18 Jul 2021 23:06:12 -0400
Subject: [PATCH] Add more ES6 code

---
 javascript-vue/js/app.js                      |   1 +
 javascript-vue/js/bootstrap.js                |  28 +++
 javascript-vue/js/icons/eye-fill.vue          |   8 +
 javascript-vue/js/icons/instagram.vue         |  67 ++++++
 javascript-vue/js/icons/loading.vue           |   3 +
 javascript-vue/js/icons/plus-fill.vue         |   5 +
 javascript-vue/js/icons/plus.vue              |   6 +
 javascript-vue/js/icons/youtube.vue           |  39 ++++
 javascript-vue/js/main.js                     | 116 +++++++++++
 javascript-vue/js/panel/admin.vue             |   0
 javascript-vue/js/panel/credits.vue           | 188 +++++++++++++++++
 javascript-vue/js/panel/edit-cards.vue        |  82 ++++++++
 javascript-vue/js/panel/order-item.vue        |  71 +++++++
 javascript-vue/js/panel/orders.vue            | 100 +++++++++
 {vue => javascript-vue/js/panel}/panel.vue    |   3 +-
 javascript-vue/js/panel/payment-card.vue      |  42 ++++
 javascript-vue/js/panel/payment-slider.vue    |  17 ++
 javascript-vue/js/panel/saved-cards.vue       |  28 +++
 javascript-vue/js/panel/service-pane.vue      |  28 +++
 javascript-vue/js/panel/services.vue          | 196 ++++++++++++++++++
 javascript-vue/js/panel/settings.vue          | 119 +++++++++++
 javascript-vue/js/panel/sidebar.vue           |  46 ++++
 javascript-vue/js/panel/summary.vue           |   3 +
 javascript-vue/js/panel/support.vue           |  82 ++++++++
 .../js/panel/transaction-endpoint.vue         |  33 +++
 .../js/register-area/register-area.vue        | 168 +++++++++++++++
 26 files changed, 1478 insertions(+), 1 deletion(-)
 create mode 100644 javascript-vue/js/app.js
 create mode 100644 javascript-vue/js/bootstrap.js
 create mode 100644 javascript-vue/js/icons/eye-fill.vue
 create mode 100644 javascript-vue/js/icons/instagram.vue
 create mode 100644 javascript-vue/js/icons/loading.vue
 create mode 100644 javascript-vue/js/icons/plus-fill.vue
 create mode 100644 javascript-vue/js/icons/plus.vue
 create mode 100644 javascript-vue/js/icons/youtube.vue
 create mode 100644 javascript-vue/js/main.js
 create mode 100644 javascript-vue/js/panel/admin.vue
 create mode 100644 javascript-vue/js/panel/credits.vue
 create mode 100644 javascript-vue/js/panel/edit-cards.vue
 create mode 100644 javascript-vue/js/panel/order-item.vue
 create mode 100644 javascript-vue/js/panel/orders.vue
 rename {vue => javascript-vue/js/panel}/panel.vue (95%)
 create mode 100644 javascript-vue/js/panel/payment-card.vue
 create mode 100644 javascript-vue/js/panel/payment-slider.vue
 create mode 100644 javascript-vue/js/panel/saved-cards.vue
 create mode 100644 javascript-vue/js/panel/service-pane.vue
 create mode 100644 javascript-vue/js/panel/services.vue
 create mode 100644 javascript-vue/js/panel/settings.vue
 create mode 100644 javascript-vue/js/panel/sidebar.vue
 create mode 100644 javascript-vue/js/panel/summary.vue
 create mode 100644 javascript-vue/js/panel/support.vue
 create mode 100644 javascript-vue/js/panel/transaction-endpoint.vue
 create mode 100644 javascript-vue/js/register-area/register-area.vue

diff --git a/javascript-vue/js/app.js b/javascript-vue/js/app.js
new file mode 100644
index 0000000..40c55f6
--- /dev/null
+++ b/javascript-vue/js/app.js
@@ -0,0 +1 @@
+require('./bootstrap');
diff --git a/javascript-vue/js/bootstrap.js b/javascript-vue/js/bootstrap.js
new file mode 100644
index 0000000..6922577
--- /dev/null
+++ b/javascript-vue/js/bootstrap.js
@@ -0,0 +1,28 @@
+window._ = require('lodash');
+
+/**
+ * We'll load the axios HTTP library which allows us to easily issue requests
+ * to our Laravel back-end. This library automatically handles sending the
+ * CSRF token as a header based on the value of the "XSRF" token cookie.
+ */
+
+window.axios = require('axios');
+
+window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
+
+/**
+ * Echo exposes an expressive API for subscribing to channels and listening
+ * for events that are broadcast by Laravel. Echo and event broadcasting
+ * allows your team to easily build robust real-time web applications.
+ */
+
+// import Echo from 'laravel-echo';
+
+// window.Pusher = require('pusher-js');
+
+// window.Echo = new Echo({
+//     broadcaster: 'pusher',
+//     key: process.env.MIX_PUSHER_APP_KEY,
+//     cluster: process.env.MIX_PUSHER_APP_CLUSTER,
+//     forceTLS: true
+// });
diff --git a/javascript-vue/js/icons/eye-fill.vue b/javascript-vue/js/icons/eye-fill.vue
new file mode 100644
index 0000000..726eed7
--- /dev/null
+++ b/javascript-vue/js/icons/eye-fill.vue
@@ -0,0 +1,8 @@
+<template>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
+fill="currentColor" class="bi bi-eye-fill" viewBox="0 0 16 16">
+  <path d="M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z"/>
+  <path d="M0 8s3-5.5 8-5.5S16 8 16 8s-3 5.5-8 5.5S0 8 0 8zm8 3.5a3.5 3.5 0
+  1 0 0-7 3.5 3.5 0 0 0 0 7z"/>
+</svg>
+</template>
diff --git a/javascript-vue/js/icons/instagram.vue b/javascript-vue/js/icons/instagram.vue
new file mode 100644
index 0000000..cd4ec42
--- /dev/null
+++ b/javascript-vue/js/icons/instagram.vue
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 551.034 551.034" style="enable-background:new 0 0 551.034 551.034;" xml:space="preserve">
+<g>
+	
+		<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="275.517" y1="4.57" x2="275.517" y2="549.72" gradientTransform="matrix(1 0 0 -1 0 554)">
+		<stop  offset="0" style="stop-color:#E09B3D"/>
+		<stop  offset="0.3" style="stop-color:#C74C4D"/>
+		<stop  offset="0.6" style="stop-color:#C21975"/>
+		<stop  offset="1" style="stop-color:#7024C4"/>
+	</linearGradient>
+	<path style="fill:url(#SVGID_1_);" d="M386.878,0H164.156C73.64,0,0,73.64,0,164.156v222.722
+		c0,90.516,73.64,164.156,164.156,164.156h222.722c90.516,0,164.156-73.64,164.156-164.156V164.156
+		C551.033,73.64,477.393,0,386.878,0z M495.6,386.878c0,60.045-48.677,108.722-108.722,108.722H164.156
+		c-60.045,0-108.722-48.677-108.722-108.722V164.156c0-60.046,48.677-108.722,108.722-108.722h222.722
+		c60.045,0,108.722,48.676,108.722,108.722L495.6,386.878L495.6,386.878z"/>
+	
+		<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="275.517" y1="4.57" x2="275.517" y2="549.72" gradientTransform="matrix(1 0 0 -1 0 554)">
+		<stop  offset="0" style="stop-color:#E09B3D"/>
+		<stop  offset="0.3" style="stop-color:#C74C4D"/>
+		<stop  offset="0.6" style="stop-color:#C21975"/>
+		<stop  offset="1" style="stop-color:#7024C4"/>
+	</linearGradient>
+	<path style="fill:url(#SVGID_2_);" d="M275.517,133C196.933,133,133,196.933,133,275.516s63.933,142.517,142.517,142.517
+		S418.034,354.1,418.034,275.516S354.101,133,275.517,133z M275.517,362.6c-48.095,0-87.083-38.988-87.083-87.083
+		s38.989-87.083,87.083-87.083c48.095,0,87.083,38.988,87.083,87.083C362.6,323.611,323.611,362.6,275.517,362.6z"/>
+	
+		<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="418.31" y1="4.57" x2="418.31" y2="549.72" gradientTransform="matrix(1 0 0 -1 0 554)">
+		<stop  offset="0" style="stop-color:#E09B3D"/>
+		<stop  offset="0.3" style="stop-color:#C74C4D"/>
+		<stop  offset="0.6" style="stop-color:#C21975"/>
+		<stop  offset="1" style="stop-color:#7024C4"/>
+	</linearGradient>
+	<circle style="fill:url(#SVGID_3_);" cx="418.31" cy="134.07" r="34.15"/>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/javascript-vue/js/icons/loading.vue b/javascript-vue/js/icons/loading.vue
new file mode 100644
index 0000000..10fd5d3
--- /dev/null
+++ b/javascript-vue/js/icons/loading.vue
@@ -0,0 +1,3 @@
+<template>
+<svg class="loading-icon" data-set="loaders" data-loading="lazy" width="30px" height="30px" data-src="https://s2.svgbox.net/loaders.svg?ic=oval" data-icon="oval" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" color="" data-attributes-set="viewBox,xmlns,stroke,color" data-rendered="true"><g transform="translate(1 1)" stroke-width="2" fill="none" fill-rule="evenodd"><circle stroke-opacity=".5" cx="18" cy="18" r="18"></circle><path d="M36 18c0-9.94-8.06-18-18-18"><animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="1s" repeatCount="indefinite"></animateTransform></path></g></svg>
+</template>
diff --git a/javascript-vue/js/icons/plus-fill.vue b/javascript-vue/js/icons/plus-fill.vue
new file mode 100644
index 0000000..4a86388
--- /dev/null
+++ b/javascript-vue/js/icons/plus-fill.vue
@@ -0,0 +1,5 @@
+<template>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-square-fill" viewBox="0 0 16 16">
+  <path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z"/>
+</svg>
+</template>
diff --git a/javascript-vue/js/icons/plus.vue b/javascript-vue/js/icons/plus.vue
new file mode 100644
index 0000000..b917ed6
--- /dev/null
+++ b/javascript-vue/js/icons/plus.vue
@@ -0,0 +1,6 @@
+<template>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-square" viewBox="0 0 16 16">
+  <path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
+  <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
+</svg>
+</template>
diff --git a/javascript-vue/js/icons/youtube.vue b/javascript-vue/js/icons/youtube.vue
new file mode 100644
index 0000000..1bf5548
--- /dev/null
+++ b/javascript-vue/js/icons/youtube.vue
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 461.001 461.001" style="enable-background:new 0 0 461.001 461.001;" xml:space="preserve">
+<path style="fill:#F61C0D;" d="M365.257,67.393H95.744C42.866,67.393,0,110.259,0,163.137v134.728
+	c0,52.878,42.866,95.744,95.744,95.744h269.513c52.878,0,95.744-42.866,95.744-95.744V163.137
+	C461.001,110.259,418.135,67.393,365.257,67.393z M300.506,237.056l-126.06,60.123c-3.359,1.602-7.239-0.847-7.239-4.568V168.607
+	c0-3.774,3.982-6.22,7.348-4.514l126.06,63.881C304.363,229.873,304.298,235.248,300.506,237.056z"/>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/javascript-vue/js/main.js b/javascript-vue/js/main.js
new file mode 100644
index 0000000..4ba0b70
--- /dev/null
+++ b/javascript-vue/js/main.js
@@ -0,0 +1,116 @@
+import RegisterArea from './register-area/register-area.vue'
+import Panel from './panel/panel.vue'
+import '../scss/main.scss'
+import { createApp } from 'vue'
+importAll(require.context('../images', false, /\.(png|jpe?g|svg)$/))
+
+let heroText = document.querySelectorAll(".landing-hero h2,.landing-hero p")
+let registerToggles = document.querySelectorAll(".register-btn, .register-area\
+	.cancel-button, .services-cards button")
+let token = getCookie('XSRF-TOKEN')
+
+function importAll(r) {
+	return r.keys().map(r)
+}
+
+function getCookie(name) {
+  var re = new RegExp(name + "=([^;]+)")
+  var value = re.exec(document.cookie)
+
+  return (value != null) ? unescape(value[1]) : null
+}
+
+function getToken() {
+	return fetch("/sanctum/csrf-cookie", {
+		method: 'GET'
+	}).then( () => {
+		token = getCookie('XSRF-TOKEN')
+		return token
+	})
+}
+
+function login(event) {
+	getToken().then(fetch("/login", {
+		method: 'POST',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': token},
+		body: JSON.stringify({"email":
+			document.getElementById("login_email").value,
+		"password": document.getElementById("login_password").value}),
+	}).then(response => {
+		if (response.ok) {
+			window.location.assign("/panel")
+		} else {
+			document.querySelector("#login_form .error").innerText =
+				"Invalid credentials."
+		}
+	}))
+	event.preventDefault()
+	// event.stopPropogation()
+}
+
+
+//Attempt to resend the verification link
+function resendLink(event) {
+	fetch("/resend-verification", {
+		method: 'POST',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': token},
+	}).then(response => {
+		if (response.ok) {
+			event.target.parentNode.getElementsByTagName('h3')[0].innerText =
+			"The link has been resent."
+		} else {
+			event.target.parentNode.getElementsByTagName('h3')[0].innerText =
+			`${response.status} : ${response.statusText}`
+		}})
+	event.preventDefault();
+}
+
+function toggleNav() {
+	heroText.forEach(item => {
+		item.classList.toggle("hidden")
+	})
+	document.querySelector("nav form.login").classList.toggle("active")
+	this.classList.toggle("toggled")
+}
+
+
+if (window.location.pathname == '/') {
+	document.getElementById('nav_toggle').addEventListener('click', toggleNav)
+	document.querySelector('#login_form button').addEventListener('click', login)
+	const app = createApp(RegisterArea).mount('#app')
+	// app.token = token
+	if (!token) {app.token = getToken()}
+
+	//Triggers for registration menu
+	for (let i = 0; i < registerToggles.length; i++) {
+		registerToggles[i].addEventListener("click", function() {
+		document.querySelector(".register-area").classList.add("active")
+		app.active = 'register'
+		})
+	}
+	document.getElementById("forgot-password-btn").onclick = event => {
+		document.querySelector(".register-area").classList.add("active")
+		app.active = 'forgot'
+		event.preventDefault()
+	}
+
+	//FAQ collapsibles
+	let cols = document.getElementsByClassName("collapsible");
+
+	for (let i = 0; i < cols.length; i++) {
+		cols[i].addEventListener("click", function() {
+			this.classList.toggle("active");
+		});
+	}
+} else if (window.location.pathname == '/verify-email') {
+	document.getElementById('resend_verification').addEventListener("click", resendLink)
+} else if (window.location.pathname == '/panel') {
+	const app = createApp(Panel).mount('#panel')
+	getToken().then(()=> {app.token = token})
+	window.onhashchange = ()=>{app.active = location.hash}
+}
+
diff --git a/javascript-vue/js/panel/admin.vue b/javascript-vue/js/panel/admin.vue
new file mode 100644
index 0000000..e69de29
diff --git a/javascript-vue/js/panel/credits.vue b/javascript-vue/js/panel/credits.vue
new file mode 100644
index 0000000..90e1fc0
--- /dev/null
+++ b/javascript-vue/js/panel/credits.vue
@@ -0,0 +1,188 @@
+<template>
+<section class="select-credits">
+<div class="credits-pane"><h2>10 Credits</h2>
+<h3>$10.99</h3><div><span>Qty</span><input min="0" max="1000" v-model="packs.credits10" type="number"></div>
+</div>
+
+<div class="credits-pane"><div><h2>50 Credits</h2><span>+5 Free Credits</span></div>
+<h3>$54.99 </h3><div><span>Qty</span><input min="0" max="1000" v-model="packs.credits50" type="number"></div>
+</div>
+
+<div class="credits-pane"><div><h2>100 Credits</h2><span>+10 Free Credits</span></div>
+<h3>$109.99</h3> <div><span>Qty</span><input min="0" max="1000" v-model="packs.credits100" type="number"></div>
+</div>
+
+<div class="credits-pane"><div><h2>1000 Credits</h2><span>+150 Free Credits</span></div>
+<h3>$1010.00</h3> <div><span>Qty</span><input min="0" max="1000" v-model="packs.credits1000" type="number"></div>
+</div>
+	<h3>Total: ${{total.toLocaleString('en')}}</h3>
+	<div id="credits-errors"></div>
+</section>
+
+<section id="payment-section">
+	<h4>Select a payment method</h4>
+
+	<div class="sliding-menu">
+	<a @click="method = 'payeer'" :class="{selected: method == 'payeer'}">Payeer</a>
+	<a @click="method = 'pm'" :class="{selected: method == 'pm'}">Perfect Money</a>
+	<div :class="{right: (method == 'pm')}" class="menu-slider"><div></div></div>
+	</div>
+
+	<div v-if="method == 'payeer'" class="payment-window">
+	<img src="../../images/payeer.png" alt="">
+	<p>Payeer allows you to pay securely by transfering your choice of cryptocurrency
+	to a temporary address.
+	</p>
+	</div>
+
+	<div v-if="method == 'pm'" class="payment-window">
+	<img src="../../images/perfect_money.svg" alt="">
+	<p>Pay by transfering USD from your Perfect Money wallet.</p>
+	</div>
+
+	<div id="agreement-check">
+	<input v-model="agreed" type="checkbox"><label>I have read and agree to the <a
+	href="/terms-and-policy">Terms and Policy</a> and will not pursue a dispute or chargeback.</label>
+	<div id="payment-error"></div>
+	</div>
+</section>
+
+<section class="credits-confirm">
+	<button @click="pay" :disabled="!ready"
+	class="brand-btn">Buy<loading v-if="loading"></loading></button>
+</section>
+
+</template>
+
+<script>
+import Loading from '../icons/loading.vue'
+
+function total() {
+	return this.packs.credits10*10.99 + this.packs.credits50*54.99
+	+ this.packs.credits100*109.99 + this.packs.credits1000*1010
+}
+
+//Gets the secret key specific to chosen payment amount and user
+function getSecret() {
+	document.getElementById('credits-errors').textContent = ''
+	this.loading = true
+	return fetch('/panel/secret', {
+		method: 'POST',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': this.token},
+		body: JSON.stringify({'packs': this.packs})
+	}).then((response) => {
+		if (response.ok) {
+			return response.text()
+		} else {
+			document.getElementById('credits-errors').textContent =
+			`${response.status}: ${response.statusText}`
+		}
+	}).then(secret => {
+		this.loading = false
+		return secret
+	})
+}
+
+function pay() {
+	if (this.method == 'payeer') {
+		this.payPayeer()
+	} else if (this.method == 'pm') {
+		this.payPm()
+	}
+}
+
+function makeInput(name, value) {
+	let input = document.createElement('input')
+	input.type = 'hidden'
+	input.name = name
+	input.value = value
+	return input
+}
+
+function payPayeer() {
+	fetch('/panel/payeer', {
+		method: 'POST',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': this.token},
+		body: JSON.stringify({'packs': this.packs})
+		}).then(response => {return response.json()}).then(data => {
+			let form = document.createElement('form')
+			document.body.appendChild(form)
+			form.method = 'POST'
+			form.action = 'https://payeer.com/merchant/'
+			form.appendChild(this.makeInput('m_shop', data.shop))
+			form.appendChild(this.makeInput('m_orderid', data.transaction))
+			form.appendChild(this.makeInput('m_amount', data.amount))
+			form.appendChild(this.makeInput('m_curr', 'USD'))
+			form.appendChild(this.makeInput('m_desc', data.description))
+			form.appendChild(this.makeInput('m_sign', data.signature))
+			form.appendChild(this.makeInput('m_params', data.params))
+			form.appendChild(this.makeInput('m_cipher_method', 'AES-256-CBC'))
+			form.submit()
+			/* console.log(data.signature) */
+		})
+
+}
+
+function payPm() {
+	fetch('/panel/pm', {
+		method: 'POST',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': this.token},
+		body: JSON.stringify({'packs': this.packs})
+		}).then(response => {return response.json()}).then(data => {
+			let form = document.createElement('form')
+			document.body.appendChild(form)
+			form.method = 'POST'
+			form.action = 'https://perfectmoney.is/api/step1.asp'
+			form.appendChild(this.makeInput('PAYEE_ACCOUNT', data.account))
+			form.appendChild(this.makeInput('PAYEE_NAME', 'Trendplays Network'))
+			form.appendChild(this.makeInput('PAYMENT_AMOUNT', data.amount))
+			form.appendChild(this.makeInput('PAYMENT_UNITS', 'USD'))
+			form.appendChild(this.makeInput('PAYMENT_ID', data.transaction))
+			form.appendChild(this.makeInput('STATUS_URL',
+			'https://trendplays.com/hooks/pm-transaction'))
+			form.appendChild(this.makeInput('PAYMENT_URL',
+			'https://trendplays.com/panel/transaction-complete'))
+			form.appendChild(this.makeInput('PAYMENT_URL_METHOD', 'POST'))
+			form.appendChild(this.makeInput('NOPAYMENT_URL',
+			'https://trendplays.com/panel/transaction-failed'))
+			form.appendChild(this.makeInput('NOPAYMENT_URL_METHOD', 'GET'))
+			form.appendChild(this.makeInput('SUGGESTED_MEMO', data.description))
+			form.appendChild(this.makeInput('SUGGESTED_MEMO_NOCHANGE', true))
+			form.submit()
+		})
+}
+
+function ready() {
+	if (this.packs.credis10 < 0) {
+		return false
+	} else if (this.packs.credis50 < 0) {
+		return false	
+	} else if (this.packs.credis100 < 0) {
+		return false
+	} else if (this.packs.credis1000 < 0) {
+		return false
+	}
+
+	return this.total > 0 && !this.loading && this.agreed
+}
+
+export default {
+	components:{Loading},
+	data() {
+		return {packs: {credits10: 0, credits50: 0,
+		credits100: 0, credits1000: 0}, loading: false, method: 'payeer',
+		agreed: false
+		}
+	},
+	computed: {total, ready},
+	methods: {getSecret, pay, payPm, payPayeer, makeInput},
+	props: ['preferred', 'token'],
+	emits: ['purchaseComplete'],
+}
+</script>
diff --git a/javascript-vue/js/panel/edit-cards.vue b/javascript-vue/js/panel/edit-cards.vue
new file mode 100644
index 0000000..31bf0ac
--- /dev/null
+++ b/javascript-vue/js/panel/edit-cards.vue
@@ -0,0 +1,82 @@
+<template>
+<div v-if="cards && cards.length > 0">
+<div class="saved-cards-heading">
+<h5>Card</h5> <h5>Default</h5> <h5>Delete</h5>
+</div>
+<div v-for="card in cards" :key="card.id" class="saved-card">
+	<span>{{card.card.brand[0].toUpperCase() + card.card.brand.substring(1)}}
+	(••••{{card.card.last4}})</span> 
+	<span><input :checked="card.id == preferred" :value="card.id" name="selected-card" type="radio"
+	@change="change(card.id)"></span>
+	<span><img @click="remove(card.id)" src="../../images/close-icon-black.svg"/></span>
+</div>
+<p id="billing-error"></p>
+</div>
+</template>
+
+<script>
+function get() {
+	fetch('/panel/cards', {
+		method: 'GET',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': this.token}
+	}).then((response) => {response.json().then(data => {
+		this.cards = data.data
+	})})
+}
+
+function change(card) {
+	fetch('/panel/change-card', {
+		method: 'POST',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': this.token},
+		body: JSON.stringify({'card': card})
+	}).then((response) => {
+		if (response.ok) {
+			response.json().then((data) => {
+				this.cards = data.data
+			})
+		} else {
+			console.log('bad')
+			document.getElementById("billing-error").textContent =
+			`${response.status}: ${response.statusText}`
+		}
+	})
+}
+
+function remove(card) {
+	if (card.length == 1) {
+		return
+	}
+
+	fetch('/panel/delete-card', {
+		method: 'POST',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': this.token},
+		body: JSON.stringify({'card': card})
+	}).then((response) => {
+		if (response.ok) {
+			response.json().then((data) => {
+				this.cards = data.data
+			})
+		} else {
+			document.getElementById("billing-error").textContent =
+			`${response.status}: ${response.statusText}`
+		}
+	})
+}
+
+export default {
+	data() {
+		return {cards: null}
+	},
+	methods: {get, change, remove},
+	created() {
+		this.get()
+	},
+	props: ['token', 'preferred'],
+}
+</script>
diff --git a/javascript-vue/js/panel/order-item.vue b/javascript-vue/js/panel/order-item.vue
new file mode 100644
index 0000000..171dad3
--- /dev/null
+++ b/javascript-vue/js/panel/order-item.vue
@@ -0,0 +1,71 @@
+<template>
+<div id="overlay" v-if="selected">
+
+	<img @click="$emit('close')" class="cancel icon"
+	src="../../images/cancel-icon2.svg" alt=""/>
+	<div class="overlay-item">
+		<img v-if="selected.service.site == 'youtube'" class="icon"
+		src="../../images/youtube-icon.svg" alt=""/>
+		<img v-if="selected.service.site == 'instagram'" class="icon"
+		src="../../images/instagram-icon.svg" alt=""/>
+		<img v-if="selected.service.site == 'twitter'" class="icon"
+		src="../../images/twitter.svg" alt=""/>
+		<img v-if="selected.service.site == 'tiktok'" class="icon"
+		src="../../images/tik-tok.svg" alt=""/>
+		<h3>{{selected.service.name}}</h3>
+
+	<div class="details">
+		<ul>
+			<li><b>Status:</b> <span>{{selected.status.charAt(0).toUpperCase() +
+			selected.status.slice(1)}}</span></li>
+			<li><b>Quantity:</b> <span>{{selected.quantity}}</span></li>
+			<li><b>Remaining:</b> <span>{{selected.remaining}}</span></li>
+			<li><b>URL:</b> <span>{{selected.url}}</span></li>
+		</ul>
+	</div>
+
+	<div v-if="selected.status == 'processing' || selected.status ==
+	'error'" class="change-url">
+		<h4>URL</h4>
+		<div><input v-model="url" type="url" id="url"></div> 
+		<button @click="saveURL" :disabled="loading">Save
+		<loading-icon v-if="loading"></loading-icon></button>
+		<p id="overlay-error">{{errorMessage}}</p>
+	</div>
+
+	</div>
+
+	</div>
+</template>
+
+<script>
+import LoadingIcon from '../icons/loading.vue'
+
+function saveURL() {
+	fetch('/panel/save-url', {
+		method: 'POST',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': this.token},
+		body: JSON.stringify({'url': this.url, 'order': this.selected.id})
+		}).then(response => {
+			if (response.ok) {
+				this.errorMessage = 'Saved'
+				this.$emit('changeUrl', this.url)
+			} else {
+				this.errorMessage = 'An error occured'
+			}
+		})
+
+}
+
+export default {
+	data() {
+		return {loading: false, errorMessage: '', url: this.selected.url}
+	},
+	components: {LoadingIcon},
+	methods: {saveURL},
+	props: ['selected', 'token'],
+	emits: ['changeUrl', 'close']
+}
+</script>
diff --git a/javascript-vue/js/panel/orders.vue b/javascript-vue/js/panel/orders.vue
new file mode 100644
index 0000000..c743c09
--- /dev/null
+++ b/javascript-vue/js/panel/orders.vue
@@ -0,0 +1,100 @@
+<template>
+<div>
+	<section class="pending-pane">
+	<div class="actions"><a class="new-order" href="#new-order">New</a><a
+	class="new-order" href="#credits">Add Credits</a></div>
+	<h4>Pending Orders</h4>
+	<ul>
+		<template v-bind:key='order.id' v-for="order in orders">
+		<div class="pending-item" v-if="order.status == 'pending'">
+			<div class="pending-heading">
+			<li @click="togglePending($event)">{{order.service.name}} ({{order.updated_at}})</li>
+			<img class="chevron" src="../../images/chevron-down.svg" alt="">
+			</div>
+		<div class="pending-content">
+			<p>ID: {{order.id}}<br>URL: {{order.url}}<br>Quantity:
+			{{order.quantity}}<br>Note: {{order.note}}</p>
+		</div>
+		</div>
+		</template>
+	</ul>
+	</section>
+	<div class="info-grey"><p>Orders are typically completed within 1-5
+	days.</p><div></div></div>
+	<section class="history-pane">
+		<h4>Order History</h4>
+
+	<div class="table-scroller">
+	<table>
+		<thead><th>Date</th><th>ID</th><th>Name</th><th>Status</th>
+		<th>Quantity</th></thead>
+		<tbody>
+		<tr v-bind:key='order.id' v-for='order in
+		orders.slice(historyPage*10-10, historyPage*10)'>
+		<td>{{order.updated_at}}</td>
+		<td>{{order.id}}</td>
+		<td>{{order.service.name}}</td>
+		<td :class="order.status"
+		class="status"><span>{{order.status.charAt(0).toUpperCase() +
+		order.status.slice(1)}}</span></td>
+		<td>{{order.quantity}}</td>
+		<td> <eye @click="select(order)"></eye> </td>
+		</tr>
+		</tbody>
+	</table>
+	</div>
+
+	<img @click="moveHistory(false)" class="nav-btn left"
+	src="../../images/arrow-left-circle-fill.svg" alt=""/>
+	<p class="nav-legend">{{historyPage}}/{{Math.ceil(orders.length/10)}}</p>
+	<img @click="moveHistory(true)" class="nav-btn right"
+	src="../../images/arrow-right-circle-fill.svg" alt=""/>
+
+	</section>
+
+	<order-item v-if="selected" @close="close" :selected="selected"
+	:token="token" @change-url="(url) => selected.url = url"></order-item>
+
+</div>
+</template>
+
+<script>
+import Eye from '../icons/eye-fill.vue'
+import OrderItem from './order-item.vue'
+
+function togglePending(event) {
+	event.target.parentNode.parentNode.classList.toggle('selected')
+}
+
+function moveHistory(forward) {
+	if (forward) {
+		this.historyPage += 1
+	} else {
+		this.historyPage -= 1
+	}
+	if (this.historyPage < 1) {
+		this.historyPage = 1
+		return
+	} else if (this.historyPage > this.orders.length/10+1) {
+		this.historyPage -= 1
+		return
+	}
+}
+
+function close() {
+	this.selected = null
+}
+
+function select(order) {
+	this.selected = order
+}
+
+export default {
+	components: {Eye, OrderItem},
+	data() {return {historyPage: 1, selected: null}},
+	methods: {
+	togglePending, moveHistory, close, select
+	},
+	props: ['orders', 'token'],
+}
+</script>
diff --git a/vue/panel.vue b/javascript-vue/js/panel/panel.vue
similarity index 95%
rename from vue/panel.vue
rename to javascript-vue/js/panel/panel.vue
index 08e866a..3dcde3c 100644
--- a/vue/panel.vue
+++ b/javascript-vue/js/panel/panel.vue
@@ -9,7 +9,8 @@
 	{{(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>
+	being tested. If you experience a delay in credits being added to your
+	account, please wait 24 hours before contacting support@trendplays.com.</p>
 	</section>
 	<section class="recent-pane"><h4>Recent Activity</h4>
 	<table>
diff --git a/javascript-vue/js/panel/payment-card.vue b/javascript-vue/js/panel/payment-card.vue
new file mode 100644
index 0000000..7bdb90a
--- /dev/null
+++ b/javascript-vue/js/panel/payment-card.vue
@@ -0,0 +1,42 @@
+<template>
+	<form id="payment-form" action="">
+	<label for="name">Name on Card</label>
+	<input @input="$emit('updateBillingName', $event)" id="billing-name" type="name">
+	<div id="card-element"></div>
+	<div id="card-errors"></div>
+	<div id=save-card>
+	<input name="save-card" type="checkbox" checked="true">
+	<label for="">Save Card</label>
+	</div>
+	</form>
+</template>
+
+<script>
+function mountPaymentForm() {
+	let card = this.stripe.elements().create('card')
+	card.mount('#card-element')
+	this.$emit('setCard', card)
+
+	card.on('change', function(event) {
+	let displayError = document.getElementById('card-errors');
+	if (event.error) {
+		displayError.textContent = event.error.message;
+		displayError.textContent = '';
+		this.$emit('cardValid', true)
+	} else {
+		displayError.textContent = '';
+		this.$emit('cardValid', true)
+	}
+	}.bind(this));
+}
+
+export default {
+	data() {
+		return {billingName: null}
+	},
+	methods: {mountPaymentForm},
+	props: ['stripe'],
+	mounted: mountPaymentForm,
+	emits: ['updateBillingName', 'cardValid', 'setCard']
+}
+</script>
diff --git a/javascript-vue/js/panel/payment-slider.vue b/javascript-vue/js/panel/payment-slider.vue
new file mode 100644
index 0000000..cba5483
--- /dev/null
+++ b/javascript-vue/js/panel/payment-slider.vue
@@ -0,0 +1,17 @@
+<template>
+	<div class="services-menu">
+	<a href="#new-order" :class="{selected: page == 'new-order'}">Services</a>
+	<a href="#credits" :class="{selected: page == 'credits'}">Credits</a>
+	<div :class="page" class="menu-slider"><div></div></div>
+	</div>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			items: null
+		}
+	}
+}
+</script>
diff --git a/javascript-vue/js/panel/saved-cards.vue b/javascript-vue/js/panel/saved-cards.vue
new file mode 100644
index 0000000..3a6a6dc
--- /dev/null
+++ b/javascript-vue/js/panel/saved-cards.vue
@@ -0,0 +1,28 @@
+<template>
+<div v-if="cards">
+<div v-for="(card, index) in cards" :key="card.id" class="saved-card">
+	<span>{{card.card.brand[0].toUpperCase() + card.card.brand.substring(1)}}
+	(••••{{card.card.last4}})</span> 
+	<input :checked="index === 0 || card.id == preferred" :value="card.id" name="selected-card" type="radio"
+	@change="$emit('update:pickedCard', card.id)">
+</div>
+</div>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {}
+		},
+		mounted() {
+			if (this.cards && this.cards.length > 0) {
+				this.$emit('update:pickedCard', this.cards[0].id)
+			}
+		},
+		unmounted() {
+			this.$emit('update:pickedCard', null)
+		},
+		props: ['token', 'cards', 'preferred'],
+		emits: ['update:pickedCard']
+	}
+</script>
diff --git a/javascript-vue/js/panel/service-pane.vue b/javascript-vue/js/panel/service-pane.vue
new file mode 100644
index 0000000..c96b1ab
--- /dev/null
+++ b/javascript-vue/js/panel/service-pane.vue
@@ -0,0 +1,28 @@
+<template>
+<section class="services-pane youtube" >
+	<h4>{{site.charAt(0).toUpperCase() + site.slice(1)}}</h4>
+	<ul :key="service.id" v-for="service in filter">
+		<li v-if="service.available"><span>{{service.name}}</span><span>{{(service.price/100).toLocaleString('en')}}</span><span>{{service.minimum.toLocaleString('en')}}</span><span>{{service.maximum.toLocaleString('en')}}</span>
+<svg @click="$emit('select', service)" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-square-fill" viewBox="0 0 16 16">
+  <path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z"/>
+</svg>
+		</li>
+	</ul>
+	</section>
+</template>
+
+<script>
+function filter() {
+	if (!this.services || !this.site) {return}
+
+	return this.services.filter((s) => {
+		return s.site == this.site
+	})
+}
+
+export default {
+	props: ['services', 'site'],
+	emits: ['select'],
+	computed: {filter}
+}
+</script>
diff --git a/javascript-vue/js/panel/services.vue b/javascript-vue/js/panel/services.vue
new file mode 100644
index 0000000..899db6d
--- /dev/null
+++ b/javascript-vue/js/panel/services.vue
@@ -0,0 +1,196 @@
+<template>
+<div>
+	<div class="sliding-menu">
+	<a href="#new-order" :class="{selected: page == 'new-order'}">Services</a>
+	<a href="#credits" :class="{selected: page == 'credits'}">Credits</a>
+	<div :class="page" class="menu-slider"><div></div></div>
+	</div>
+	<h4 class="credits-display"><img class="icon" src="../../images/coin-stack.svg" alt=""><span> {{(credits/100).toLocaleString('en')}}</span></h4>
+
+	<template v-if="page == 'new-order'">
+
+	<div class="services-legend">
+	<h5>Name</h5><h5>Credits per 1000</h5><h5>Min Qt.</h5><h5>Max Qt.</h5>
+	</div>
+
+	<ServicePane :site="'youtube'" :services="services"
+	@select="select"></ServicePane>
+
+	<ServicePane :site="'instagram'" :services="services"
+	@select="select"></ServicePane> 
+
+	<ServicePane :site="'twitter'" :services="services"
+	@select="select"></ServicePane>
+
+	<ServicePane :site="'tiktok'" :services="services"
+	@select="select"></ServicePane>
+
+	<div id="overlay" v-if="selected">
+
+	<div v-if="!completed" class="overlay-item">
+	<img @click="completed = false; selected = null" class="cancel icon"
+	src="../../images/cancel-icon2.svg" alt=""/>
+		<img v-if="selected.site == 'youtube'" class="icon"
+		src="../../images/youtube-icon.svg" alt=""/>
+		<img v-if="selected.site == 'instagram'" class="icon"
+		src="../../images/instagram-icon.svg" alt=""/>
+		<img v-if="selected.site == 'twitter'" class="icon"
+		src="../../images/twitter.svg" alt=""/>
+		<img v-if="selected.site == 'tiktok'" class="icon"
+		src="../../images/tik-tok.svg" alt=""/>
+		<h3>{{selected.name}}</h3>
+		<h4>Cost: {{(cost).toLocaleString('en')}}</h4>
+		<h4>Quantity</h4>
+		<div><input required :min="selected.minimum" :max="selected.maximum"
+		type="number" v-model="amount" id="selQty"><span> /
+		{{selected.maximum.toLocaleString('en')}}</span></div> 
+
+		<template v-if="selected.modifier == 'location'">
+		<h4>Location</h4>
+		<div><select required id="country" name="">
+		<option value="usa">USA</option>
+		<option value="canada">Canada</option>
+		<option value="uk">United Kingdom</option>
+		<option value="germany">Germany</option>
+		<option value="france">France</option>
+		</select>
+		</div> 
+		</template>
+
+		<template v-if="selected.modifier == 'language'">
+		<h4>Location</h4>
+		<div><select required id="language" name="">
+		<option value="english">English</option>
+		<option value="french">French</option>
+		<option value="spanish">Spanish</option>
+		<option value="german">German</option>
+		<option value="arabic">Arabic</option>
+		</select>
+		</div> 
+		</template>
+
+		<h4>URL</h4>
+		<div><input required type="url" id="url" v-model="url"></div> 
+		<button @click="buyService" :disabled="paying">Submit<loading
+		v-if="paying"></loading></button>
+		<p id="overlay-error">{{errorText}}</p>
+	</div>
+
+	<div class="overlay-item" v-else-if="completed">
+	<img @click="completed = false; selected = null" class="cancel icon"
+	src="../../images/cancel-icon2.svg" alt=""/>
+	<img class="icon" src="../../images/checked2.svg" alt=""/>
+	<h3>Success!</h3>
+	</div>
+
+	</div>
+
+	</template>
+
+	<credits @purchase-complete="$emit('updateUser')" :preferred="preferred" :token="token" v-if="page == 'credits'"></credits>
+
+</div>
+</template>
+
+<script>
+import ServicePane from './service-pane.vue'
+import Credits from './credits.vue'
+import Loading from '../icons/loading.vue'
+
+function select(service) {
+	this.completed = false
+	if (this.amount < service.minimum){
+		this.amount = service.minimum;
+	}
+	if (this.amount > service.maximum){
+		this.amount = service.maximum;
+	}
+
+	this.selected = service
+	this.errorText = ''
+}
+
+function cost() {
+	return (this.selected.price * this.amount / 100000).toFixed(2)
+}
+
+function buyService() {
+	if (!this.url) {
+		this.errorText = "You must provide a URL."	
+		return
+	} else if (Math.ceil(this.cost > this.credits)) {
+		this.errorText = 'Insuficient Credits'
+		return
+	} else if (this.amount < this.selected.minimum || this.amount > this.selected.maximum) {
+		this.errorText = 'Invalid amount'
+		return
+	}
+
+	this.paying = true
+	let note = ''
+	let country = document.getElementById('country')
+	let language = document.getElementById('language')
+
+	if (country) {
+		note = JSON.stringify({'location': country.value})
+	} else if (language) {
+		note = JSON.stringify({'language': language.value})
+	}
+	
+	fetch('/panel/orders', {
+		method: 'POST',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': this.token},
+		body: JSON.stringify({'service': this.selected.id,
+		'quantity': this.amount, 'url': this.url, 'note': note}), }).then(
+			response => {
+				if (response.ok) {
+					this.errorText = `Success!`
+					this.completed = true
+					this.$emit('updateUser')
+					this.$emit('updateOrders')
+				} else if (response.status == 520) {
+					this.errorText = 'Insuficient Credits'
+				} else {
+					this.errorText = `Error ${response.status}:
+					${response.statusText}`
+				}
+
+				this.paying = false
+			}
+		)
+
+}
+
+function page() {
+	switch (this.active) {
+		case '#new-order':
+			return 'new-order'
+		case '#credits':
+			return 'credits'
+	}
+}
+
+export default {
+	data() {
+		return {servicePane: true, services: null, selected: null, amount: 0,
+		paying: false, url: '', completed: false, errorText: ''}
+	},
+	components: {ServicePane, Loading, Credits},
+	props: ['token', 'credits', 'active', 'preferred'],
+	created() {
+		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})
+		})
+	},
+	methods: {select, buyService},
+	computed: {cost, page},
+	emits: ['updateUser', 'updateOrders']
+}
+</script>
diff --git a/javascript-vue/js/panel/settings.vue b/javascript-vue/js/panel/settings.vue
new file mode 100644
index 0000000..21df833
--- /dev/null
+++ b/javascript-vue/js/panel/settings.vue
@@ -0,0 +1,119 @@
+<template>
+<div>
+	<h2>Settings</h2>
+	<section class="change-name-pane">
+		<h4>Name</h4>
+		<input :value="user.name" name="name" id="changed_name" type="text">
+		<button @click="changeName">Save <loading src="../../images/loading-white.svg" alt=""></loading></button>
+		<span></span>
+	</section>
+	<section class="change-email-pane">
+		<h4>Email</h4>
+		<input :value="user.email" name="email" type="text" id="changed_email">
+		<button @click="changeEmail">Save<img class="loading-icon" src="../../images/loading-white.svg" alt=""></button>
+		<span></span>
+	</section>
+	<section class="change-password-pane">
+		<h4>Change Password</h4>
+		<h5>Current Password</h5><input name="current_passowrd" id="current_password" type="password">
+		<h5>New Password</h5><input id="new_password" name="password" type="password">
+		<h5>Confirm Password</h5><input id="confirm_password" name="confirm_passowrd" type="password">
+		<button @click="changePassword">Save<img class="loading-icon" src="../../images/loading-white.svg" alt=""></button>
+		<span></span>
+	</section>
+</div>
+</template>
+
+<script>
+import Loading from '../icons/loading.vue'
+
+function changeName() {
+	let name = document.getElementById('changed_name').value
+	let info = document.querySelector('.change-name-pane span')
+	let pane = document.querySelector('.change-name-pane')
+
+	pane.classList.add('loading')
+	pane.classList.remove('error')
+	fetch("/panel/change-name", {
+		method: 'POST',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': this.token},
+		body: JSON.stringify({'name': name}),
+	}).then(response => {
+		if (response.ok) {
+			pane.classList.add('completed')
+			info.textContent = 'Completed'
+		} else {
+			pane.classList.add('error')
+			info.textContent = 'Error: ' + response.status
+		}
+
+		pane.classList.remove('loading')
+	})
+}
+
+function changeEmail() {
+	let email = document.getElementById('changed_email').value
+	let info = document.querySelector('.change-email-pane span')
+	let pane = document.querySelector('.change-email-pane')
+
+	pane.classList.add('loading')
+	pane.classList.remove('error')
+	fetch("/panel/change-email", {
+		method: 'POST',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': this.token},
+		body: JSON.stringify({'email': email}),
+	}).then(response => {
+		if (response.ok) {
+			pane.classList.add('completed')
+			info.textContent = 'Verification link sent'
+		} else {
+			pane.classList.add('error')
+			info.textContent = 'Error: ' + response.status
+		}
+
+		pane.classList.remove('loading')
+	})
+}
+
+function changePassword() {
+	let info = document.querySelector('.change-password-pane span')
+	let pane = document.querySelector('.change-password-pane')
+
+	pane.classList.add('loading')
+	pane.classList.remove('error')
+	fetch("/panel/change-password", {
+		method: 'POST',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': this.token},
+		body: JSON.stringify({'current_password':
+		document.getElementById('current_password').value,
+		'password': document.getElementById('new_password').value,
+		'password_confirmation':
+		document.getElementById('confirm_password').value}),
+	}).then(response => {
+		response.json().then(data => {console.log(data)})
+		if (response.ok) {
+			pane.classList.add('completed')
+			info.textContent = 'Completed'
+		} else {
+			pane.classList.add('error')
+			info.textContent = 'Error: ' + response.status
+		}
+
+		pane.classList.remove('loading')
+	})
+}
+
+export default {
+	components: {Loading,},
+	methods: {
+		changePassword, changeName, changeEmail
+	},
+	props: ['user', 'token']
+}
+</script>
diff --git a/javascript-vue/js/panel/sidebar.vue b/javascript-vue/js/panel/sidebar.vue
new file mode 100644
index 0000000..4404fdb
--- /dev/null
+++ b/javascript-vue/js/panel/sidebar.vue
@@ -0,0 +1,46 @@
+<template>
+<nav id="sidebar">
+<a :class="{selected: active == ''}" href="/panel#">
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-house-door-fill" viewBox="0 0 16 16">
+  <path d="M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5z"/>
+</svg>
+</a>
+<a :class="{selected: active == '#orders'}" href="/panel#orders">
+<svg fill="currentColor" enable-background="new 0 0 24 24" height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m16.12 1.929-10.891 5.576-4.329-2.13 10.699-5.283c.24-.122.528-.122.78 0z"/><path d="m23.088 5.375-11.082 5.49-4.15-2.045-.6-.305 10.903-5.575.6.304z"/><path d="m11.118 12.447-.012 11.553-10.614-5.539c-.3-.158-.492-.475-.492-.816v-10.688l4.498 2.216v3.896c0 .499.408.913.9.913s.9-.414.9-.913v-2.995l.6.292z"/><path d="m23.988 6.969-11.07 5.466-.012 11.553 11.094-5.793z"/></svg>
+</a>
+
+<a :class="{selected: active == '#new-order' || active == '#credits'}" href="/panel#new-order">
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M12,2C6.486,2,2,6.486,2,12s4.486,10,10,10c5.514,0,10-4.486,10-10S17.514,2,12,2z M17,13h-4v4h-2v-4H7v-2h4V7h2v4h4V13z"></path></svg>
+</a>
+
+<a :class="{selected: active == '#settings'}" href="/panel#settings">
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-gear-fill" viewBox="0 0 16 16">
+  <path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
+</svg>
+</a>
+
+<a :class="{selected: active == '#support'}" href="/panel#support">
+<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-life-preserver" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+  <path fill-rule="evenodd" d="M14.43 10.772l-2.788-1.115a4.015 4.015 0 0 1-1.985 1.985l1.115 2.788a7.025 7.025 0 0 0 3.658-3.658zM5.228 14.43l1.115-2.788a4.015 4.015 0 0 1-1.985-1.985L1.57 10.772a7.025 7.025 0 0 0 3.658 3.658zm9.202-9.202a7.025 7.025 0 0 0-3.658-3.658L9.657 4.358a4.015 4.015 0 0 1 1.985 1.985l2.788-1.115zm-8.087-.87L5.228 1.57A7.025 7.025 0 0 0 1.57 5.228l2.788 1.115a4.015 4.015 0 0 1 1.985-1.985zM8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm0-5a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
+</svg>
+</a>
+
+<a v-if="role == 'admin'" :class="{selected: active == '#admin'}" href="/telescope">
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-key-fill" viewBox="0 0 16 16">
+  <path d="M3.5 11.5a3.5 3.5 0 1 1 3.163-5H14L15.5 8 14 9.5l-1-1-1 1-1-1-1 1-1-1-1 1H6.663a3.5 3.5 0 0 1-3.163 2zM2.5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
+</svg>
+</a>
+<a :class="{selected: active == '#exit'}" href="/panel#exit">
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-door-open-fill" viewBox="0 0 16 16">
+  <path d="M1.5 15a.5.5 0 0 0 0 1h13a.5.5 0 0 0 0-1H13V2.5A1.5 1.5 0 0 0 11.5 1H11V.5a.5.5 0 0 0-.57-.495l-7 1A.5.5 0 0 0 3 1.5V15H1.5zM11 2h.5a.5.5 0 0 1 .5.5V15h-1V2zm-2.5 8c-.276 0-.5-.448-.5-1s.224-1 .5-1 .5.448.5 1-.224 1-.5 1z"/>
+</svg>
+</a>
+</nav>
+</template>
+
+<script>
+export default {
+	props: ['active', 'role']
+}
+</script>
+
diff --git a/javascript-vue/js/panel/summary.vue b/javascript-vue/js/panel/summary.vue
new file mode 100644
index 0000000..9a8bdf5
--- /dev/null
+++ b/javascript-vue/js/panel/summary.vue
@@ -0,0 +1,3 @@
+<template>
+<div id="main">important info here</div>
+</template>
diff --git a/javascript-vue/js/panel/support.vue b/javascript-vue/js/panel/support.vue
new file mode 100644
index 0000000..b9af205
--- /dev/null
+++ b/javascript-vue/js/panel/support.vue
@@ -0,0 +1,82 @@
+<template>
+<div class="support-section" id="main">
+
+<h2>Support</h2>
+
+<loading v-if="loading"></loading>
+
+<div v-if="!loading && complete" class="dialog">
+	<img class="icon" src="../../images/checked2.svg" alt="">
+	<h3>Ticket sent. An administrator will contact you soon.</h3>
+</div>
+
+<div v-if="!loading && !complete" id="support-form">
+
+<label for="">Topic</label>
+<select id="support-topic" name="" v-model="topic">
+	<option value="order">Order</option>
+	<option value="service">Service</option>
+	<option value="credits">Credits</option>
+	<option value="payment">Payment</option>
+	<option value="other">Other</option>
+</select>
+
+<label for="">Details</label>
+<textarea id="" name="" cols="30" rows="10" v-model="message"></textarea>
+<span class="note-grey">Include any relevant information like the order number,
+service name, etc</span>
+
+<button @click="send">Submit</button>
+
+<p class="error-message">{{errorMessage}}</p>
+
+</div>
+
+</div>
+</template>
+
+
+<script>
+import Loading from '../icons/loading.vue'
+
+function send() {
+	this.errorMessage = ''
+
+	if (!this.topic || !this.message) {
+		this.errorMessage = 'Topic and details cannot be blank.'
+		return
+	}
+
+	this.loading = true
+
+	fetch("/panel/support", {
+		method: 'POST',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': this.token},
+		body: JSON.stringify({'topic': this.topic, 'message': this.message})}).
+		then(response => {
+			if (response.ok) {
+				this.complete = true
+			} else {
+				this.complete = false
+				this.error = true
+				this.errorMessage = `${response.status}:
+				${response.statusText}`
+			}
+
+			this.loading = false
+		})
+
+}
+
+export default {
+	components: {Loading},
+	props: ['user', 'token'],
+	data() {
+		return {loading: false, complete: false, error: false, errorMessage:
+		'', topic: null, message: ''}
+	}, 
+	methods: {send}
+}
+</script>
diff --git a/javascript-vue/js/panel/transaction-endpoint.vue b/javascript-vue/js/panel/transaction-endpoint.vue
new file mode 100644
index 0000000..68d6da6
--- /dev/null
+++ b/javascript-vue/js/panel/transaction-endpoint.vue
@@ -0,0 +1,33 @@
+<template>
+<div v-once id="main">
+
+<div v-if="active == '#transaction-complete' && user.paying" class="status-dialog">
+	<img class="icon" src="../../images/checked2.svg" alt=""/>
+	<h3>Purchase complete.</h3>
+</div>
+
+<div v-if="active == '#transaction-failed' && user.paying" class="status-dialog">
+	<img class="icon" src="../../images/warning-colored.svg" alt=""/>
+	<h3>Purchase failed.</h3>
+</div>
+
+</div>
+</template>
+
+<script>
+export default {
+	props: ['token', 'user', 'active'],
+	emits: ['purchaseComplete'],
+
+	//Should check that user is actualling in the paying state. If so, send get
+	//request to panel/transaction-end. It's then() should emit a payment
+	//complete to panel for a user state refresh.
+	mounted() {
+	fetch('/panel/clear-paying', {
+		method: 'GET',
+		headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+		'X-XSRF-TOKEN': this.token}}).then(() => this.$emit('purchaseComplete'))
+	}
+}
+</script>
diff --git a/javascript-vue/js/register-area/register-area.vue b/javascript-vue/js/register-area/register-area.vue
new file mode 100644
index 0000000..311fa3a
--- /dev/null
+++ b/javascript-vue/js/register-area/register-area.vue
@@ -0,0 +1,168 @@
+<template>
+	<form v-if="active === 'register'" onsubmit = "event.preventDefault(); return false" id="register-form">
+		<h3>Registration</h3>
+		<div>{{errorMessage}}</div>
+		<div>
+		<label for='sender_name'>Name</label>
+		<input id='register-name' required type='name' name='sender_name' placeholder=''
+		spellcheck='false'>
+		</div>
+		<div>
+		<label for='sender_email'>Email</label>
+		<input v-on:input="checkPasswords" id='register-email' required type='email' name='sender_email' placeholder=''
+		spellcheck='false'>
+		</div>
+		<div>
+		<label for='sender_password'>Password</label>
+		<input v-on:input="checkPasswords" id='register-password' required type='password' name='sender_password'
+		placeholder='' spellcheck='false'>
+		</div>
+		<div>
+		<label for='confirm_password'>Confirm Password</label>
+		<input v-on:input="checkPasswords" id='confirm-password' required type='password'
+		name='sender_password' placeholder='' spellcheck='false'>
+		</div>
+		<button @click="register($event)" class="submit-btn" type="submit">Submit</button>
+	</form>
+	<form v-if="active === 'forgot'" v-on:submit="forgotPassword" id="forgot-form">
+	<h3>Forgot Password</h3>
+	<div>
+	<label for='sender_email'>Email</label>
+	<input id='forgot-email' required type='email' name='sender_email' placeholder=''
+	spellcheck='false'>
+	</div>
+	<button class="submit-btn" type="submit">Submit</button>
+	</form>
+	<img v-if="active === 'loading'" type="image/svg+xml" class="loading-icon" src="../../images/loading.svg" alt=""/>
+	<div v-if="active === 'register-completed'">
+		<img class="medium-icon" src="../../images/checked2.svg" alt="">
+		<h3>Success!</h3>
+		<p>A verification link has been sent to your inbox.</p>
+	</div>
+	<div v-if="active === 'forgot-completed'">
+		<img class="medium-icon" src="../../images/checked2.svg" alt="">
+		<h3>Success!</h3>
+		<p>A password reset link has been sent.</p>
+	</div>
+	<div v-if="active === 'error'">
+		<img class="medium-icon" src="../../images/warning-colored.svg" alt="">
+		<h3>An Error Occured.</h3>
+		<p>{{`${error}: ${errorMessage}`}}</p>
+	</div>
+	<div v-on:click="closeArea" class="cancel-button"></div>
+</template>
+
+<script>
+function getCookie(name) {
+  var re = new RegExp(name + "=([^;]+)")
+  var value = re.exec(document.cookie)
+
+  let v = (value != null) ? unescape(value[1]) : null
+  return v
+}
+
+function getToken() {
+	return fetch("/sanctum/csrf-cookie", {
+		method: 'GET'
+	}).then( () => {
+		this.token = this.getCookie('XSRF-TOKEN')
+		return this.token
+	})
+}
+	function register(event) {
+		event.preventDefault();
+		event.stopPropagation();
+
+		this.errorMessage = ''
+		let name = document.getElementById("register-name").value
+		let email = document.getElementById("register-email").value
+		let password = document.getElementById("register-password").value
+		let password_confirmation = document.getElementById("confirm-password").value
+
+		this.active = 'loading'
+
+		this.getToken().then(() => {fetch("/register", {
+			method: 'POST',
+			headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+			'X-XSRF-TOKEN': this.token},
+			body: JSON.stringify({"name": name,
+			"email": email,
+			"password": password,
+			"password_confirmation": password_confirmation})})
+		.then(response => {
+			//Give completed or error
+			if (response.ok) {
+				this.active = 'register-completed'
+			} else if (response.status != 500) {
+				response.json().then((e) => {
+					try {
+						for (let i in e.errors) {
+							this.errorMessage = e.errors[i].flat().join("\n") +
+							"\n" + this.errorMessage
+						}
+					} catch (x) {
+						this.errorMessage = e.message
+						}
+					})
+					this.active = 'register'
+			} else {
+				this.errorMessage = response.statusText
+				this.active = 'register'
+				}
+		});
+		})
+		return false
+	}
+
+	function checkPasswords() {
+		let passInput = document.getElementById('register-password')
+		let passInput2 = document.getElementById('confirm-password')
+		if (passInput.value != passInput2.value) {
+			passInput2.setCustomValidity('Passwords must be matching')
+		} else {
+			passInput2.setCustomValidity('');
+		}
+	}
+
+	function forgotPassword(event) {
+		let email = document.getElementById("forgot-email").value
+		this.active = 'loading'
+		fetch("/forgot-password", {
+			method: 'POST',
+			headers: {'Content-Type': 'application/json',
+			'Accept': 'application/json',
+			'X-XSRF-TOKEN': this.token},
+			body: JSON.stringify({"email": email})})
+		.then(response => {
+			if (response.ok) {
+				this.active = 'forgot-completed'
+			} else {
+				this.error = response.status
+				this.errorMessage = response.statusText
+				this.active = 'error'
+			}
+			/* console.log(response.json()) */
+		});
+		event.preventDefault();
+	}
+
+	module.exports = {
+		data() {
+		return {active: 'register', token: '', errorMessage: ''}
+		},
+		methods: {
+			getToken,
+			getCookie,
+			register,
+			checkPasswords,
+			forgotPassword,
+			closeArea() {
+				document.querySelector(".register-area").classList.remove("active")
+			},
+		},
+		mounted() {
+			this.getToken()			
+		}
+	}
+</script>