@@ -0,0 +1 @@ | |||||
require('./bootstrap'); |
@@ -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 | |||||
// }); |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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} | |||||
} | |||||
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -9,7 +9,8 @@ | |||||
{{(user.credits/100).toLocaleString('en')}}</p></section> | {{(user.credits/100).toLocaleString('en')}}</p></section> | ||||
<section class="alerts-pane"><h4>News and Announcements</h4> | <section class="alerts-pane"><h4>News and Announcements</h4> | ||||
<p>We've just launched. Thanks for joining us! Some features are still | <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> | ||||
<section class="recent-pane"><h4>Recent Activity</h4> | <section class="recent-pane"><h4>Recent Activity</h4> | ||||
<table> | <table> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> | |||||
@@ -0,0 +1,3 @@ | |||||
<template> | |||||
<div id="main">important info here</div> | |||||
</template> |
@@ -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> |
@@ -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> |
@@ -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> |