- Konto-Kacheln: Icon in einer Wabe, die sich beim Hover mit Honig fuellt, einlaufender Pfeil, Serifen-Titel - Bestell-Statusleiste: Waben statt Punkte, erledigte Abschnitte fliessen nacheinander voll, aktueller Schritt pulsiert sanft - Status-Badge mit pulsierendem Punkt bei laufenden Bestellungen - Leere Zustaende mit gestrichelter Wabe und Serifen-Titel - Formularfelder: einheitlicher Honig-Schein beim Fokus - Groessere Abschnittstitel im Konto, Bestellsummen in Serifen-Preisschrift - Nur CSS-Animationen (Komponenten rendern auch auf Admin-Serverseiten); reduzierte Bewegung schaltet Puls und Fuellen ab Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
177 lines
7.6 KiB
TypeScript
177 lines
7.6 KiB
TypeScript
import type { Metadata } from "next";
|
||
import { redirect } from "next/navigation";
|
||
import Link from "next/link";
|
||
import { Container, SectionHeading, LinkButton } from "@/components/ui";
|
||
import { BackLink } from "@/components/back-link";
|
||
import { ReorderButton } from "@/components/reorder-button";
|
||
import { AccountEmptyState, OrderStatusBadge, OrderStatusTimeline, OrderIcon } from "@/components/account-ui";
|
||
import { getCurrentCustomer } from "@/lib/auth";
|
||
import { getOrdersForCustomer } from "@/lib/account-data";
|
||
import { formatPrice, formatDate } from "@/lib/format";
|
||
import { getCarrierTrackingUrl } from "@/lib/shipment-tracking";
|
||
import { getLocale } from "@/i18n/locale";
|
||
import { getDictionary } from "@/i18n/get-dictionary";
|
||
|
||
export const metadata: Metadata = { title: "Meine Bestellungen" };
|
||
export const dynamic = "force-dynamic";
|
||
|
||
// Deckt sich mit RETURN_ELIGIBLE_STATUSES in bestellungen/actions.ts.
|
||
const RETURN_ELIGIBLE_STATUSES = [
|
||
"BEZAHLT",
|
||
"IN_VORBEREITUNG",
|
||
"VERSANDBEREIT",
|
||
"VERSENDET",
|
||
"ABHOLBEREIT",
|
||
"ABGEHOLT",
|
||
];
|
||
|
||
export default async function KontoBestellungenPage() {
|
||
const locale = await getLocale();
|
||
const t = getDictionary(locale);
|
||
const statusLabels = t.orderStatusBadge;
|
||
const returnStatusLabels = t.ordersPage.returnStatusLabels;
|
||
const customer = await getCurrentCustomer();
|
||
if (!customer) redirect("/konto");
|
||
|
||
const orders = await getOrdersForCustomer(customer.id);
|
||
|
||
return (
|
||
<div className="py-20">
|
||
<Container className="max-w-3xl">
|
||
<BackLink href="/konto" label={t.ordersPage.backToAccount} />
|
||
<SectionHeading eyebrow={t.account.eyebrow} title={t.ordersPage.title} />
|
||
|
||
|
||
{orders.length === 0 ? (
|
||
<AccountEmptyState
|
||
icon={<OrderIcon />}
|
||
title={t.ordersPage.emptyTitle}
|
||
description={t.ordersPage.emptyDescription}
|
||
action={<LinkButton href="/shop">{t.cartPage.goToShop}</LinkButton>}
|
||
/>
|
||
) : (
|
||
<div className="mt-10 space-y-6">
|
||
{orders.map((order) => (
|
||
<div
|
||
key={order.id}
|
||
className="border border-ink/10 p-6 transition-colors hover:border-ink/25"
|
||
>
|
||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||
<div>
|
||
<p className="text-forest">{order.orderNumber}</p>
|
||
<p className="text-xs text-ink/65">{formatDate(order.createdAt, locale)}</p>
|
||
</div>
|
||
<div className="text-right">
|
||
<OrderStatusBadge status={order.status} label={statusLabels[order.status] ?? order.status} />
|
||
<p className="mt-1.5 text-xs text-ink/65">
|
||
{order.fulfillmentMethod === "VERSAND"
|
||
? order.shipment?.shippedAt
|
||
? t.ordersPage.shippedOn(formatDate(order.shipment.shippedAt, locale))
|
||
: t.ordersPage.shippingPending
|
||
: order.pickup?.pickedUpAt
|
||
? t.ordersPage.pickedUpOn(formatDate(order.pickup.pickedUpAt, locale))
|
||
: order.pickup?.readyAt
|
||
? t.ordersPage.readySince(formatDate(order.pickup.readyAt, locale))
|
||
: t.ordersPage.pickupPending}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<ul className="mt-4 text-sm text-ink/70 space-y-1">
|
||
{order.items.map((item) => (
|
||
<li key={item.id}>
|
||
{item.quantity} ×{" "}
|
||
<Link href={`/honig/${item.product.slug}#bewertungen`} className="hover:text-forest hover:underline">
|
||
{item.productName}
|
||
</Link>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
|
||
<div className="mt-5 max-w-md">
|
||
<OrderStatusTimeline
|
||
status={order.status}
|
||
fulfillmentMethod={order.fulfillmentMethod}
|
||
labels={{
|
||
ordered: t.orderStatusTimeline.ordered,
|
||
paid: t.orderStatusTimeline.paid,
|
||
preparing: t.orderStatusTimeline.preparing,
|
||
readyToShip: t.orderStatusTimeline.readyToShip,
|
||
shipped: t.orderStatusTimeline.shipped,
|
||
readyForPickup: t.orderStatusTimeline.readyForPickup,
|
||
pickedUp: t.orderStatusTimeline.pickedUp,
|
||
cancelled: t.orderStatusTimeline.cancelled,
|
||
ariaLabel: t.orderStatusTimeline.ariaLabel,
|
||
}}
|
||
/>
|
||
</div>
|
||
|
||
{order.shipment?.carrier && order.shipment.trackingNumber ? (
|
||
<p className="mt-3 text-xs text-ink/65">
|
||
{t.shipmentInfo.carrierLabel}: {order.shipment.carrier} · {t.shipmentInfo.trackingNumberLabel}:{" "}
|
||
{order.shipment.trackingNumber}
|
||
{(() => {
|
||
const url = getCarrierTrackingUrl(order.shipment.carrier, order.shipment.trackingNumber);
|
||
return url ? (
|
||
<>
|
||
{" "}
|
||
·{" "}
|
||
<a href={url} target="_blank" rel="noopener noreferrer" className="text-forest underline">
|
||
{t.shipmentInfo.trackPackage}
|
||
</a>
|
||
</>
|
||
) : null;
|
||
})()}
|
||
</p>
|
||
) : null}
|
||
|
||
{order.giftMessage ? (
|
||
<p className="mt-3 text-xs text-ink/65 bg-honey/10 border border-honey-dark/20 px-3 py-2">
|
||
🎁 {t.ordersPage.giftMessagePrefix} „{order.giftMessage}“
|
||
</p>
|
||
) : null}
|
||
|
||
<div className="mt-4 flex flex-wrap items-center justify-between gap-4">
|
||
<p className="font-serif text-2xl text-forest tabular-nums">{formatPrice(order.totalCents, order.currency, locale)}</p>
|
||
<div className="flex items-center gap-3">
|
||
{order.invoice ? (
|
||
<Link
|
||
href={`/rechnung/${order.invoice.invoiceNumber}`}
|
||
className="text-sm text-forest underline"
|
||
>
|
||
{t.ordersPage.viewInvoice}
|
||
</Link>
|
||
) : null}
|
||
{order.returnRequest ? (
|
||
<span className="text-sm text-ink/65">
|
||
{returnStatusLabels[order.returnRequest.status] ?? order.returnRequest.status}
|
||
</span>
|
||
) : RETURN_ELIGIBLE_STATUSES.includes(order.status) ? (
|
||
<Link
|
||
href={`/konto/bestellungen/${order.id}/retoure`}
|
||
className="text-sm text-forest underline"
|
||
>
|
||
{t.ordersPage.requestReturn}
|
||
</Link>
|
||
) : null}
|
||
<ReorderButton
|
||
items={order.items.map((item) => ({
|
||
productId: item.productId,
|
||
slug: item.product.slug,
|
||
name: item.productName,
|
||
weightGrams: item.weightGrams,
|
||
priceCents: item.unitPriceCents,
|
||
quantity: item.quantity,
|
||
}))}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</Container>
|
||
</div>
|
||
);
|
||
}
|