Viele Aktionen gaben keine Rueckmeldung (Retoure annehmen, Archiv, Bewertungen, Adressen, Newsletter, Kundensperre ...) oder meldeten Erfolg, obwohl die zugehoerige E-Mail nie rausging. - lib/flash: Meldung als kurzlebiges Cookie; Toast im Root-Layout (FlashToast). Ersetzt ?message=/?error= in der URL — darueber liess sich bisher beliebiger Text als Systemmeldung im Wabenhain-Layout anzeigen (Phishing-Hebel). Server Components leiten ueber /api/hinweis mit festen Codes und festen Zielen weiter. - fail()/succeed() zentral und asynchron; ein Quelltext-Test stellt sicher, dass jeder Aufruf mit await/return erfolgt (sonst liefe eine Action nach einem Fehler weiter). - sendMail meldet, ob die Mail angenommen wurde; Aktionen mit Mail zeigen ehrlich an, wenn der Versand scheitert (z.B. "Link erneut senden"). - Jede Server Action meldet jetzt Erfolg oder den Grund des Abbruchs. - SubmitFeedback: geklickter Knopf zeigt sofort einen Ladezustand und ist bis zur Antwort gegen Doppelklick gesperrt. - 2FA: nach dem Einrichten zurueck ins Profil statt auf die Seite mit "Deaktivieren". Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
62 lines
2.0 KiB
TypeScript
62 lines
2.0 KiB
TypeScript
/**
|
|
* Reine Hilfsfunktionen der Flash-Meldungen (siehe lib/flash.ts) ohne
|
|
* next/headers — damit auch der Proxy (src/proxy.ts) und Route Handler sie
|
|
* nutzen koennen.
|
|
*/
|
|
export const FLASH_COOKIE = "wh_flash";
|
|
export const MAX_FLASH_MESSAGE_LENGTH = 300;
|
|
/** Ohne JavaScript loescht niemand das Cookie — dann verfaellt es von selbst. */
|
|
const FLASH_MAX_AGE_SECONDS = 60;
|
|
|
|
const FLASH_KINDS = ["success", "error", "info"] as const;
|
|
export type FlashKind = (typeof FLASH_KINDS)[number];
|
|
|
|
export interface Flash {
|
|
id: string;
|
|
kind: FlashKind;
|
|
message: string;
|
|
}
|
|
|
|
export function serializeFlash(flash: Flash): string {
|
|
return encodeURIComponent(JSON.stringify({ ...flash, message: flash.message.slice(0, MAX_FLASH_MESSAGE_LENGTH) }));
|
|
}
|
|
|
|
export function parseFlash(raw: string | undefined): Flash | null {
|
|
if (!raw) return null;
|
|
let value: unknown;
|
|
try {
|
|
value = JSON.parse(decodeURIComponent(raw));
|
|
} catch {
|
|
return null;
|
|
}
|
|
if (typeof value !== "object" || value === null) return null;
|
|
const { id, kind, message } = value as Record<string, unknown>;
|
|
if (typeof id !== "string" || typeof message !== "string" || message.trim() === "") return null;
|
|
if (!(FLASH_KINDS as readonly unknown[]).includes(kind)) return null;
|
|
return { id: id.slice(0, 64), kind: kind as FlashKind, message: message.slice(0, MAX_FLASH_MESSAGE_LENGTH) };
|
|
}
|
|
|
|
export function flashCookieOptions() {
|
|
return {
|
|
path: "/",
|
|
maxAge: FLASH_MAX_AGE_SECONDS,
|
|
sameSite: "lax" as const,
|
|
secure: process.env.NODE_ENV === "production",
|
|
httpOnly: false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Fuer Route Handler, die selbst eine NextResponse zurueckgeben (z.B. der
|
|
* Link aus der Bestaetigungs-E-Mail): haengt die Meldung direkt an die
|
|
* Weiterleitung.
|
|
*/
|
|
export function withFlash<T extends { cookies: { set: (name: string, value: string, options: object) => unknown } }>(
|
|
response: T,
|
|
kind: FlashKind,
|
|
message: string
|
|
): T {
|
|
response.cookies.set(FLASH_COOKIE, serializeFlash({ id: crypto.randomUUID(), kind, message }), flashCookieOptions());
|
|
return response;
|
|
}
|