New /gdpr route: skeleton loading state, 2s polling for async export, styled report cards/tables matching /schedule and /account styling. 16 data sections with conditional rendering (empty sections hidden). VAT breakdowns hidden until any payment has non-zero VAT. Deposits Required hidden when 0. PDF export via window.print() with print CSS hiding navbar and verification banner. Raw JSON download button. Export My Data button added to Account page under Change Password. Vite worker format set to 'es' for WASM encoder compatibility. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
85 lines
2.2 KiB
Svelte
85 lines
2.2 KiB
Svelte
<script lang="ts">
|
|
import '../app.css';
|
|
import favicon from '$lib/assets/favicon.svg';
|
|
import NavBar from '$lib/components/layout/NavBar.svelte';
|
|
import { Toaster } from '$lib/components/ui/sonner/index.js';
|
|
import { toast } from 'svelte-sonner';
|
|
import { authStore } from '$lib/stores/auth.svelte';
|
|
import { onMount } from 'svelte';
|
|
|
|
let { children } = $props();
|
|
|
|
// Default to 'top-center' (mobile-first approach)
|
|
let toasterPosition = $state<'top-center' | 'bottom-center'>('top-center');
|
|
|
|
function updateToasterPosition() {
|
|
// Use 640px (Tailwind's 'sm' breakpoint) to differentiate desktop and mobile
|
|
if (typeof window !== 'undefined' && window.innerWidth >= 768) {
|
|
toasterPosition = 'bottom-center';
|
|
} else {
|
|
toasterPosition = 'top-center';
|
|
}
|
|
}
|
|
|
|
onMount(() => {
|
|
updateToasterPosition();
|
|
window.addEventListener('resize', updateToasterPosition);
|
|
return () => {
|
|
window.removeEventListener('resize', updateToasterPosition);
|
|
};
|
|
});
|
|
|
|
// Verify email address
|
|
async function verify_email() {
|
|
const loadingToast = toast.loading('Verifying email address...');
|
|
const response = await fetch('/api/verify-email', {
|
|
method: 'POST'
|
|
});
|
|
|
|
if (response.ok) {
|
|
toast.success('Email verified successfully!', { id: loadingToast });
|
|
window.location.reload();
|
|
} else {
|
|
toast.error('Failed to verify email address', { id: loadingToast });
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<link rel="icon" href={favicon} />
|
|
</svelte:head>
|
|
|
|
<NavBar />
|
|
<Toaster position={toasterPosition} />
|
|
|
|
<main class="pt-16">
|
|
{#if authStore.currentUser?.role === 'unverified_email'}
|
|
<div class="w-full bg-red-600 py-2 pr-8 pl-8 text-center text-sm font-medium text-white">
|
|
Please verify your email address to continue. Didn't recieve the email? Check your spam
|
|
folder, or <button
|
|
type="button"
|
|
onclick={verify_email}
|
|
class="cursor-pointer border-0 bg-transparent p-0 text-white underline"
|
|
>
|
|
click here
|
|
</button>
|
|
to resend it.
|
|
</div>
|
|
{/if}
|
|
{@render children?.()}
|
|
</main>
|
|
|
|
<style>
|
|
@media print {
|
|
:global(nav) {
|
|
display: none !important;
|
|
}
|
|
:global(main > div.bg-red-600) {
|
|
display: none !important;
|
|
}
|
|
:global(main) {
|
|
padding-top: 0 !important;
|
|
}
|
|
}
|
|
</style>
|