import { useEffect, useState } from 'react'; import { Link, NavLink, useLocation, useNavigate } from 'react-router-dom'; import { ApiError, fetchMyCustomer, fetchProducts, fetchStatusEvents, getSeenAt, loadSession, logout, markNotificationsSeen, sendSampleRequest, type Product, type StatusEvent, } from '../lib/api'; import { STATUS_LABEL } from '../lib/status'; import { useDialogs } from '../lib/dialogs'; import { useWizard } from '../lib/wizard'; import OfferForm from './OfferForm'; import { IconBell, IconCheck, IconChevronDown, IconClose, IconHelp, IconList, IconMail, IconMenu, IconOffer, IconPen, IconPhone, IconUser, } from './Icons'; const KONTAKT = { mail: 'hello@skrift.de', tel: '+49 6872 2153' }; /** Routen, unter denen der Konfigurator läuft (Start + Direktlinks). */ const KONFIGURATOR_PFADE = ['/', '/briefe', '/postkarten', '/unterschriftenservice', '/follow_ups', '/followups', '/follow-ups']; const LAENDER = ['Deutschland', 'Österreich', 'Schweiz', 'Luxemburg', 'Belgien', 'Niederlande', 'Frankreich']; const istEmail = (e: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e.trim()); function Wordmark() { const [logoFehlt, setLogoFehlt] = useState(false); return ( {logoFehlt ? 'Skrift' : Skrift setLogoFehlt(true)} />} ); } /** Konto-Dropdown in der Kopfleiste: Name/Rolle + Abmelden (bzw. Anmelden). */ function UserMenu() { const navigate = useNavigate(); const angemeldet = !!loadSession(); const [offen, setOffen] = useState(false); const [kunde, setKunde] = useState<{ name: string; email: string; rolle: string } | null>(null); useEffect(() => { if (!angemeldet) return; fetchMyCustomer() .then((c) => { if (!c) return; setKunde({ name: [c.first_name, c.last_name].filter(Boolean).join(' ') || c.email, email: c.email, rolle: c.person_type === 'unternehmen' ? 'Unternehmen' : 'Privatkunde', }); }) .catch(() => { /* Anzeige ist unkritisch */ }); }, [angemeldet]); if (!angemeldet) { return ( Anmelden ); } return (
{offen && ( <>
setOffen(false)} />
{kunde?.name}
{kunde?.email}
{kunde?.rolle}
)}
); } /** Schlichtes Overlay für Hilfe und Angebotsanfrage. */ function Dialog({ title, onClose, children }: { title: string; onClose: () => void; children: React.ReactNode }) { return (
e.stopPropagation()}>

{title}

{children}
); } /** Konfigurator samt lokalem Cache zurücksetzen (Login bleibt erhalten). */ function konfiguratorZuruecksetzen() { if (!window.confirm('Konfigurator zurücksetzen? Alle Eingaben und der lokale Zwischenspeicher werden gelöscht (die Anmeldung bleibt bestehen).')) return; try { Object.keys(localStorage) .filter((k) => k.startsWith('skrift.') && k !== 'skrift.session') .forEach((k) => localStorage.removeItem(k)); } catch { /* localStorage nicht verfügbar – dann reicht der Reload */ } window.location.reload(); } function Hilfe({ onClose }: { onClose: () => void }) { return (

Sie erreichen uns direkt – wir melden uns werktags am selben Tag.

{KONTAKT.mail} {KONTAKT.tel}
{/* Rettungsanker bei hängengebliebenem Zustand (z. B. falscher Zeilen-Hinweis). */}

Zeigt der Konfigurator unerwartetes Verhalten (z. B. einen falschen Zeilen-Hinweis)? Setzen Sie ihn hier zurück:

AGB Datenschutz Impressum
); } function Angebot({ onClose, vorbelegung }: { onClose: () => void; vorbelegung?: string }) { // vorbelegung = gewähltes Kontakt-Produkt; wird separat übermittelt, nicht im Text. return ( ); } /** Muster bestellen – kostenlos, mit Produktwahl und Lieferadresse. * variant='page' rendert als eigene Seite (Direktlink /muster) statt als Popup. */ export function Muster({ onClose = () => {}, variant = 'dialog' }: { onClose?: () => void; variant?: 'dialog' | 'page' }) { const [produkte, setProdukte] = useState([]); const [form, setForm] = useState({ product: '', first_name: '', last_name: '', email: '', phone: '', street: '', zip: '', city: '', country: 'Deutschland', message: '', }); const [datenschutz, setDatenschutz] = useState(false); const [status, setStatus] = useState<'formular' | 'sendet' | 'fertig'>('formular'); const [fehler, setFehler] = useState([]); useEffect(() => { fetchProducts() .then((ps) => setProdukte(ps.filter((p) => p.type === 'letter' || p.type === 'postcard'))) .catch(() => setProdukte([])); if (loadSession()) { fetchMyCustomer() .then((c) => c && setForm((f) => ({ ...f, first_name: c.first_name || f.first_name, last_name: c.last_name || f.last_name, email: c.email || f.email, phone: c.phone || f.phone, }))) .catch(() => { /* optional */ }); } }, []); const set = (k: keyof typeof form) => (e: React.ChangeEvent) => { const roh = e.target.value; setForm({ ...form, [k]: k === 'phone' ? roh.replace(/[^0-9+ ]/g, '') : k === 'zip' ? roh.replace(/[^0-9]/g, '') : roh }); }; async function absenden(e: React.FormEvent) { e.preventDefault(); if (!istEmail(form.email)) { setFehler(['Bitte geben Sie eine gültige E-Mail-Adresse an.']); return; } setFehler([]); setStatus('sendet'); try { await sendSampleRequest({ product: form.product, first_name: form.first_name.trim(), last_name: form.last_name.trim(), email: form.email.trim(), phone: form.phone.trim() || undefined, street: form.street.trim(), zip: form.zip.trim(), city: form.city.trim(), country: form.country, message: form.message.trim() || undefined, }); setStatus('fertig'); } catch (err) { setFehler(err instanceof ApiError ? (err.details.length ? err.details : [err.message]) : ['Anfrage fehlgeschlagen.']); setStatus('formular'); } } // Rahmen je nach Modus: Popup (Dialog) oder eigene Seite (sk-view). const wrap = (titel: string, inhalt: React.ReactNode) => variant === 'page' ?

{titel}

{inhalt}
: {inhalt}; if (status === 'fertig') { return wrap('Muster ist unterwegs', ( <>

Vielen Dank – Ihr kostenloses Muster wird vorbereitet und verschickt.

)); } const feld = (k: keyof typeof form, label: string, opt = false, type = 'text') => (
); const gueltig = form.product && form.first_name.trim() && form.last_name.trim() && istEmail(form.email) && form.phone.trim() && form.street.trim() && form.zip.trim() && form.city.trim() && datenschutz; return wrap('Muster bestellen', ( <>

Wir schicken Ihnen kostenlos ein Handschrift-Muster zum Anfassen.

{feld('first_name', 'Vorname')} {feld('last_name', 'Nachname')} {feld('email', 'E-Mail-Adresse', false, 'email')} {feld('phone', 'Telefon', false, 'tel')} {feld('street', 'Straße und Hausnummer')} {feld('zip', 'PLZ')} {feld('city', 'Ort')}