feat(ui): v0.16 features + dark mode across every page
Wires up everything from the previous commits into actual UI surfaces, and applies Tailwind dark: variants throughout. All pages now support the 'system' / 'light' / 'dark' preference set in the onboarding step or in Mein Konto → Design. Layout & nav: - routes/+layout.svelte: initTheme(), global pin-reset SSE handler that filters by user_id and calls clearPin(), one-shot /me/context fetch on boot to hydrate privacyNote + quota. - components/BottomNav.svelte: dark variants on the frosted-glass bar. - components/UploadSheet.svelte: dark variants on backdrop, sheet, source buttons. - components/OnboardingGuide.svelte: new "Helles oder dunkles Design?" step (3-option custom-radio grid), reactive currentStep with proper type narrowing, dark variants throughout. Privacy-note nudge appears on the PIN step only when one is configured. Feed: - routes/feed/+page.svelte: diashow entry icon (tablet/desktop only), long-press → ContextSheet (Löschen for own posts, Original anzeigen for all), upload-deleted + feed-delta SSE handlers, dark variants on header, search, autocomplete, filter chips, empty states. - components/FeedListCard.svelte: long-press wireup, double-tap-to-like, data-mode-aware mediaSrc via pickMediaUrl, kebab fallback for desktop, isOwn prop, dark variants. - components/FeedGrid.svelte: long-press wireup, dark variants. - components/LightboxModal.svelte: data-mode-aware src, double-tap heart burst, dark variants on card / comments / input. - components/HashtagChips.svelte: dark variants. Account: - routes/account/+page.svelte: theme picker (3-button radio grid), data mode picker (with confirm sheet for Original), live quota widget, preformatted Datenschutzhinweis block, diashow tile (mobile only), pin now sourced from the $currentPin store so a global pin-reset clears it live, clearQueue() on explicit logout, dark variants across every card + both bottom sheets. Upload: - routes/upload/+page.svelte: per-user quota progress bar above the submit button, dark variants. Host & Admin: - routes/host/+page.svelte: PIN-reset confirm + one-time PIN modal, hosts may demote other hosts, canResetPinFor() helper, dark variants on all cards, modals, stats, toast. - routes/admin/+page.svelte: Config form rebuilt as CONFIG_GROUPS with per-field kind (number / bool / text), renders toggles for the rate-limit + quota switches and a textarea for the privacy_note; Nutzer tab gains PIN reset + hosts-may-demote-hosts wiring; same one-time PIN modal; dark variants everywhere. - routes/admin/login/+page.svelte: dark variants. Join / Recover / Export: - routes/join/+page.svelte: rename inline link to "Ich habe bereits einen Account", dark variants. - routes/recover/+page.svelte: dark variants. - routes/export/+page.svelte: dark variants on status cards + HTML guide modal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,24 @@
|
||||
<script lang="ts">
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
import '../app.css';
|
||||
import { initAuth } from '$lib/auth';
|
||||
import { onMount } from 'svelte';
|
||||
import { initAuth, getToken, getUserId, clearPin } from '$lib/auth';
|
||||
import { initTheme } from '$lib/theme-store';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import BottomNav from '$lib/components/BottomNav.svelte';
|
||||
import UploadSheet from '$lib/components/UploadSheet.svelte';
|
||||
import { showBottomNav } from '$lib/ui-store';
|
||||
import { isAuthenticated } from '$lib/auth';
|
||||
import { queueItems, isProcessing } from '$lib/upload-queue';
|
||||
import { privacyNote } from '$lib/privacy-note-store';
|
||||
import { refreshQuota } from '$lib/quota-store';
|
||||
import { onSseEvent } from '$lib/sse';
|
||||
import { api } from '$lib/api';
|
||||
import type { MeContextDto } from '$lib/types';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
let unsubs: Array<() => void> = [];
|
||||
|
||||
// Slim progress bar: ratio of completed items to total, shown while processing.
|
||||
let progressPct = $derived.by(() => {
|
||||
const total = $queueItems.length;
|
||||
@@ -19,8 +27,41 @@
|
||||
return Math.round((done / total) * 100);
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
onMount(async () => {
|
||||
initAuth();
|
||||
// Hooks up the appliedTheme → <html class="dark"> sync. Must run early so the
|
||||
// first paint after hydration matches the saved preference.
|
||||
initTheme();
|
||||
// Hydrate cross-cutting stores once on boot if the user is already authenticated.
|
||||
// Page-level mounts will refresh again as needed.
|
||||
if (getToken()) {
|
||||
try {
|
||||
const ctx = await api.get<MeContextDto>('/me/context');
|
||||
privacyNote.set(ctx.privacy_note);
|
||||
} catch {
|
||||
// non-fatal; users without a session land on /join anyway
|
||||
}
|
||||
void refreshQuota();
|
||||
}
|
||||
|
||||
// Global pin-reset listener — clears the now-invalid plaintext PIN from
|
||||
// localStorage no matter which route the user is currently on. The reactive
|
||||
// `currentPin` store carries the change into any page that reads it (My
|
||||
// Account in particular).
|
||||
unsubs.push(
|
||||
onSseEvent('pin-reset', (data) => {
|
||||
try {
|
||||
const payload = JSON.parse(data) as { user_id: string };
|
||||
if (payload.user_id === getUserId()) clearPin();
|
||||
} catch {
|
||||
// ignore malformed payload
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
for (const unsub of unsubs) unsub();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user