feat: sichtbare Rueckmeldung fuer alle Aktionen (Toast statt URL-Meldungen)

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]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-25 04:36:36 +02:00
1 parent fb9c2353c6
commit 839bbff595
71 files changed
+762 -416

No files matched your search

+2 -3
View File
@@ -1,5 +1,6 @@
import { randomBytes } from "node:crypto";
import { NextResponse } from "next/server";
import { withFlash } from "@/lib/flash";
import { prisma } from "@/lib/prisma";
import { getBaseUrl } from "@/lib/url";
import {
@@ -50,9 +51,7 @@ export async function GET(request: Request) {
const baseUrl = getBaseUrl();
function redirectWithError(message: string): NextResponse {
const url = new URL("/konto", baseUrl);
url.searchParams.set("error", message);
return NextResponse.redirect(url);
return withFlash(NextResponse.redirect(new URL("/konto", baseUrl)), "error", message);
}
if (!isEntraConfigured()) {
+6 -3
View File
@@ -1,4 +1,5 @@
import { NextResponse } from "next/server";
import { withFlash } from "@/lib/flash";
import { getBaseUrl } from "@/lib/url";
import { setEntraOAuthCookie } from "@/lib/auth";
import { signEntraOAuthState } from "@/lib/session";
@@ -12,9 +13,11 @@ import { isEntraConfigured, buildEntraAuthorizeUrl, generatePkcePair, generateOA
*/
export async function GET() {
if (!isEntraConfigured()) {
const url = new URL("/konto", getBaseUrl());
url.searchParams.set("error", "SSO ist noch nicht konfiguriert. Bitte melde dich mit E-Mail und Passwort an.");
return NextResponse.redirect(url);
return withFlash(
NextResponse.redirect(new URL("/konto", getBaseUrl())),
"error",
"SSO ist noch nicht konfiguriert. Bitte melde dich mit E-Mail und Passwort an."
);
}
const { codeVerifier, codeChallenge } = generatePkcePair();
+17
View File
@@ -0,0 +1,17 @@
import { NextResponse } from "next/server";
import { withFlash } from "@/lib/flash-core";
import { NOTICES, isNoticeCode } from "@/lib/notice";
import { getBaseUrl } from "@/lib/url";
/**
* Zwischenstation fuer Weiterleitungen aus Server Components (Layouts,
* Seiten): haengt die Flash-Meldung zu einem festen Code an und leitet auf
* dessen festes Ziel weiter (siehe lib/notice.ts).
*/
export async function GET(request: Request) {
const code = new URL(request.url).searchParams.get("code");
const baseUrl = getBaseUrl();
if (!isNoticeCode(code)) return NextResponse.redirect(new URL("/", baseUrl));
const notice = NOTICES[code];
return withFlash(NextResponse.redirect(new URL(notice.to, baseUrl)), "error", await notice.message());
}
+6 -4
View File
@@ -2,12 +2,12 @@ import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { hashVerificationToken } from "@/lib/email-verification";
import { getBaseUrl } from "@/lib/url";
import { withFlash } from "@/lib/flash";
export async function GET(request: Request) {
const token = new URL(request.url).searchParams.get("token");
const baseUrl = getBaseUrl();
const fail = (message: string) =>
NextResponse.redirect(`${baseUrl}/konto?error=${encodeURIComponent(message)}`);
const fail = (message: string) => withFlash(NextResponse.redirect(`${baseUrl}/konto`), "error", message);
if (!token) {
return fail("Dieser Bestätigungslink ist ungültig.");
@@ -34,7 +34,9 @@ export async function GET(request: Request) {
}),
]);
return NextResponse.redirect(
`${baseUrl}/konto?message=${encodeURIComponent("Deine E-Mail-Adresse wurde bestätigt.")}`
return withFlash(
NextResponse.redirect(`${baseUrl}/konto`),
"success",
"Deine E-Mail-Adresse wurde bestätigt. Du kannst jetzt bestellen."
);
}