Files
Wabenhain/src/app/konto/bestellungen/[id]/retoure/page.tsx
T
Nick SzaunigandClaude Opus 5.5 839bbff595 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]>
2026-09-25 04:36:36 +02:00

80 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { redirect, notFound } from "next/navigation";
import { Container, SectionHeading, Button } from "@/components/ui";
import { BackLink } from "@/components/back-link";
import { getCurrentCustomer } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { requestReturnAction } from "../../actions";
import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
export const dynamic = "force-dynamic";
const RETURN_ELIGIBLE_STATUSES = [
"BEZAHLT",
"IN_VORBEREITUNG",
"VERSANDBEREIT",
"VERSENDET",
"ABHOLBEREIT",
"ABGEHOLT",
];
export default async function RetourePage(props: PageProps<"/konto/bestellungen/[id]/retoure">) {
const t = getDictionary(await getLocale());
const { id } = await props.params;
const customer = await getCurrentCustomer();
if (!customer) redirect("/konto");
const order = await prisma.order.findUnique({
where: { id },
select: {
id: true,
orderNumber: true,
customerId: true,
status: true,
items: { select: { id: true, productName: true, quantity: true } },
returnRequest: { select: { id: true } },
},
});
if (!order || order.customerId !== customer.id) notFound();
if (!RETURN_ELIGIBLE_STATUSES.includes(order.status) || order.returnRequest) {
redirect("/konto/bestellungen");
}
return (
<div className="py-20">
<Container className="max-w-lg">
<BackLink href="/konto/bestellungen" label={t.retourePage.backLabel} />
<SectionHeading eyebrow={t.retourePage.orderEyebrow(order.orderNumber)} title={t.retourePage.title} />
<ul className="mt-8 text-sm text-ink/70 space-y-1">
{order.items.map((item) => (
<li key={item.id}>
{item.quantity} × {item.productName}
</li>
))}
</ul>
<form action={requestReturnAction} className="mt-6 space-y-4">
<input type="hidden" name="orderId" value={order.id} />
<div>
<label htmlFor="reason" className="text-sm text-ink/70">
{t.retourePage.reasonLabel}
</label>
<textarea
id="reason"
name="reason"
required
minLength={3}
rows={5}
placeholder={t.retourePage.reasonPlaceholder}
className="mt-2 w-full border border-ink/20 bg-background px-4 py-3 text-sm focus:outline-none focus:border-forest"
/>
</div>
<Button type="submit">{t.retourePage.submit}</Button>
</form>
</Container>
</div>
);
}