/** * Signature manager — CRUD for mail signatures with rich text editor. * Supports placeholder variables for user/tenant data. */ import { asError } from '@/utils/errorTypes'; import DOMPurify from 'dompurify'; import React, { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Card } from '@/components/ui/Card'; import { EmptyState } from '@/components/ui/EmptyState'; import { useToast } from '@/components/ui/Toast'; import { ConfirmDialog } from '@/components/ui/ConfirmDialog'; import { RichTextEditor } from './RichTextEditor'; import { Loader2 } from 'lucide-react'; import { fetchSignatures, createSignature, updateSignature, deleteSignature, type MailSignature, } from '@/api/mail'; /** Available placeholder variables for signatures. */ const SIGNATURE_VARIABLES = [ { token: '{{user.name}}', label: 'Vollständiger Name', description: 'Max Mustermann' }, { token: '{{user.first_name}}', label: 'Vorname', description: 'Max' }, { token: '{{user.last_name}}', label: 'Nachname', description: 'Mustermann' }, { token: '{{user.email}}', label: 'E-Mail', description: 'max@firma.de' }, { token: '{{user.role}}', label: 'Rolle', description: 'Admin' }, { token: '{{tenant.name}}', label: 'Firma/Organisation', description: 'HMS Licht & Ton' }, { token: '{{date}}', label: 'Aktuelles Datum', description: '16.07.2026' }, ]; export function SignatureManager() { const { t } = useTranslation(); const toast = useToast(); const [signatures, setSignatures] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [editing, setEditing] = useState(null); const [showForm, setShowForm] = useState(false); const [saving, setSaving] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); // ── Signature form (RHF + Zod) ── const sigSchema = z.object({ name: z.string().min(1, 'required'), body_html: z.string().default(''), is_default: z.boolean().default(false), }); type SigFormData = z.infer; const { register: registerSig, handleSubmit: handleSubmitSig, reset: resetSig, watch: watchSig, setValue: setSigValue, formState: { errors: sigErrors } } = useForm({ resolver: zodResolver(sigSchema), defaultValues: { name: '', body_html: '', is_default: false }, }); const bodyHtmlValue = watchSig('body_html'); const load = useCallback(() => { setLoading(true); fetchSignatures() .then((data) => { setSignatures(data); setError(null); }) .catch((err: unknown) => { const errObj = asError(err); setError(errObj?.message || errObj?.detail || (typeof errObj === 'string' ? errObj : 'Failed to load signatures')); }) .finally(() => setLoading(false)); }, []); useEffect(() => { load(); }, [load]); const handleNew = () => { setEditing(null); resetSig({ name: '', body_html: '', is_default: false }); setShowForm(true); }; const handleEdit = (sig: MailSignature) => { setEditing(sig); resetSig({ name: sig.name, body_html: sig.body_html, is_default: sig.is_default }); setShowForm(true); }; const handleSave = useCallback(async (data: SigFormData) => { setSaving(true); try { if (editing) { const updated = await updateSignature(editing.id, { name: data.name, body_html: data.body_html, is_default: data.is_default }); setSignatures((prev) => prev.map((s) => (s.id === editing.id ? updated : s))); toast.success(t('mail.signatureUpdated')); } else { const created = await createSignature({ name: data.name, body_html: data.body_html, is_default: data.is_default }); setSignatures((prev) => [...prev, created]); toast.success(t('mail.signatureCreated')); } setShowForm(false); } catch (err: unknown) { const errObj = asError(err); toast.error(errObj?.message || errObj?.detail || (typeof errObj === 'string' ? errObj : 'Save failed')); } finally { setSaving(false); } }, [editing, toast, t]); const handleDelete = useCallback(async () => { if (!deleteTarget) return; try { await deleteSignature(deleteTarget.id); setSignatures((prev) => prev.filter((s) => s.id !== deleteTarget.id)); toast.success(t('mail.signatureDeleted')); setDeleteTarget(null); } catch (err: unknown) { const errObj = asError(err); toast.error(errObj?.message || errObj?.detail || (typeof errObj === 'string' ? errObj : 'Delete failed')); } }, [deleteTarget, toast, t]); if (loading) { return (
); } if (error) { return (

{error}

); } return (

{t('mail.signatures')}

{showForm && (
{/* Placeholder variable buttons */}
{SIGNATURE_VARIABLES.map((v) => ( ))}
setSigValue('body_html', html)} placeholder="

Mit freundlichen Grüßen,
{{user.name}}

" />
)} {signatures.length === 0 && !showForm ? ( ) : (
{signatures.map((sig) => (

{sig.name}

{sig.is_default && {t('mail.defaultSignature')}}

))}
)} setDeleteTarget(null)} />
); }