diff --git a/frontend/src/routes/dashboard/settings/+page.svelte b/frontend/src/routes/dashboard/settings/+page.svelte index c0c7595..876b89b 100644 --- a/frontend/src/routes/dashboard/settings/+page.svelte +++ b/frontend/src/routes/dashboard/settings/+page.svelte @@ -5,16 +5,40 @@ * also niemand für jemand anderen ändern. */ import { api, auth } from '$lib/stores/pocketbase.svelte' + import { app } from '$lib/stores/app.svelte' + import { getTeamContext } from '$lib/stores/teams.svelte' + import { goto } from '$app/navigation' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import Avatar from '$lib/components/Avatar.svelte' import { Badge } from '@/components/ui/badge' - import { KeyRound, User } from 'lucide-svelte' + import { Camera, KeyRound, Trash2, User } from 'lucide-svelte' let name = $state(auth.user?.name ?? '') let avatarFile = $state(null) + + /** + * Vorschau des gewaehlten Bildes. Ohne sie bliebe die Wahl unsichtbar — + * das Dateifeld ist versteckt, es gibt also keinen Dateinamen zu lesen. + * Die Object-URL wird beim naechsten Wechsel wieder freigegeben, sonst + * haelt der Browser jedes einmal angesehene Bild bis zum Seitenwechsel + * fest. + */ + let avatarPreview = $state(null) + + $effect(() => { + const file = avatarFile + if (!file) { + avatarPreview = null + return + } + + const url = URL.createObjectURL(file) + avatarPreview = url + return () => URL.revokeObjectURL(url) + }) let savingProfile = $state(false) let profileMessage = $state(null) let profileError = $state(null) @@ -50,6 +74,56 @@ } } + // --- Eigenes Konto löschen ------------------------------------------- + // + // `users.deleteRule = id = @request.auth.id` erlaubt genau das und sonst + // nichts: Niemand löscht ein fremdes Konto, auch die Teamleitung nicht. + // + // Was dabei bleibt: die Fahrer, die an diesem Konto hingen. Deren + // Verknüpfung ist ohne Kaskade angelegt (riders.user, cascadeDelete + // false) — der Fahrer bleibt also mit Namen, Zeiten und Teilnahmen im + // Kader stehen und hat nur kein Login mehr. Das ist die Absicht: + // Ergebnisse gehören dem Team, nicht dem Konto. + // + // Wer ein Team besitzt, kommt hier nicht durch. `teams.owner` ist ein + // Pflichtfeld ohne Kaskade; PocketBase müsste es beim Löschen leeren und + // bräche dabei den Datensatz. Der Versuch endet in einem Fehler, den + // niemand deuten kann — deshalb steht die Bedingung hier vorne und im + // Klartext. + const teams = getTeamContext() + const ownedTeams = $derived(teams.records.filter((t) => t.owner === auth.user?.id)) + let deletingAccount = $state(false) + let deleteError = $state(null) + + function deleteAccount() { + if (!auth.user || ownedTeams.length > 0) return + + app.confirm.request({ + title: 'Konto löschen?', + text: + 'Dein Konto wird endgültig gelöscht und du wirst abgemeldet. ' + + 'Dein Fahrer bleibt mit allen Zeiten und Teilnahmen im Kader stehen — ' + + 'nur anmelden kannst du dich nicht mehr. Über einen neuen Einladungslink ' + + 'kommst du zurück.', + yes: async () => { + deletingAccount = true + deleteError = null + try { + await api.collection('users').delete(auth.user!.id) + app.confirm.close() + await auth.logout() + goto('/login', { replaceState: true }) + } catch (e: any) { + app.confirm.close() + deleteError = e.message ?? 'Das Konto konnte nicht gelöscht werden.' + } finally { + deletingAccount = false + } + }, + no: () => app.confirm.close(), + }) + } + let oldPassword = $state('') let newPassword = $state('') let confirmPassword = $state('') @@ -109,36 +183,53 @@ -
- -
- + +
+ + +
+ +
-
- - -
- -
- - -

- Die E-Mail-Adresse ist der Anmeldename. Ändern kann sie nur die - Team-Administration über PocketBase. -

-
+

+ Die E-Mail-Adresse ist der Anmeldename. Ändern kann sie nur die + Team-Administration über PocketBase. +

{#if profileError}

{profileError}

@@ -187,4 +278,43 @@ + + + + + + + Konto löschen + + + + {#if ownedTeams.length > 0} +

+ Du bist Owner von + {ownedTeams.map((t) => `„${t.name}"`).join(', ')}. Ein Team braucht + seinen Owner: Übergib es oder lösche es zuerst, danach lässt sich + dein Konto löschen. +

+ + {:else} +

+ Dein Konto wird endgültig gelöscht und du wirst abgemeldet. Dein + Fahrer bleibt mit allen Zeiten und Teilnahmen im Kader stehen — nur + anmelden kannst du dich nicht mehr. +

+ + {#if deleteError} +

{deleteError}

+ {/if} + + + {/if} +
+