瀏覽代碼

Add more ES6 code

master
Immanuel Onyeka 3 年之前
父節點
當前提交
ab67f6726f
共有 26 個文件被更改,包括 1478 次插入1 次删除
  1. +1
    -0
      javascript-vue/js/app.js
  2. +28
    -0
      javascript-vue/js/bootstrap.js
  3. +8
    -0
      javascript-vue/js/icons/eye-fill.vue
  4. +67
    -0
      javascript-vue/js/icons/instagram.vue
  5. +3
    -0
      javascript-vue/js/icons/loading.vue
  6. +5
    -0
      javascript-vue/js/icons/plus-fill.vue
  7. +6
    -0
      javascript-vue/js/icons/plus.vue
  8. +39
    -0
      javascript-vue/js/icons/youtube.vue
  9. +116
    -0
      javascript-vue/js/main.js
  10. +0
    -0
      javascript-vue/js/panel/admin.vue
  11. +188
    -0
      javascript-vue/js/panel/credits.vue
  12. +82
    -0
      javascript-vue/js/panel/edit-cards.vue
  13. +71
    -0
      javascript-vue/js/panel/order-item.vue
  14. +100
    -0
      javascript-vue/js/panel/orders.vue
  15. +2
    -1
      javascript-vue/js/panel/panel.vue
  16. +42
    -0
      javascript-vue/js/panel/payment-card.vue
  17. +17
    -0
      javascript-vue/js/panel/payment-slider.vue
  18. +28
    -0
      javascript-vue/js/panel/saved-cards.vue
  19. +28
    -0
      javascript-vue/js/panel/service-pane.vue
  20. +196
    -0
      javascript-vue/js/panel/services.vue
  21. +119
    -0
      javascript-vue/js/panel/settings.vue
  22. +46
    -0
      javascript-vue/js/panel/sidebar.vue
  23. +3
    -0
      javascript-vue/js/panel/summary.vue
  24. +82
    -0
      javascript-vue/js/panel/support.vue
  25. +33
    -0
      javascript-vue/js/panel/transaction-endpoint.vue
  26. +168
    -0
      javascript-vue/js/register-area/register-area.vue

+ 1
- 0
javascript-vue/js/app.js 查看文件

@@ -0,0 +1 @@
require('./bootstrap');

+ 28
- 0
javascript-vue/js/bootstrap.js 查看文件

@@ -0,0 +1,28 @@
window._ = require('lodash');

/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/

window.axios = require('axios');

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/

// import Echo from 'laravel-echo';

// window.Pusher = require('pusher-js');

// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: process.env.MIX_PUSHER_APP_KEY,
// cluster: process.env.MIX_PUSHER_APP_CLUSTER,
// forceTLS: true
// });

+ 8
- 0
javascript-vue/js/icons/eye-fill.vue 查看文件

@@ -0,0 +1,8 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
fill="currentColor" class="bi bi-eye-fill" viewBox="0 0 16 16">
<path d="M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z"/>
<path d="M0 8s3-5.5 8-5.5S16 8 16 8s-3 5.5-8 5.5S0 8 0 8zm8 3.5a3.5 3.5 0
1 0 0-7 3.5 3.5 0 0 0 0 7z"/>
</svg>
</template>

+ 67
- 0
javascript-vue/js/icons/instagram.vue 查看文件

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 551.034 551.034" style="enable-background:new 0 0 551.034 551.034;" xml:space="preserve">
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="275.517" y1="4.57" x2="275.517" y2="549.72" gradientTransform="matrix(1 0 0 -1 0 554)">
<stop offset="0" style="stop-color:#E09B3D"/>
<stop offset="0.3" style="stop-color:#C74C4D"/>
<stop offset="0.6" style="stop-color:#C21975"/>
<stop offset="1" style="stop-color:#7024C4"/>
</linearGradient>
<path style="fill:url(#SVGID_1_);" d="M386.878,0H164.156C73.64,0,0,73.64,0,164.156v222.722
c0,90.516,73.64,164.156,164.156,164.156h222.722c90.516,0,164.156-73.64,164.156-164.156V164.156
C551.033,73.64,477.393,0,386.878,0z M495.6,386.878c0,60.045-48.677,108.722-108.722,108.722H164.156
c-60.045,0-108.722-48.677-108.722-108.722V164.156c0-60.046,48.677-108.722,108.722-108.722h222.722
c60.045,0,108.722,48.676,108.722,108.722L495.6,386.878L495.6,386.878z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="275.517" y1="4.57" x2="275.517" y2="549.72" gradientTransform="matrix(1 0 0 -1 0 554)">
<stop offset="0" style="stop-color:#E09B3D"/>
<stop offset="0.3" style="stop-color:#C74C4D"/>
<stop offset="0.6" style="stop-color:#C21975"/>
<stop offset="1" style="stop-color:#7024C4"/>
</linearGradient>
<path style="fill:url(#SVGID_2_);" d="M275.517,133C196.933,133,133,196.933,133,275.516s63.933,142.517,142.517,142.517
S418.034,354.1,418.034,275.516S354.101,133,275.517,133z M275.517,362.6c-48.095,0-87.083-38.988-87.083-87.083
s38.989-87.083,87.083-87.083c48.095,0,87.083,38.988,87.083,87.083C362.6,323.611,323.611,362.6,275.517,362.6z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="418.31" y1="4.57" x2="418.31" y2="549.72" gradientTransform="matrix(1 0 0 -1 0 554)">
<stop offset="0" style="stop-color:#E09B3D"/>
<stop offset="0.3" style="stop-color:#C74C4D"/>
<stop offset="0.6" style="stop-color:#C21975"/>
<stop offset="1" style="stop-color:#7024C4"/>
</linearGradient>
<circle style="fill:url(#SVGID_3_);" cx="418.31" cy="134.07" r="34.15"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

+ 3
- 0
javascript-vue/js/icons/loading.vue 查看文件

@@ -0,0 +1,3 @@
<template>
<svg class="loading-icon" data-set="loaders" data-loading="lazy" width="30px" height="30px" data-src="https://s2.svgbox.net/loaders.svg?ic=oval" data-icon="oval" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" color="" data-attributes-set="viewBox,xmlns,stroke,color" data-rendered="true"><g transform="translate(1 1)" stroke-width="2" fill="none" fill-rule="evenodd"><circle stroke-opacity=".5" cx="18" cy="18" r="18"></circle><path d="M36 18c0-9.94-8.06-18-18-18"><animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="1s" repeatCount="indefinite"></animateTransform></path></g></svg>
</template>

+ 5
- 0
javascript-vue/js/icons/plus-fill.vue 查看文件

@@ -0,0 +1,5 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-square-fill" viewBox="0 0 16 16">
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z"/>
</svg>
</template>

+ 6
- 0
javascript-vue/js/icons/plus.vue 查看文件

@@ -0,0 +1,6 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-square" viewBox="0 0 16 16">
<path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
</svg>
</template>

+ 39
- 0
javascript-vue/js/icons/youtube.vue 查看文件

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 461.001 461.001" style="enable-background:new 0 0 461.001 461.001;" xml:space="preserve">
<path style="fill:#F61C0D;" d="M365.257,67.393H95.744C42.866,67.393,0,110.259,0,163.137v134.728
c0,52.878,42.866,95.744,95.744,95.744h269.513c52.878,0,95.744-42.866,95.744-95.744V163.137
C461.001,110.259,418.135,67.393,365.257,67.393z M300.506,237.056l-126.06,60.123c-3.359,1.602-7.239-0.847-7.239-4.568V168.607
c0-3.774,3.982-6.22,7.348-4.514l126.06,63.881C304.363,229.873,304.298,235.248,300.506,237.056z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

+ 116
- 0
javascript-vue/js/main.js 查看文件

@@ -0,0 +1,116 @@
import RegisterArea from './register-area/register-area.vue'
import Panel from './panel/panel.vue'
import '../scss/main.scss'
import { createApp } from 'vue'
importAll(require.context('../images', false, /\.(png|jpe?g|svg)$/))

let heroText = document.querySelectorAll(".landing-hero h2,.landing-hero p")
let registerToggles = document.querySelectorAll(".register-btn, .register-area\
.cancel-button, .services-cards button")
let token = getCookie('XSRF-TOKEN')

function importAll(r) {
return r.keys().map(r)
}

function getCookie(name) {
var re = new RegExp(name + "=([^;]+)")
var value = re.exec(document.cookie)

return (value != null) ? unescape(value[1]) : null
}

function getToken() {
return fetch("/sanctum/csrf-cookie", {
method: 'GET'
}).then( () => {
token = getCookie('XSRF-TOKEN')
return token
})
}

function login(event) {
getToken().then(fetch("/login", {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': token},
body: JSON.stringify({"email":
document.getElementById("login_email").value,
"password": document.getElementById("login_password").value}),
}).then(response => {
if (response.ok) {
window.location.assign("/panel")
} else {
document.querySelector("#login_form .error").innerText =
"Invalid credentials."
}
}))
event.preventDefault()
// event.stopPropogation()
}


//Attempt to resend the verification link
function resendLink(event) {
fetch("/resend-verification", {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': token},
}).then(response => {
if (response.ok) {
event.target.parentNode.getElementsByTagName('h3')[0].innerText =
"The link has been resent."
} else {
event.target.parentNode.getElementsByTagName('h3')[0].innerText =
`${response.status} : ${response.statusText}`
}})
event.preventDefault();
}

function toggleNav() {
heroText.forEach(item => {
item.classList.toggle("hidden")
})
document.querySelector("nav form.login").classList.toggle("active")
this.classList.toggle("toggled")
}


if (window.location.pathname == '/') {
document.getElementById('nav_toggle').addEventListener('click', toggleNav)
document.querySelector('#login_form button').addEventListener('click', login)
const app = createApp(RegisterArea).mount('#app')
// app.token = token
if (!token) {app.token = getToken()}

//Triggers for registration menu
for (let i = 0; i < registerToggles.length; i++) {
registerToggles[i].addEventListener("click", function() {
document.querySelector(".register-area").classList.add("active")
app.active = 'register'
})
}
document.getElementById("forgot-password-btn").onclick = event => {
document.querySelector(".register-area").classList.add("active")
app.active = 'forgot'
event.preventDefault()
}

//FAQ collapsibles
let cols = document.getElementsByClassName("collapsible");

for (let i = 0; i < cols.length; i++) {
cols[i].addEventListener("click", function() {
this.classList.toggle("active");
});
}
} else if (window.location.pathname == '/verify-email') {
document.getElementById('resend_verification').addEventListener("click", resendLink)
} else if (window.location.pathname == '/panel') {
const app = createApp(Panel).mount('#panel')
getToken().then(()=> {app.token = token})
window.onhashchange = ()=>{app.active = location.hash}
}


+ 0
- 0
javascript-vue/js/panel/admin.vue 查看文件


+ 188
- 0
javascript-vue/js/panel/credits.vue 查看文件

@@ -0,0 +1,188 @@
<template>
<section class="select-credits">
<div class="credits-pane"><h2>10 Credits</h2>
<h3>$10.99</h3><div><span>Qty</span><input min="0" max="1000" v-model="packs.credits10" type="number"></div>
</div>

<div class="credits-pane"><div><h2>50 Credits</h2><span>+5 Free Credits</span></div>
<h3>$54.99 </h3><div><span>Qty</span><input min="0" max="1000" v-model="packs.credits50" type="number"></div>
</div>

<div class="credits-pane"><div><h2>100 Credits</h2><span>+10 Free Credits</span></div>
<h3>$109.99</h3> <div><span>Qty</span><input min="0" max="1000" v-model="packs.credits100" type="number"></div>
</div>

<div class="credits-pane"><div><h2>1000 Credits</h2><span>+150 Free Credits</span></div>
<h3>$1010.00</h3> <div><span>Qty</span><input min="0" max="1000" v-model="packs.credits1000" type="number"></div>
</div>
<h3>Total: ${{total.toLocaleString('en')}}</h3>
<div id="credits-errors"></div>
</section>

<section id="payment-section">
<h4>Select a payment method</h4>

<div class="sliding-menu">
<a @click="method = 'payeer'" :class="{selected: method == 'payeer'}">Payeer</a>
<a @click="method = 'pm'" :class="{selected: method == 'pm'}">Perfect Money</a>
<div :class="{right: (method == 'pm')}" class="menu-slider"><div></div></div>
</div>

<div v-if="method == 'payeer'" class="payment-window">
<img src="../../images/payeer.png" alt="">
<p>Payeer allows you to pay securely by transfering your choice of cryptocurrency
to a temporary address.
</p>
</div>

<div v-if="method == 'pm'" class="payment-window">
<img src="../../images/perfect_money.svg" alt="">
<p>Pay by transfering USD from your Perfect Money wallet.</p>
</div>

<div id="agreement-check">
<input v-model="agreed" type="checkbox"><label>I have read and agree to the <a
href="/terms-and-policy">Terms and Policy</a> and will not pursue a dispute or chargeback.</label>
<div id="payment-error"></div>
</div>
</section>

<section class="credits-confirm">
<button @click="pay" :disabled="!ready"
class="brand-btn">Buy<loading v-if="loading"></loading></button>
</section>

</template>

<script>
import Loading from '../icons/loading.vue'

function total() {
return this.packs.credits10*10.99 + this.packs.credits50*54.99
+ this.packs.credits100*109.99 + this.packs.credits1000*1010
}

//Gets the secret key specific to chosen payment amount and user
function getSecret() {
document.getElementById('credits-errors').textContent = ''
this.loading = true
return fetch('/panel/secret', {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token},
body: JSON.stringify({'packs': this.packs})
}).then((response) => {
if (response.ok) {
return response.text()
} else {
document.getElementById('credits-errors').textContent =
`${response.status}: ${response.statusText}`
}
}).then(secret => {
this.loading = false
return secret
})
}

function pay() {
if (this.method == 'payeer') {
this.payPayeer()
} else if (this.method == 'pm') {
this.payPm()
}
}

function makeInput(name, value) {
let input = document.createElement('input')
input.type = 'hidden'
input.name = name
input.value = value
return input
}

function payPayeer() {
fetch('/panel/payeer', {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token},
body: JSON.stringify({'packs': this.packs})
}).then(response => {return response.json()}).then(data => {
let form = document.createElement('form')
document.body.appendChild(form)
form.method = 'POST'
form.action = 'https://payeer.com/merchant/'
form.appendChild(this.makeInput('m_shop', data.shop))
form.appendChild(this.makeInput('m_orderid', data.transaction))
form.appendChild(this.makeInput('m_amount', data.amount))
form.appendChild(this.makeInput('m_curr', 'USD'))
form.appendChild(this.makeInput('m_desc', data.description))
form.appendChild(this.makeInput('m_sign', data.signature))
form.appendChild(this.makeInput('m_params', data.params))
form.appendChild(this.makeInput('m_cipher_method', 'AES-256-CBC'))
form.submit()
/* console.log(data.signature) */
})

}

function payPm() {
fetch('/panel/pm', {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token},
body: JSON.stringify({'packs': this.packs})
}).then(response => {return response.json()}).then(data => {
let form = document.createElement('form')
document.body.appendChild(form)
form.method = 'POST'
form.action = 'https://perfectmoney.is/api/step1.asp'
form.appendChild(this.makeInput('PAYEE_ACCOUNT', data.account))
form.appendChild(this.makeInput('PAYEE_NAME', 'Trendplays Network'))
form.appendChild(this.makeInput('PAYMENT_AMOUNT', data.amount))
form.appendChild(this.makeInput('PAYMENT_UNITS', 'USD'))
form.appendChild(this.makeInput('PAYMENT_ID', data.transaction))
form.appendChild(this.makeInput('STATUS_URL',
'https://trendplays.com/hooks/pm-transaction'))
form.appendChild(this.makeInput('PAYMENT_URL',
'https://trendplays.com/panel/transaction-complete'))
form.appendChild(this.makeInput('PAYMENT_URL_METHOD', 'POST'))
form.appendChild(this.makeInput('NOPAYMENT_URL',
'https://trendplays.com/panel/transaction-failed'))
form.appendChild(this.makeInput('NOPAYMENT_URL_METHOD', 'GET'))
form.appendChild(this.makeInput('SUGGESTED_MEMO', data.description))
form.appendChild(this.makeInput('SUGGESTED_MEMO_NOCHANGE', true))
form.submit()
})
}

function ready() {
if (this.packs.credis10 < 0) {
return false
} else if (this.packs.credis50 < 0) {
return false
} else if (this.packs.credis100 < 0) {
return false
} else if (this.packs.credis1000 < 0) {
return false
}

return this.total > 0 && !this.loading && this.agreed
}

export default {
components:{Loading},
data() {
return {packs: {credits10: 0, credits50: 0,
credits100: 0, credits1000: 0}, loading: false, method: 'payeer',
agreed: false
}
},
computed: {total, ready},
methods: {getSecret, pay, payPm, payPayeer, makeInput},
props: ['preferred', 'token'],
emits: ['purchaseComplete'],
}
</script>

+ 82
- 0
javascript-vue/js/panel/edit-cards.vue 查看文件

@@ -0,0 +1,82 @@
<template>
<div v-if="cards && cards.length > 0">
<div class="saved-cards-heading">
<h5>Card</h5> <h5>Default</h5> <h5>Delete</h5>
</div>
<div v-for="card in cards" :key="card.id" class="saved-card">
<span>{{card.card.brand[0].toUpperCase() + card.card.brand.substring(1)}}
(••••{{card.card.last4}})</span>
<span><input :checked="card.id == preferred" :value="card.id" name="selected-card" type="radio"
@change="change(card.id)"></span>
<span><img @click="remove(card.id)" src="../../images/close-icon-black.svg"/></span>
</div>
<p id="billing-error"></p>
</div>
</template>

<script>
function get() {
fetch('/panel/cards', {
method: 'GET',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token}
}).then((response) => {response.json().then(data => {
this.cards = data.data
})})
}

function change(card) {
fetch('/panel/change-card', {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token},
body: JSON.stringify({'card': card})
}).then((response) => {
if (response.ok) {
response.json().then((data) => {
this.cards = data.data
})
} else {
console.log('bad')
document.getElementById("billing-error").textContent =
`${response.status}: ${response.statusText}`
}
})
}

function remove(card) {
if (card.length == 1) {
return
}

fetch('/panel/delete-card', {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token},
body: JSON.stringify({'card': card})
}).then((response) => {
if (response.ok) {
response.json().then((data) => {
this.cards = data.data
})
} else {
document.getElementById("billing-error").textContent =
`${response.status}: ${response.statusText}`
}
})
}

export default {
data() {
return {cards: null}
},
methods: {get, change, remove},
created() {
this.get()
},
props: ['token', 'preferred'],
}
</script>

+ 71
- 0
javascript-vue/js/panel/order-item.vue 查看文件

@@ -0,0 +1,71 @@
<template>
<div id="overlay" v-if="selected">

<img @click="$emit('close')" class="cancel icon"
src="../../images/cancel-icon2.svg" alt=""/>
<div class="overlay-item">
<img v-if="selected.service.site == 'youtube'" class="icon"
src="../../images/youtube-icon.svg" alt=""/>
<img v-if="selected.service.site == 'instagram'" class="icon"
src="../../images/instagram-icon.svg" alt=""/>
<img v-if="selected.service.site == 'twitter'" class="icon"
src="../../images/twitter.svg" alt=""/>
<img v-if="selected.service.site == 'tiktok'" class="icon"
src="../../images/tik-tok.svg" alt=""/>
<h3>{{selected.service.name}}</h3>

<div class="details">
<ul>
<li><b>Status:</b> <span>{{selected.status.charAt(0).toUpperCase() +
selected.status.slice(1)}}</span></li>
<li><b>Quantity:</b> <span>{{selected.quantity}}</span></li>
<li><b>Remaining:</b> <span>{{selected.remaining}}</span></li>
<li><b>URL:</b> <span>{{selected.url}}</span></li>
</ul>
</div>

<div v-if="selected.status == 'processing' || selected.status ==
'error'" class="change-url">
<h4>URL</h4>
<div><input v-model="url" type="url" id="url"></div>
<button @click="saveURL" :disabled="loading">Save
<loading-icon v-if="loading"></loading-icon></button>
<p id="overlay-error">{{errorMessage}}</p>
</div>

</div>

</div>
</template>

<script>
import LoadingIcon from '../icons/loading.vue'

function saveURL() {
fetch('/panel/save-url', {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token},
body: JSON.stringify({'url': this.url, 'order': this.selected.id})
}).then(response => {
if (response.ok) {
this.errorMessage = 'Saved'
this.$emit('changeUrl', this.url)
} else {
this.errorMessage = 'An error occured'
}
})

}

export default {
data() {
return {loading: false, errorMessage: '', url: this.selected.url}
},
components: {LoadingIcon},
methods: {saveURL},
props: ['selected', 'token'],
emits: ['changeUrl', 'close']
}
</script>

+ 100
- 0
javascript-vue/js/panel/orders.vue 查看文件

@@ -0,0 +1,100 @@
<template>
<div>
<section class="pending-pane">
<div class="actions"><a class="new-order" href="#new-order">New</a><a
class="new-order" href="#credits">Add Credits</a></div>
<h4>Pending Orders</h4>
<ul>
<template v-bind:key='order.id' v-for="order in orders">
<div class="pending-item" v-if="order.status == 'pending'">
<div class="pending-heading">
<li @click="togglePending($event)">{{order.service.name}} ({{order.updated_at}})</li>
<img class="chevron" src="../../images/chevron-down.svg" alt="">
</div>
<div class="pending-content">
<p>ID: {{order.id}}<br>URL: {{order.url}}<br>Quantity:
{{order.quantity}}<br>Note: {{order.note}}</p>
</div>
</div>
</template>
</ul>
</section>
<div class="info-grey"><p>Orders are typically completed within 1-5
days.</p><div></div></div>
<section class="history-pane">
<h4>Order History</h4>

<div class="table-scroller">
<table>
<thead><th>Date</th><th>ID</th><th>Name</th><th>Status</th>
<th>Quantity</th></thead>
<tbody>
<tr v-bind:key='order.id' v-for='order in
orders.slice(historyPage*10-10, historyPage*10)'>
<td>{{order.updated_at}}</td>
<td>{{order.id}}</td>
<td>{{order.service.name}}</td>
<td :class="order.status"
class="status"><span>{{order.status.charAt(0).toUpperCase() +
order.status.slice(1)}}</span></td>
<td>{{order.quantity}}</td>
<td> <eye @click="select(order)"></eye> </td>
</tr>
</tbody>
</table>
</div>

<img @click="moveHistory(false)" class="nav-btn left"
src="../../images/arrow-left-circle-fill.svg" alt=""/>
<p class="nav-legend">{{historyPage}}/{{Math.ceil(orders.length/10)}}</p>
<img @click="moveHistory(true)" class="nav-btn right"
src="../../images/arrow-right-circle-fill.svg" alt=""/>

</section>

<order-item v-if="selected" @close="close" :selected="selected"
:token="token" @change-url="(url) => selected.url = url"></order-item>

</div>
</template>

<script>
import Eye from '../icons/eye-fill.vue'
import OrderItem from './order-item.vue'

function togglePending(event) {
event.target.parentNode.parentNode.classList.toggle('selected')
}

function moveHistory(forward) {
if (forward) {
this.historyPage += 1
} else {
this.historyPage -= 1
}
if (this.historyPage < 1) {
this.historyPage = 1
return
} else if (this.historyPage > this.orders.length/10+1) {
this.historyPage -= 1
return
}
}

function close() {
this.selected = null
}

function select(order) {
this.selected = order
}

export default {
components: {Eye, OrderItem},
data() {return {historyPage: 1, selected: null}},
methods: {
togglePending, moveHistory, close, select
},
props: ['orders', 'token'],
}
</script>

vue/panel.vue → javascript-vue/js/panel/panel.vue 查看文件

@@ -9,7 +9,8 @@
{{(user.credits/100).toLocaleString('en')}}</p></section>
<section class="alerts-pane"><h4>News and Announcements</h4>
<p>We've just launched. Thanks for joining us! Some features are still
being tested.</p>
being tested. If you experience a delay in credits being added to your
account, please wait 24 hours before contacting support@trendplays.com.</p>
</section>
<section class="recent-pane"><h4>Recent Activity</h4>
<table>

+ 42
- 0
javascript-vue/js/panel/payment-card.vue 查看文件

@@ -0,0 +1,42 @@
<template>
<form id="payment-form" action="">
<label for="name">Name on Card</label>
<input @input="$emit('updateBillingName', $event)" id="billing-name" type="name">
<div id="card-element"></div>
<div id="card-errors"></div>
<div id=save-card>
<input name="save-card" type="checkbox" checked="true">
<label for="">Save Card</label>
</div>
</form>
</template>

<script>
function mountPaymentForm() {
let card = this.stripe.elements().create('card')
card.mount('#card-element')
this.$emit('setCard', card)

card.on('change', function(event) {
let displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
displayError.textContent = '';
this.$emit('cardValid', true)
} else {
displayError.textContent = '';
this.$emit('cardValid', true)
}
}.bind(this));
}

export default {
data() {
return {billingName: null}
},
methods: {mountPaymentForm},
props: ['stripe'],
mounted: mountPaymentForm,
emits: ['updateBillingName', 'cardValid', 'setCard']
}
</script>

+ 17
- 0
javascript-vue/js/panel/payment-slider.vue 查看文件

@@ -0,0 +1,17 @@
<template>
<div class="services-menu">
<a href="#new-order" :class="{selected: page == 'new-order'}">Services</a>
<a href="#credits" :class="{selected: page == 'credits'}">Credits</a>
<div :class="page" class="menu-slider"><div></div></div>
</div>
</template>

<script>
export default {
data() {
return {
items: null
}
}
}
</script>

+ 28
- 0
javascript-vue/js/panel/saved-cards.vue 查看文件

@@ -0,0 +1,28 @@
<template>
<div v-if="cards">
<div v-for="(card, index) in cards" :key="card.id" class="saved-card">
<span>{{card.card.brand[0].toUpperCase() + card.card.brand.substring(1)}}
(••••{{card.card.last4}})</span>
<input :checked="index === 0 || card.id == preferred" :value="card.id" name="selected-card" type="radio"
@change="$emit('update:pickedCard', card.id)">
</div>
</div>
</template>

<script>
export default {
data() {
return {}
},
mounted() {
if (this.cards && this.cards.length > 0) {
this.$emit('update:pickedCard', this.cards[0].id)
}
},
unmounted() {
this.$emit('update:pickedCard', null)
},
props: ['token', 'cards', 'preferred'],
emits: ['update:pickedCard']
}
</script>

+ 28
- 0
javascript-vue/js/panel/service-pane.vue 查看文件

@@ -0,0 +1,28 @@
<template>
<section class="services-pane youtube" >
<h4>{{site.charAt(0).toUpperCase() + site.slice(1)}}</h4>
<ul :key="service.id" v-for="service in filter">
<li v-if="service.available"><span>{{service.name}}</span><span>{{(service.price/100).toLocaleString('en')}}</span><span>{{service.minimum.toLocaleString('en')}}</span><span>{{service.maximum.toLocaleString('en')}}</span>
<svg @click="$emit('select', service)" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-square-fill" viewBox="0 0 16 16">
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z"/>
</svg>
</li>
</ul>
</section>
</template>

<script>
function filter() {
if (!this.services || !this.site) {return}

return this.services.filter((s) => {
return s.site == this.site
})
}

export default {
props: ['services', 'site'],
emits: ['select'],
computed: {filter}
}
</script>

+ 196
- 0
javascript-vue/js/panel/services.vue 查看文件

@@ -0,0 +1,196 @@
<template>
<div>
<div class="sliding-menu">
<a href="#new-order" :class="{selected: page == 'new-order'}">Services</a>
<a href="#credits" :class="{selected: page == 'credits'}">Credits</a>
<div :class="page" class="menu-slider"><div></div></div>
</div>
<h4 class="credits-display"><img class="icon" src="../../images/coin-stack.svg" alt=""><span> {{(credits/100).toLocaleString('en')}}</span></h4>

<template v-if="page == 'new-order'">

<div class="services-legend">
<h5>Name</h5><h5>Credits per 1000</h5><h5>Min Qt.</h5><h5>Max Qt.</h5>
</div>

<ServicePane :site="'youtube'" :services="services"
@select="select"></ServicePane>

<ServicePane :site="'instagram'" :services="services"
@select="select"></ServicePane>

<ServicePane :site="'twitter'" :services="services"
@select="select"></ServicePane>

<ServicePane :site="'tiktok'" :services="services"
@select="select"></ServicePane>

<div id="overlay" v-if="selected">

<div v-if="!completed" class="overlay-item">
<img @click="completed = false; selected = null" class="cancel icon"
src="../../images/cancel-icon2.svg" alt=""/>
<img v-if="selected.site == 'youtube'" class="icon"
src="../../images/youtube-icon.svg" alt=""/>
<img v-if="selected.site == 'instagram'" class="icon"
src="../../images/instagram-icon.svg" alt=""/>
<img v-if="selected.site == 'twitter'" class="icon"
src="../../images/twitter.svg" alt=""/>
<img v-if="selected.site == 'tiktok'" class="icon"
src="../../images/tik-tok.svg" alt=""/>
<h3>{{selected.name}}</h3>
<h4>Cost: {{(cost).toLocaleString('en')}}</h4>
<h4>Quantity</h4>
<div><input required :min="selected.minimum" :max="selected.maximum"
type="number" v-model="amount" id="selQty"><span> /
{{selected.maximum.toLocaleString('en')}}</span></div>

<template v-if="selected.modifier == 'location'">
<h4>Location</h4>
<div><select required id="country" name="">
<option value="usa">USA</option>
<option value="canada">Canada</option>
<option value="uk">United Kingdom</option>
<option value="germany">Germany</option>
<option value="france">France</option>
</select>
</div>
</template>

<template v-if="selected.modifier == 'language'">
<h4>Location</h4>
<div><select required id="language" name="">
<option value="english">English</option>
<option value="french">French</option>
<option value="spanish">Spanish</option>
<option value="german">German</option>
<option value="arabic">Arabic</option>
</select>
</div>
</template>

<h4>URL</h4>
<div><input required type="url" id="url" v-model="url"></div>
<button @click="buyService" :disabled="paying">Submit<loading
v-if="paying"></loading></button>
<p id="overlay-error">{{errorText}}</p>
</div>

<div class="overlay-item" v-else-if="completed">
<img @click="completed = false; selected = null" class="cancel icon"
src="../../images/cancel-icon2.svg" alt=""/>
<img class="icon" src="../../images/checked2.svg" alt=""/>
<h3>Success!</h3>
</div>

</div>

</template>

<credits @purchase-complete="$emit('updateUser')" :preferred="preferred" :token="token" v-if="page == 'credits'"></credits>

</div>
</template>

<script>
import ServicePane from './service-pane.vue'
import Credits from './credits.vue'
import Loading from '../icons/loading.vue'

function select(service) {
this.completed = false
if (this.amount < service.minimum){
this.amount = service.minimum;
}
if (this.amount > service.maximum){
this.amount = service.maximum;
}

this.selected = service
this.errorText = ''
}

function cost() {
return (this.selected.price * this.amount / 100000).toFixed(2)
}

function buyService() {
if (!this.url) {
this.errorText = "You must provide a URL."
return
} else if (Math.ceil(this.cost > this.credits)) {
this.errorText = 'Insuficient Credits'
return
} else if (this.amount < this.selected.minimum || this.amount > this.selected.maximum) {
this.errorText = 'Invalid amount'
return
}

this.paying = true
let note = ''
let country = document.getElementById('country')
let language = document.getElementById('language')

if (country) {
note = JSON.stringify({'location': country.value})
} else if (language) {
note = JSON.stringify({'language': language.value})
}
fetch('/panel/orders', {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token},
body: JSON.stringify({'service': this.selected.id,
'quantity': this.amount, 'url': this.url, 'note': note}), }).then(
response => {
if (response.ok) {
this.errorText = `Success!`
this.completed = true
this.$emit('updateUser')
this.$emit('updateOrders')
} else if (response.status == 520) {
this.errorText = 'Insuficient Credits'
} else {
this.errorText = `Error ${response.status}:
${response.statusText}`
}

this.paying = false
}
)

}

function page() {
switch (this.active) {
case '#new-order':
return 'new-order'
case '#credits':
return 'credits'
}
}

export default {
data() {
return {servicePane: true, services: null, selected: null, amount: 0,
paying: false, url: '', completed: false, errorText: ''}
},
components: {ServicePane, Loading, Credits},
props: ['token', 'credits', 'active', 'preferred'],
created() {
fetch("/panel/services", {
method: 'GET',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token}
}).then(response => {
response.json().then(data => {this.services = data})
})
},
methods: {select, buyService},
computed: {cost, page},
emits: ['updateUser', 'updateOrders']
}
</script>

+ 119
- 0
javascript-vue/js/panel/settings.vue 查看文件

@@ -0,0 +1,119 @@
<template>
<div>
<h2>Settings</h2>
<section class="change-name-pane">
<h4>Name</h4>
<input :value="user.name" name="name" id="changed_name" type="text">
<button @click="changeName">Save <loading src="../../images/loading-white.svg" alt=""></loading></button>
<span></span>
</section>
<section class="change-email-pane">
<h4>Email</h4>
<input :value="user.email" name="email" type="text" id="changed_email">
<button @click="changeEmail">Save<img class="loading-icon" src="../../images/loading-white.svg" alt=""></button>
<span></span>
</section>
<section class="change-password-pane">
<h4>Change Password</h4>
<h5>Current Password</h5><input name="current_passowrd" id="current_password" type="password">
<h5>New Password</h5><input id="new_password" name="password" type="password">
<h5>Confirm Password</h5><input id="confirm_password" name="confirm_passowrd" type="password">
<button @click="changePassword">Save<img class="loading-icon" src="../../images/loading-white.svg" alt=""></button>
<span></span>
</section>
</div>
</template>

<script>
import Loading from '../icons/loading.vue'

function changeName() {
let name = document.getElementById('changed_name').value
let info = document.querySelector('.change-name-pane span')
let pane = document.querySelector('.change-name-pane')

pane.classList.add('loading')
pane.classList.remove('error')
fetch("/panel/change-name", {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token},
body: JSON.stringify({'name': name}),
}).then(response => {
if (response.ok) {
pane.classList.add('completed')
info.textContent = 'Completed'
} else {
pane.classList.add('error')
info.textContent = 'Error: ' + response.status
}

pane.classList.remove('loading')
})
}

function changeEmail() {
let email = document.getElementById('changed_email').value
let info = document.querySelector('.change-email-pane span')
let pane = document.querySelector('.change-email-pane')

pane.classList.add('loading')
pane.classList.remove('error')
fetch("/panel/change-email", {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token},
body: JSON.stringify({'email': email}),
}).then(response => {
if (response.ok) {
pane.classList.add('completed')
info.textContent = 'Verification link sent'
} else {
pane.classList.add('error')
info.textContent = 'Error: ' + response.status
}

pane.classList.remove('loading')
})
}

function changePassword() {
let info = document.querySelector('.change-password-pane span')
let pane = document.querySelector('.change-password-pane')

pane.classList.add('loading')
pane.classList.remove('error')
fetch("/panel/change-password", {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token},
body: JSON.stringify({'current_password':
document.getElementById('current_password').value,
'password': document.getElementById('new_password').value,
'password_confirmation':
document.getElementById('confirm_password').value}),
}).then(response => {
response.json().then(data => {console.log(data)})
if (response.ok) {
pane.classList.add('completed')
info.textContent = 'Completed'
} else {
pane.classList.add('error')
info.textContent = 'Error: ' + response.status
}

pane.classList.remove('loading')
})
}

export default {
components: {Loading,},
methods: {
changePassword, changeName, changeEmail
},
props: ['user', 'token']
}
</script>

+ 46
- 0
javascript-vue/js/panel/sidebar.vue 查看文件

@@ -0,0 +1,46 @@
<template>
<nav id="sidebar">
<a :class="{selected: active == ''}" href="/panel#">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-house-door-fill" viewBox="0 0 16 16">
<path d="M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5z"/>
</svg>
</a>
<a :class="{selected: active == '#orders'}" href="/panel#orders">
<svg fill="currentColor" enable-background="new 0 0 24 24" height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m16.12 1.929-10.891 5.576-4.329-2.13 10.699-5.283c.24-.122.528-.122.78 0z"/><path d="m23.088 5.375-11.082 5.49-4.15-2.045-.6-.305 10.903-5.575.6.304z"/><path d="m11.118 12.447-.012 11.553-10.614-5.539c-.3-.158-.492-.475-.492-.816v-10.688l4.498 2.216v3.896c0 .499.408.913.9.913s.9-.414.9-.913v-2.995l.6.292z"/><path d="m23.988 6.969-11.07 5.466-.012 11.553 11.094-5.793z"/></svg>
</a>

<a :class="{selected: active == '#new-order' || active == '#credits'}" href="/panel#new-order">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M12,2C6.486,2,2,6.486,2,12s4.486,10,10,10c5.514,0,10-4.486,10-10S17.514,2,12,2z M17,13h-4v4h-2v-4H7v-2h4V7h2v4h4V13z"></path></svg>
</a>

<a :class="{selected: active == '#settings'}" href="/panel#settings">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-gear-fill" viewBox="0 0 16 16">
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
</svg>
</a>

<a :class="{selected: active == '#support'}" href="/panel#support">
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-life-preserver" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M14.43 10.772l-2.788-1.115a4.015 4.015 0 0 1-1.985 1.985l1.115 2.788a7.025 7.025 0 0 0 3.658-3.658zM5.228 14.43l1.115-2.788a4.015 4.015 0 0 1-1.985-1.985L1.57 10.772a7.025 7.025 0 0 0 3.658 3.658zm9.202-9.202a7.025 7.025 0 0 0-3.658-3.658L9.657 4.358a4.015 4.015 0 0 1 1.985 1.985l2.788-1.115zm-8.087-.87L5.228 1.57A7.025 7.025 0 0 0 1.57 5.228l2.788 1.115a4.015 4.015 0 0 1 1.985-1.985zM8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm0-5a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
</svg>
</a>

<a v-if="role == 'admin'" :class="{selected: active == '#admin'}" href="/telescope">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-key-fill" viewBox="0 0 16 16">
<path d="M3.5 11.5a3.5 3.5 0 1 1 3.163-5H14L15.5 8 14 9.5l-1-1-1 1-1-1-1 1-1-1-1 1H6.663a3.5 3.5 0 0 1-3.163 2zM2.5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
</svg>
</a>
<a :class="{selected: active == '#exit'}" href="/panel#exit">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-door-open-fill" viewBox="0 0 16 16">
<path d="M1.5 15a.5.5 0 0 0 0 1h13a.5.5 0 0 0 0-1H13V2.5A1.5 1.5 0 0 0 11.5 1H11V.5a.5.5 0 0 0-.57-.495l-7 1A.5.5 0 0 0 3 1.5V15H1.5zM11 2h.5a.5.5 0 0 1 .5.5V15h-1V2zm-2.5 8c-.276 0-.5-.448-.5-1s.224-1 .5-1 .5.448.5 1-.224 1-.5 1z"/>
</svg>
</a>
</nav>
</template>

<script>
export default {
props: ['active', 'role']
}
</script>


+ 3
- 0
javascript-vue/js/panel/summary.vue 查看文件

@@ -0,0 +1,3 @@
<template>
<div id="main">important info here</div>
</template>

+ 82
- 0
javascript-vue/js/panel/support.vue 查看文件

@@ -0,0 +1,82 @@
<template>
<div class="support-section" id="main">

<h2>Support</h2>

<loading v-if="loading"></loading>

<div v-if="!loading && complete" class="dialog">
<img class="icon" src="../../images/checked2.svg" alt="">
<h3>Ticket sent. An administrator will contact you soon.</h3>
</div>

<div v-if="!loading && !complete" id="support-form">

<label for="">Topic</label>
<select id="support-topic" name="" v-model="topic">
<option value="order">Order</option>
<option value="service">Service</option>
<option value="credits">Credits</option>
<option value="payment">Payment</option>
<option value="other">Other</option>
</select>

<label for="">Details</label>
<textarea id="" name="" cols="30" rows="10" v-model="message"></textarea>
<span class="note-grey">Include any relevant information like the order number,
service name, etc</span>

<button @click="send">Submit</button>

<p class="error-message">{{errorMessage}}</p>

</div>

</div>
</template>


<script>
import Loading from '../icons/loading.vue'

function send() {
this.errorMessage = ''

if (!this.topic || !this.message) {
this.errorMessage = 'Topic and details cannot be blank.'
return
}

this.loading = true

fetch("/panel/support", {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token},
body: JSON.stringify({'topic': this.topic, 'message': this.message})}).
then(response => {
if (response.ok) {
this.complete = true
} else {
this.complete = false
this.error = true
this.errorMessage = `${response.status}:
${response.statusText}`
}

this.loading = false
})

}

export default {
components: {Loading},
props: ['user', 'token'],
data() {
return {loading: false, complete: false, error: false, errorMessage:
'', topic: null, message: ''}
},
methods: {send}
}
</script>

+ 33
- 0
javascript-vue/js/panel/transaction-endpoint.vue 查看文件

@@ -0,0 +1,33 @@
<template>
<div v-once id="main">

<div v-if="active == '#transaction-complete' && user.paying" class="status-dialog">
<img class="icon" src="../../images/checked2.svg" alt=""/>
<h3>Purchase complete.</h3>
</div>

<div v-if="active == '#transaction-failed' && user.paying" class="status-dialog">
<img class="icon" src="../../images/warning-colored.svg" alt=""/>
<h3>Purchase failed.</h3>
</div>

</div>
</template>

<script>
export default {
props: ['token', 'user', 'active'],
emits: ['purchaseComplete'],

//Should check that user is actualling in the paying state. If so, send get
//request to panel/transaction-end. It's then() should emit a payment
//complete to panel for a user state refresh.
mounted() {
fetch('/panel/clear-paying', {
method: 'GET',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token}}).then(() => this.$emit('purchaseComplete'))
}
}
</script>

+ 168
- 0
javascript-vue/js/register-area/register-area.vue 查看文件

@@ -0,0 +1,168 @@
<template>
<form v-if="active === 'register'" onsubmit = "event.preventDefault(); return false" id="register-form">
<h3>Registration</h3>
<div>{{errorMessage}}</div>
<div>
<label for='sender_name'>Name</label>
<input id='register-name' required type='name' name='sender_name' placeholder=''
spellcheck='false'>
</div>
<div>
<label for='sender_email'>Email</label>
<input v-on:input="checkPasswords" id='register-email' required type='email' name='sender_email' placeholder=''
spellcheck='false'>
</div>
<div>
<label for='sender_password'>Password</label>
<input v-on:input="checkPasswords" id='register-password' required type='password' name='sender_password'
placeholder='' spellcheck='false'>
</div>
<div>
<label for='confirm_password'>Confirm Password</label>
<input v-on:input="checkPasswords" id='confirm-password' required type='password'
name='sender_password' placeholder='' spellcheck='false'>
</div>
<button @click="register($event)" class="submit-btn" type="submit">Submit</button>
</form>
<form v-if="active === 'forgot'" v-on:submit="forgotPassword" id="forgot-form">
<h3>Forgot Password</h3>
<div>
<label for='sender_email'>Email</label>
<input id='forgot-email' required type='email' name='sender_email' placeholder=''
spellcheck='false'>
</div>
<button class="submit-btn" type="submit">Submit</button>
</form>
<img v-if="active === 'loading'" type="image/svg+xml" class="loading-icon" src="../../images/loading.svg" alt=""/>
<div v-if="active === 'register-completed'">
<img class="medium-icon" src="../../images/checked2.svg" alt="">
<h3>Success!</h3>
<p>A verification link has been sent to your inbox.</p>
</div>
<div v-if="active === 'forgot-completed'">
<img class="medium-icon" src="../../images/checked2.svg" alt="">
<h3>Success!</h3>
<p>A password reset link has been sent.</p>
</div>
<div v-if="active === 'error'">
<img class="medium-icon" src="../../images/warning-colored.svg" alt="">
<h3>An Error Occured.</h3>
<p>{{`${error}: ${errorMessage}`}}</p>
</div>
<div v-on:click="closeArea" class="cancel-button"></div>
</template>

<script>
function getCookie(name) {
var re = new RegExp(name + "=([^;]+)")
var value = re.exec(document.cookie)

let v = (value != null) ? unescape(value[1]) : null
return v
}

function getToken() {
return fetch("/sanctum/csrf-cookie", {
method: 'GET'
}).then( () => {
this.token = this.getCookie('XSRF-TOKEN')
return this.token
})
}
function register(event) {
event.preventDefault();
event.stopPropagation();

this.errorMessage = ''
let name = document.getElementById("register-name").value
let email = document.getElementById("register-email").value
let password = document.getElementById("register-password").value
let password_confirmation = document.getElementById("confirm-password").value

this.active = 'loading'

this.getToken().then(() => {fetch("/register", {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token},
body: JSON.stringify({"name": name,
"email": email,
"password": password,
"password_confirmation": password_confirmation})})
.then(response => {
//Give completed or error
if (response.ok) {
this.active = 'register-completed'
} else if (response.status != 500) {
response.json().then((e) => {
try {
for (let i in e.errors) {
this.errorMessage = e.errors[i].flat().join("\n") +
"\n" + this.errorMessage
}
} catch (x) {
this.errorMessage = e.message
}
})
this.active = 'register'
} else {
this.errorMessage = response.statusText
this.active = 'register'
}
});
})
return false
}

function checkPasswords() {
let passInput = document.getElementById('register-password')
let passInput2 = document.getElementById('confirm-password')
if (passInput.value != passInput2.value) {
passInput2.setCustomValidity('Passwords must be matching')
} else {
passInput2.setCustomValidity('');
}
}

function forgotPassword(event) {
let email = document.getElementById("forgot-email").value
this.active = 'loading'
fetch("/forgot-password", {
method: 'POST',
headers: {'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': this.token},
body: JSON.stringify({"email": email})})
.then(response => {
if (response.ok) {
this.active = 'forgot-completed'
} else {
this.error = response.status
this.errorMessage = response.statusText
this.active = 'error'
}
/* console.log(response.json()) */
});
event.preventDefault();
}

module.exports = {
data() {
return {active: 'register', token: '', errorMessage: ''}
},
methods: {
getToken,
getCookie,
register,
checkPasswords,
forgotPassword,
closeArea() {
document.querySelector(".register-area").classList.remove("active")
},
},
mounted() {
this.getToken()
}
}
</script>

Loading…
取消
儲存