Files
Wabenhain/src/lib/consent.ts
T
Nick SzaunigandClaude Opus 5.5 dcfad4e011 feat(legal): electronic withdrawal function and remaining compliance fixes
Withdrawal function (§ 356a BGB, mandatory since 19 June 2026):
- /vertrag-widerrufen: two-step flow "Vertrag widerrufen" ->
  "Widerruf bestätigen", no login, only name, order number and e-mail
  required. Prominent footer button on every page, prefilled link per
  order in the customer account.
- /api/widerruf stores every declaration (matched to the order only if
  order number and e-mail fit), mails a receipt with the declaration and
  the exact time of receipt, notifies ADMIN_EMAIL. Same response for
  matching and non-matching orders (no enumeration), origin check and
  rate limit instead of a CAPTCHA that could block a statutory right.
  User input is HTML-escaped and stripped of line breaks in headers.
- /admin/widerrufe lists declarations, open ones first, with an
  open-count badge and "mark as done".
- Withdrawal notice gains the statutory sentence on the online function.

Other fixes:
- Newsletter stores confirmedAt as proof of consent (Art. 7(1) GDPR).
- "Recently viewed" is only persisted with consent (§ 25(1) TDDDG);
  choosing "essential only" or withdrawing consent deletes it. The
  banner no longer promises audience-measurement cookies that do not
  exist.
- Admin rejects an invalid company e-mail; an obfuscated stored value
  falls back to the configured address so the legal notice stays valid.
- Privacy policy: Microsoft 365 as mail processor, log retention of at
  most 14 days, withdrawal-function processing, consent timestamps.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-26 11:14:02 +02:00

40 lines
1.2 KiB
TypeScript

/**
* Cookie-/Speicher-Einwilligung (§ 25 Abs. 1 TDDDG). Die Auswahl aus dem
* Banner liegt im localStorage; nur "all" erlaubt einwilligungspflichtige
* Speicherungen. Nur im Browser aufrufen.
*/
export const COOKIE_CONSENT_KEY = "wabenhain-cookie-consent";
/**
* localStorage-Schluessel des "Zuletzt angesehen"-Verlaufs
* (store/recently-viewed-store.ts) — einwilligungspflichtig, weil die
* Funktion fuer den Einkauf nicht unbedingt erforderlich ist.
*/
export const RECENTLY_VIEWED_KEY = "wabenhain-recently-viewed";
export type Consent = "all" | "essential";
export function readConsent(): Consent | null {
try {
const value = window.localStorage.getItem(COOKIE_CONSENT_KEY);
return value === "all" || value === "essential" ? value : null;
} catch {
// Gesperrter Speicher (privater Modus, Richtlinie): wie "keine Einwilligung".
return null;
}
}
export function hasOptionalConsent(): boolean {
return readConsent() === "all";
}
/** Entfernt alle einwilligungspflichtigen Eintraege — bei "Nur notwendige" und beim Widerruf. */
export function clearOptionalStorage(): void {
try {
window.localStorage.removeItem(RECENTLY_VIEWED_KEY);
} catch {
// Nichts zu entfernen, wenn der Speicher nicht zugaenglich ist.
}
}