Files
Crussell/frontend/src/routes/+layout.svelte
T
popertotsandSisyphus 36ad38f670 feat(frontend): add /gdpr page with PDF export and JSON download
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>
2026-06-05 12:13:07 +01:00

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>