Files
craftvia/src/components/notifications/preferences-form.tsx
T
msolarczekandClaude Opus 5 879012415e L6 Benachrichtigungen & Audit: Glocke, Posteingang, Nutzer-Einstellungen, Mailkonfiguration
- Glocke im Backoffice-Header (ungelesen-Zähler, letzte 10, alle gelesen), mobil
  einbindbar über variant="mobile".
- /notifications mit Filter gelesen/ungelesen/Art, Öffnen markiert gelesen (nur
  relative Links), Pagination.
- /account Abschnitt Benachrichtigungen: E-Mail-Opt-out je Typ, Notdienst Pflicht.
- /settings/email (tenant:manage): Absendername, Antwortadresse, Empfänger Notdienst
  und Abrechnung; Validierung gegen Header-Injection, max. 20 Adressen, Audit.
- Actions unter actions/notifications mit moduleGuard + guard; Navigation ergänzt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 12:12:21 +02:00

58 lines
2.2 KiB
TypeScript

"use client";
import { useActionState } from "react";
import { Button } from "@/components/ui/button";
import { saveNotificationPreferences, type PreferencesState } from "@/server/actions/notifications/preferences";
export type PreferenceItem = { type: string; label: string; email: boolean; mandatory: boolean };
/** E-mail opt-out per notification type (in-app notifications are always on). */
export function NotificationPreferencesForm({
items,
labels,
}: {
items: PreferenceItem[];
labels: { email: string; mandatory: string; save: string; saved: string; error: string };
}) {
const [state, action, pending] = useActionState(saveNotificationPreferences, { status: "idle" } as PreferencesState);
return (
<form action={action} className="space-y-3">
<ul className="divide-y rounded-lg border">
{items.map((item) => {
const id = `pref-${item.type}`;
return (
<li key={item.type} className="flex min-h-11 items-center justify-between gap-3 px-3 py-1.5">
<label htmlFor={id} className="flex-1 text-[13px]">
{item.label}
{item.mandatory && <span className="block text-[11.5px] text-muted-foreground">{item.mandatory ? labels.mandatory : null}</span>}
</label>
<span className="flex items-center gap-2 text-[12px] text-muted-foreground">
{labels.email}
<input
id={id}
type="checkbox"
name="emailOn"
value={item.type}
defaultChecked={item.email}
disabled={item.mandatory}
className="size-5 accent-[var(--primary)]"
/>
</span>
</li>
);
})}
</ul>
<div className="flex items-center gap-3">
<Button type="submit" disabled={pending} className="min-h-11">
{labels.save}
</Button>
<p role="status" className="text-[12.5px]">
{state.status === "saved" && <span className="text-[var(--ok)]">✓ {labels.saved}</span>}
{state.status === "error" && <span className="text-[var(--risk)]">⚠ {labels.error}</span>}
</p>
</div>
</form>
);
}