"use client"; import { useEffect, useRef, useState } from "react"; import { signOut, useSession } from "next-auth/react"; export default function SettingsPage() { const { data } = useSession(); const [viewToken, setViewToken] = useState(null); const [viewId, setViewId] = useState(null); const [subscribedCategories, setSubscribedCategories] = useState>(new Set()); const [allCategories, setAllCategories] = useState<{ id: string; name: string }[]>([]); const [categoryError, setCategoryError] = useState(null); const [categoryStatus, setCategoryStatus] = useState(null); const [error, setError] = useState(null); const [status, setStatus] = useState(null); const [profileError, setProfileError] = useState(null); const [profileStatus, setProfileStatus] = useState(null); const [theme, setTheme] = useState<"light" | "dark">("light"); const [copyStatus, setCopyStatus] = useState<"success" | "error" | null>(null); const [appName, setAppName] = useState("Vereinskalender"); const [icalPastDays, setIcalPastDays] = useState(14); const icalReadyRef = useRef(false); const loadView = async () => { try { const response = await fetch("/api/views/default"); if (!response.ok) return; const payload = await response.json(); setViewToken(payload.token); setViewId(payload.id); setIcalPastDays( typeof payload.icalPastDays === "number" ? payload.icalPastDays : 14 ); icalReadyRef.current = true; const ids = new Set( (payload.categories || []).map((item: { categoryId: string }) => item.categoryId) ); setSubscribedCategories(ids); } catch { // ignore } }; const loadCategories = async () => { try { const response = await fetch("/api/categories"); if (!response.ok) return; setAllCategories(await response.json()); } catch { // ignore } }; const loadAppName = async () => { try { const nameResponse = await fetch("/api/settings/app-name"); if (nameResponse.ok) { const payload = await nameResponse.json(); setAppName(payload.name || "Vereinskalender"); } } catch { // ignore } }; useEffect(() => { if (data?.user) { loadView(); loadCategories(); loadAppName(); } }, [data?.user]); useEffect(() => { if (typeof window === "undefined") return; const saved = window.localStorage.getItem("theme"); const next = saved === "dark" ? "dark" : "light"; setTheme(next); document.documentElement.dataset.theme = next; }, []); const rotateToken = async () => { setError(null); setStatus(null); setCopyStatus(null); const response = await fetch("/api/views/default/rotate", { method: "POST" }); if (!response.ok) { const payload = await response.json(); setError(payload.error || "Token konnte nicht erneuert werden."); return; } const payload = await response.json(); setViewToken(payload.token); setStatus("Neuer iCal-Link erstellt."); }; const updateProfile = async (event: React.FormEvent) => { event.preventDefault(); setProfileError(null); setProfileStatus(null); const formData = new FormData(event.currentTarget); const payload = { currentPassword: formData.get("currentPassword"), newEmail: formData.get("newEmail"), newPassword: formData.get("newPassword") }; const response = await fetch("/api/profile", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); if (!response.ok) { const data = await response.json(); setProfileError(data.error || "Profil konnte nicht aktualisiert werden."); return; } const dataRes = await response.json(); setProfileStatus("Profil aktualisiert."); if (dataRes.changedEmail || dataRes.changedPassword) { setProfileStatus("Profil aktualisiert. Bitte erneut anmelden."); await signOut({ callbackUrl: "/login" }); } }; const baseUrl = typeof window === "undefined" ? "" : window.location.origin; const toFilename = (value: string) => value .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/(^-|-$)/g, "") || "kalender"; const icalQuery = icalPastDays > 0 ? `?pastDays=${icalPastDays}` : ""; const icalUrl = viewToken ? `${baseUrl}/api/ical/${viewToken}/${toFilename(appName)}.ical${icalQuery}` : ""; const updateIcalPastDays = async (value: number) => { setError(null); setStatus(null); const response = await fetch("/api/views/default", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ icalPastDays: value }) }); if (!response.ok) { const data = await response.json(); setError(data.error || "Einstellung konnte nicht gespeichert werden."); return; } setStatus("iCal-Einstellung gespeichert."); window.setTimeout(() => setStatus(null), 2500); }; useEffect(() => { if (!icalReadyRef.current || !viewId) return; updateIcalPastDays(icalPastDays); }, [icalPastDays, viewId]); const applyTheme = (next: "light" | "dark") => { setTheme(next); if (typeof window !== "undefined") { window.localStorage.setItem("theme", next); document.documentElement.dataset.theme = next; } }; const copyIcalUrl = async () => { if (!icalUrl) return; setCopyStatus(null); try { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(icalUrl); } else { const textarea = document.createElement("textarea"); textarea.value = icalUrl; textarea.setAttribute("readonly", "true"); textarea.style.position = "absolute"; textarea.style.left = "-9999px"; document.body.appendChild(textarea); textarea.select(); document.execCommand("copy"); document.body.removeChild(textarea); } setCopyStatus("success"); } catch { setCopyStatus("error"); } window.setTimeout(() => setCopyStatus(null), 2500); }; const toggleCategory = async (categoryId: string) => { if (!viewId) return; setCategoryError(null); setCategoryStatus(null); const isSubscribed = subscribedCategories.has(categoryId); const response = await fetch(`/api/views/${viewId}/categories`, { method: isSubscribed ? "DELETE" : "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ categoryId }) }); if (!response.ok) { const payload = await response.json(); setCategoryError(payload.error || "Kategorien konnten nicht aktualisiert werden."); return; } const next = new Set(subscribedCategories); if (isSubscribed) { next.delete(categoryId); setCategoryStatus("Kategorie entfernt."); } else { next.add(categoryId); setCategoryStatus("Kategorie abonniert."); } setSubscribedCategories(next); }; return (

Profil

Einstellungen

{profileStatus && (

{profileStatus}

)} {profileError &&

{profileError}

}

Darstellung

Theme

iCal

Persönlicher Kalenderlink

Dein Link kann in externen Kalender-Apps abonniert werden.

{viewToken ? (
iCal {icalUrl}
{copyStatus && (
{copyStatus === "success" ? "Kopiert" : "Fehler"}
)}
) : (

iCal-Link wird geladen...

)}
{status && (
{status}
)} {error &&

{error}

}

Kategorien

Kategorien abonnieren

{allCategories.length === 0 ? (

Noch keine Kategorien vorhanden.

) : (
{allCategories.map((category) => { const isSubscribed = subscribedCategories.has(category.id); return ( ); })}
)} {categoryStatus && (

{categoryStatus}

)} {categoryError &&

{categoryError}

}
); }