From 041bed923be26981b7f24328b78663c35192a76c Mon Sep 17 00:00:00 2001 From: Daniel Michelberger Date: Mon, 7 Sep 2026 22:11:44 +0200 Subject: [PATCH] feat: Profilbild als Knopf, und das eigene Konto laesst sich loeschen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Das Bild steht links ueber Name und Adresse und ist zugleich der Knopf: Das Dateifeld liegt unsichtbar darunter, angeklickt wird das Bild. Ein Label "Bild" ueber einem Dateifeld sagte nichts, was das Bild nicht schon zeigt. Dazu eine Vorschau der Wahl — ohne sie bliebe sie unsichtbar, weil es keinen Dateinamen mehr zu lesen gibt. Loeschen des eigenen Kontos erlaubt users.deleteRule laengst (id = @request.auth.id); es fehlte nur der Weg dorthin. Die Fahrer bleiben stehen: riders.user haengt ohne Kaskade daran, also behaelt das Team Namen, Zeiten und Teilnahmen und die Person verliert nur ihr Login. Wer ein Team besitzt, kommt nicht durch. teams.owner ist ein Pflichtfeld ohne Kaskade; PocketBase muesste es beim Loeschen leeren und braeche dabei den Datensatz. Der Versuch endete in einem Fehler, den niemand deuten kann — deshalb steht die Bedingung vorne und im Klartext. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr --- .../routes/dashboard/settings/+page.svelte | 174 +++++++++++++++--- 1 file changed, 152 insertions(+), 22 deletions(-) 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} +
+