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