'use client'; import { useTranslations } from 'next-intl'; import { useTheme } from 'next-themes'; import { type CSSProperties, useCallback, useEffect, useRef, useState } from 'react'; import { updateDashboardBackgroundAction } from '@/lib/auth-actions'; import { BACKGROUND_PRESETS, type DashboardBackground, NO_BACKGROUND, presetBackground, takeLegacyDashboardBackground, } from '@/lib/dashboard-background'; import { type DashboardImageMeta, dashboardImageSrc, fetchDashboardImages, uploadDashboardImage, } from '@/lib/dashboard-images-api'; import { useAuthStore } from '@/lib/stores/auth-store'; /** Schreibt die Wahl in den Auth-Store, sofern noch derselbe Benutzer angemeldet ist. */ function setStoredBackground(userId: string, value: DashboardBackground | null) { const current = useAuthStore.getState().user; if (!current || current.id !== userId) return; useAuthStore.getState().setUser({ ...current, dashboardBackground: value }); } /** * Wahl des Hintergrunds fuer den angemeldeten Benutzer (quick-260928-ujj). * * Gelesen aus dem Auth-Store (Sitzungsantwort, `null` = nie gewaehlt = * kein Hintergrund). `choose()` setzt den Store sofort und speichert ueber * die Server-Aktion; schlaegt das fehl, kommt der vorige Wert zurueck. * Ist der Server-Wert `null` und liegt noch eine alte localStorage-Wahl vor, * wird sie genau einmal je Benutzerkennung uebernommen. */ export function useDashboardBackground() { const user = useAuthStore((s) => s.user); const userId = user?.id ?? null; const stored = user?.dashboardBackground ?? null; const background = stored ?? NO_BACKGROUND; const migratedFor = useRef(null); const save = useCallback( (id: string, value: DashboardBackground, previous: DashboardBackground | null) => { setStoredBackground(id, value); const revert = () => setStoredBackground(id, previous); updateDashboardBackgroundAction(value) .then((result) => { if (!result.success) revert(); }) .catch(revert); }, [], ); const choose = useCallback( (value: DashboardBackground) => { if (!userId) return; save(userId, value, stored); }, [userId, stored, save], ); useEffect(() => { if (!userId || migratedFor.current === userId) return; migratedFor.current = userId; // Auch bei gesetztem Server-Wert auslesen: der alte Schluessel wird so // aufgeraeumt, uebernommen wird aber nur, wenn noch nie gewaehlt wurde. const legacy = takeLegacyDashboardBackground(userId); if (legacy && stored === null) save(userId, legacy, null); }, [userId, stored, save]); return { background, choose }; } /** CSS fuer eine Wahl; Flaechenfarbe oder Bild, jeweils bildschirmfuellend. */ function backgroundStyle(value: DashboardBackground, dark: boolean): CSSProperties | null { if (value.kind === 'none') return null; if (value.kind === 'image') { return { backgroundImage: `url("${dashboardImageSrc(value.imageId)}")`, backgroundSize: 'cover', backgroundPosition: 'center', }; } const css = presetBackground(value.id, dark); if (!css) return null; return css.startsWith('url(') ? { backgroundImage: css, backgroundSize: 'cover', backgroundPosition: 'center' } : { backgroundColor: css }; } /** * Bildschirmfuellende Hintergrundebene hinter dem Dashboard. Liegt fest * unter Kopfzeile und Seitenleiste (beide haben eigene Flaechen). Im dunklen * Erscheinungsbild werden eigene Bilder abgedunkelt — so bleibt der Text auf * den durchscheinenden Kacheln auch bei einem sehr hellen Foto lesbar. */ export function DashboardBackdrop({ background }: { background: DashboardBackground }) { const { resolvedTheme } = useTheme(); const dark = resolvedTheme === 'dark'; const style = backgroundStyle(background, dark); if (!style) return null; return (