Files
Website/resources/views/account.blade.php

276 lines
16 KiB
PHP

@php
$user = auth()->user();
$shipping_same_billing = $user->shipping_address === $user->billing_address
&& $user->shipping_address2 === $user->billing_address2
&& $user->shipping_city === $user->billing_city
&& $user->shipping_state === $user->billing_state
&& $user->shipping_postcode === $user->billing_postcode
&& $user->shipping_country === $user->billing_country;
@endphp
<x-layout>
<x-mast>Account Settings</x-mast>
<x-container>
<form method="POST" action="{{ route('account.update') }}" x-data x-on:submit.prevent="SM.updateShippingAddress(); $el.submit()">
@csrf
<h3 class="text-lg font-bold mt-4 mb-3">Contact Information</h3>
<div class="flex flex-col sm:gap-8 sm:flex-row">
<div class="flex-1">
<x-ui.input label="First name" name="firstname" value="{{ $user->firstname }}" />
</div>
<div class="flex-1">
<x-ui.input label="Surname" name="surname" value="{{ $user->surname }}" />
</div>
</div>
<div class="flex flex-col sm:gap-8 sm:flex-row">
<div class="flex-1">
<x-ui.input type="email" label="Email" name="email" value="{{ $user->email }}" info="{{ $user->email_update_pending ? 'Pending request to change to ' . $user->email_update_pending : '' }}"/>
</div>
<div class="flex-1">
<x-ui.input label="Phone" name="phone" value="{{ $user->phone }}" />
</div>
</div>
<section x-data="{ open: true }">
<a href="#" class="flex items-center" @click.prevent="open = !open">
<i :class="{'transform': !open, '-rotate-90': !open, 'translate-y-0.5': true}" class="fa-solid fa-angle-down text-lg transition-transform mr-2"></i>
<h3 class="text-lg font-bold mt-4 mb-3">Email Subscriptions</h3>
</a>
<div x-show="open">
<x-ui.checkbox label="Upcoming Workshops" name="subscribed" checked="{{ $user->subscribed }}" />
</div>
</section>
<section x-data="{ open: false }">
<a href="#" class="flex items-center" @click.prevent="open = !open">
<i :class="{'transform': !open, '-rotate-90': !open, 'translate-y-0.5': true}"
class="fa-solid fa-angle-down text-lg transition-transform mr-2"></i>
<h3 class="text-lg font-bold mt-4 mb-3">Two Factor Authentication</h3>
</a>
<div class="px-4 mb-4" x-show="open">
<div class="flex items-center border border-gray-300 rounded bg-white pl-2 pr-4 py-3 mb-4">
<div class="bg-gray-200 rounded-full w-14 h-14 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-envelope text-2xl"></i>
</div>
<div class="mx-4 flex-grow">
<p class="flex mb-2">
<span class="text-sm font-bold mr-2">Use Email</span>
<span class="text-xs bg-green-500 text-white rounded px-2 py-0.5">Enabled</span>
</p>
<p class="text-xs">Use the security link sent to your email address as your two-factor authentication (2FA). The security link will be sent to the address associated with your account.</p>
</div>
</div>
<div class="border border-gray-300 rounded bg-white pl-2 pr-4 py-3">
<div class="flex items-center">
<div class="bg-gray-200 rounded-full w-14 h-14 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-mobile-screen-button text-2xl"></i>
</div>
<div class="mx-4 flex-grow">
<p class="flex mb-2">
<span class="text-sm font-bold mr-2">Use Authenticator App</span>
<span x-cloak x-show="!$store.tfa.enabled" class="text-xs bg-red-500 text-white rounded px-2 py-0.5">Disabled</span>
<span x-cloak x-show="$store.tfa.enabled" class="text-xs bg-green-500 text-white rounded px-2 py-0.5">Enabled</span>
</p>
<p class="text-xs">Use an Authenticator App as your two-factor authenticator. When you sign in you'll be asked to use the security code provided by your Authenticator App.</p>
</div>
<div class="flex flex-col text-nowrap gap-2">
<x-ui.button x-show="!$store.tfa.enabled" id="tfa_button" type="button" color="primary-outline" x-data x-on:click.prevent="setupTFA()">Setup</x-ui.button>
<x-ui.button x-show="$store.tfa.enabled" type="button" color="danger-outline" x-data x-on:click.prevent="destroyTFA()">Disable</x-ui.button>
<a href="#" x-show="$store.tfa.enabled" x-on:click.prevent="resetBackupCodes($event)" class="text-xs link">Reset Backup Codes</a>
</div>
</div>
<div class="mt-4 pt-4 border-t flex items-center justify-center gap-4" x-cloak x-show="$store.tfa.show && !$store.tfa.loading">
<img src="/loading.gif" id="tfa_image_loader" alt="loading" width="100" height="100"/>
<img src="" id="tfa_image" alt="QR Code" width="150" height="150" style="display:none" onload="this.style.display='block';document.getElementById('tfa_image_loader').style.display='none';"/>
<div>
<p class="text-xs mb-2">Scan the QR Code into your Authenticator App and enter the code provided below</p>
<div class="flex items-center gap-4 justify-center">
<x-ui.input name="code" id="code" class="mb-0" />
<x-ui.button class="mt-1" type="button" color="primary-outline" x-on:click.prevent="linkTFA()">Link</x-ui.button>
</div>
</div>
</div>
<div class="mt-4 pt-4 border-t flex justify-center" x-cloak x-show="$store.tfa.loading">
<img src="/loading.gif" alt="loading" width="100" height="100"/>
</div>
<div class="mt-4 pt-4 border-t flex justify-center" x-cloak x-show="$store.tfa.codes && !$store.tfa.loading">
<div class="w-[34rem] flex items-center gap-4">
<div class="w-[18rem] mx-auto">
<p class="text-sm font-bold mb-1">Save your Backup Codes</p>
<ul class="ml-6 mb-4 text-xs list-disc">
<li>Keep these backup codes safe</li>
<li>You can only use each one once</li>
<li>They will not be shown again</li>
<li>Any existing codes can no longer be used</li>
</ul>
</div>
<div class="w-[16rem] bg-gray-200 p-4 text-sm font-mono flex flex-wrap justify-center">
<template x-for="(code, idx) in $store.tfa.codes" :key="idx">
<p class="mx-4" x-text="code"></p>
</template>
</div>
</div>
</div>
</div>
</div>
</section>
<section x-data="{ open: true }">
<a href="#" class="flex items-center" @click.prevent="open = !open">
<i :class="{'transform': !open, '-rotate-90': !open, 'translate-y-0.5': true}" class="fa-solid fa-angle-down text-lg transition-transform mr-2"></i>
<h3 class="text-lg font-bold mt-4 mb-3">Billing Address</h3>
</a>
<div x-show="open">
<x-ui.input label="Address" name="billing_address" value="{{ $user->billing_address }}" />
<x-ui.input label="Address 2" name="billing_address2" value="{{ $user->billing_address2 }}" />
<x-ui.input label="City" name="billing_city" value="{{ $user->billing_city }}" />
<div class="flex flex-col sm:gap-8 sm:flex-row">
<div class="flex-1">
<x-ui.input label="State" name="billing_state" value="{{ $user->billing_state }}" />
</div>
<div class="flex-1">
<x-ui.input label="Postcode" name="billing_postcode" value="{{ $user->billing_postcode }}" />
</div>
</div>
<x-ui.input label="Country" name="billing_country" value="{{ $user->billing_country }}" />
</div>
</section>
<section x-data="{ open: true }">
<a href="#" class="flex items-center" @click.prevent="open = !open">
<i :class="{'transform': !open, '-rotate-90': !open, 'translate-y-0.5': true}" class="fa-solid fa-angle-down text-lg transition-transform mr-2"></i>
<h3 class="text-lg font-bold mt-4 mb-3">Shipping Address</h3>
</a>
<div x-show="open">
<x-ui.checkbox label="Same as billing address" name="shipping_same_billing" checked="{{ $shipping_same_billing }}" x-data x-on:click="SM.updateShippingAddress" />
<x-ui.input label="Address" name="shipping_address" value="{{ $user->shipping_address }}" readonly="{{ $shipping_same_billing }}" />
<x-ui.input label="Address 2" name="shipping_address2" value="{{ $user->shipping_address2 }}" readonly="{{ $shipping_same_billing }}" />
<x-ui.input label="City" name="shipping_city" value="{{ $user->shipping_city }}" readonly="{{ $shipping_same_billing }}" />
<div class="flex flex-col sm:gap-8 sm:flex-row">
<div class="flex-1">
<x-ui.input label="State" name="shipping_state" value="{{ $user->shipping_state }}" readonly="{{ $shipping_same_billing }}" />
</div>
<div class="flex-1">
<x-ui.input label="Postcode" name="shipping_postcode" value="{{ $user->shipping_postcode }}" readonly="{{ $shipping_same_billing }}" />
</div>
</div>
<x-ui.input label="Country" name="shipping_country" value="{{ $user->shipping_country }}" readonly="{{ $shipping_same_billing }}" />
</div>
</section>
<div class="flex justify-between mt-8">
@if($user->id !== 1)
<x-ui.button type="button" color="danger" x-data x-on:click.prevent="SM.confirmDelete('{{ csrf_token() }}', 'Delete account?', 'Are you sure you want to delete your account? This action cannot be undone.<br /><br />Any workshop tickets will remain valid.', '{{ route('account.destroy') }}')">Delete</x-ui.button>
@else
<div></div>
@endif
<x-ui.button type="submit">Save</x-ui.button>
</div>
</form>
</x-container>
</x-layout>
{{ $codes ?? '' }}
<script>
document.addEventListener('alpine:init', () => {
Alpine.store('tfa', {
show: false,
secret: null,
enabled: {{ $user->tfa_secret !== null ? 'true' : 'false'}},
codes: null,
loading: false
});
});
function setupTFA() {
document.getElementById('tfa_button').disabled = true;
axios.get('/account/2fa')
.then(response => {
if(response.data.secret) {
Alpine.store('tfa').show = true;
Alpine.store('tfa').secret = response.data.secret;
document.getElementById('tfa_image').src = '/account/2fa/image?secret=' + response.data.secret;
} else {
SM.alert('2FA Error', 'An error occurred while setting up two-factor authentication. Please try again later', 'danger');
}
})
.catch(() => {
SM.alert('2FA Error', 'An error occurred while setting up two-factor authentication. Please try again later', 'danger');
});
}
function linkTFA() {
Alpine.store('tfa').loading = true;
axios.post('/account/2fa', {
code: document.getElementById('code').value,
secret: Alpine.store('tfa').secret,
})
.then(response => {
console.log(response.data);
if(response.data.success) {
SM.alert('2FA Linked', 'Two-factor authentication has been successfully linked to your account', 'success');
document.getElementById('tfa_button').disabled = false;
document.getElementById('code').value = '';
document.getElementById('tfa_image').src = '';
Alpine.store('tfa').show = false;
Alpine.store('tfa').enabled = true;
Alpine.store('tfa').codes = response.data.codes;
} else {
SM.alert('2FA Error', 'An error occurred while linking two-factor authentication. Please try again later', 'danger');
}
})
.catch(() => {
SM.alert('2FA Error', 'An error occurred while linking two-factor authentication. Please try again later', 'danger');
})
.finally(() => {
Alpine.store('tfa').loading = false;
});
}
function resetBackupCodes(event) {
event.target.classList.add('disabled');
Alpine.store('tfa').codes = null;
Alpine.store('tfa').loading = true;
axios.post('/account/2fa/reset-backup-codes')
.then(response => {
if(response.data.success) {
Alpine.store('tfa').codes = response.data.codes;
} else {
SM.alert('2FA Error', 'An error occurred while resetting your backup codes. Please try again later', 'danger');
}
})
.catch(() => {
SM.alert('2FA Error', 'An error occurred while resetting your backup codes. Please try again later', 'danger');
})
.finally(() => {
event.target.classList.remove('disabled');
Alpine.store('tfa').loading = false;
});
}
function destroyTFA() {
SM.confirm('Disable 2FA', 'Are you sure you want to remove two-factor authentication from your account?', 'Disable', (confirm) => {
if(confirm) {
axios.delete('/account/2fa')
.then(response => {
if (response.data.success) {
SM.alert('2FA Disabled', 'Two-factor authentication has been successfully disabled on your account', 'success');
Alpine.store('tfa').enabled = false;
Alpine.store('tfa').codes = null;
} else {
SM.alert('2FA Error', 'An error occurred while disabling two-factor authentication. Please try again later', 'danger');
}
})
.catch(() => {
SM.alert('2FA Error', 'An error occurred while disabling two-factor authentication. Please try again later', 'danger');
});
}
}, {
confirmButtonText: 'Disable'
});
}
</script>