import { api, auth } from './pocketbase.svelte' import type { NotificationPrefsResponse } from '$lib/types' /** * Was jemand per Mail erfahren will. * * Kein Context-Store wie die Collection-Stores: Diese Einstellungen gehören * dem Konto und nicht dem aktiven Team, es gibt sie genau einmal je * Anmeldung. Ein Modul-Singleton ist dafür die ehrlichere Form. * * Ein Datensatz entsteht erst beim ersten Speichern. Bis dahin gilt * `DEFAULTS` — dieselbe Linie wie serverseitig in * backend/pb_hooks/notifications_lib.js: An heißt „betrifft mich", aus heißt * „ist viel". Beide Listen müssen zusammenpassen; stünde hier etwas anderes, * zeigte die Oberfläche einen Schalter an, der nichts mit dem zu tun hat, was * tatsächlich verschickt wird. */ export type NotificationType = | 'event_created' | 'event_participation' | 'event_changed' | 'trail_comment' | 'trail_marker' | 'trail_status' | 'team_member' export type NotificationPrefs = Record & { enabled: boolean } export const DEFAULTS: NotificationPrefs = { enabled: true, event_created: true, event_participation: true, event_changed: true, trail_comment: false, trail_marker: false, trail_status: true, team_member: false, } /** Reihenfolge, Beschriftung und Begründung — was die Oberfläche anzeigt. */ export const NOTIFICATION_GROUPS: { title: string items: { key: NotificationType; label: string; hint: string }[] }[] = [ { title: 'Events', items: [ { key: 'event_created', label: 'Neues Event im Team', hint: 'Sobald jemand einen Termin anlegt.', }, { key: 'event_participation', label: 'Du stehst auf der Teilnehmerliste', hint: 'Wenn dich jemand zu einem Event einträgt.', }, { key: 'event_changed', label: 'Termin oder Ort verschoben', hint: 'Nur bei Datum und Ort — nicht bei jeder Textänderung.', }, ], }, { title: 'Trails', items: [ { key: 'trail_status', label: 'Ein Trail wird gesperrt oder wieder frei', hint: 'Danach richtet sich, ob die Ausfahrt am Sonntag stattfindet.', }, { key: 'trail_marker', label: 'Neue Meldung an der Strecke', hint: 'Umgestürzter Baum, kaputte Anlieger, jede neue Markierung.', }, { key: 'trail_comment', label: 'Neuer Kommentar', hint: 'Jeder Kommentar an jedem Trail des Teams. Kann viel werden.', }, ], }, { title: 'Team', items: [ { key: 'team_member', label: 'Jemand ist neu im Kader', hint: 'Bei jedem neu angelegten Fahrer.', }, ], }, ] const COLLECTION = 'notification_prefs' export class NotificationPrefsStore { /** Der Datensatz, sofern es schon einen gibt. */ record = $state(null) values = $state({ ...DEFAULTS }) loading = $state(false) saving = $state(false) error = $state(null) async load() { const uid = auth.user?.id if (!uid) return this.loading = true this.error = null try { const list = await api.collection(COLLECTION).getFullList({ filter: `user = "${uid}"`, requestKey: null, }) const found = (list[0] as NotificationPrefsResponse) ?? null this.record = found // Ohne Datensatz bleiben die Voreinstellungen stehen. Sie sind // keine Behelfsanzeige, sondern genau das, was der Server tut. if (found) { const next = { ...DEFAULTS } for (const key of Object.keys(DEFAULTS) as (keyof NotificationPrefs)[]) { next[key] = !!found[key as keyof NotificationPrefsResponse] } this.values = next } } catch (e: any) { this.error = e.message ?? 'Die Einstellungen konnten nicht geladen werden.' } finally { this.loading = false } } /** * Einen Schalter umlegen und sofort speichern. * * Kein Speichern-Knopf: Es sind Schalter, sie gehören nur dem Kontoinhaber * und jeder für sich ergibt sofort Sinn — genau wie Name und Bild im * Profil. Ein Knopf, der fast immer nichts zu tun hat, ist eine Frage an * jemanden, der längst geantwortet hat. */ async set(key: keyof NotificationPrefs, value: boolean) { const uid = auth.user?.id if (!uid || this.saving) return const previous = this.values[key] this.values = { ...this.values, [key]: value } this.saving = true this.error = null try { if (this.record) { this.record = (await api .collection(COLLECTION) .update(this.record.id, { [key]: value })) as NotificationPrefsResponse } else { // Der erste Griff an die Schalter legt den Datensatz an — mit // allen Werten, nicht nur dem geänderten. Sonst stünden die // übrigen auf `false` statt auf ihrer Voreinstellung, und // wer einen Schalter ausmacht, hätte nebenbei alle anderen // mit ausgemacht. this.record = (await api.collection(COLLECTION).create({ ...this.values, user: uid, })) as NotificationPrefsResponse } } catch (e: any) { this.values = { ...this.values, [key]: previous } this.error = e.message ?? 'Das konnte nicht gespeichert werden.' } finally { this.saving = false } } } export const notificationPrefs = new NotificationPrefsStore()