feat(ui): Polish-Durchgang Admin und Mobil, Erstattung nur mit Rueckfrage

Sicherheit: "Jetzt per Stripe erstatten" loeste die Rueckerstattung bisher
mit einem einzigen Klick aus. Erstatten und Ablehnen einer Retoure laufen
jetzt ueber ConfirmForm; die Rueckfrage nennt Bestellnummer und Betrag.

Admin: Produkte, Bestellungen und Retouren nutzen dieselben Bausteine wie
die uebrigen Listen (Seitenkopf, Suche mit Zusatzfiltern und Archiv-
Parameter, Status-Badges, Leerzustaende). Produkttabelle ohne seitliches
Scrollen bei 1440 px: Bearbeiten ueber den Produktnamen, Charge ohne
Umbruch. Geschenk-Markierung als SVG statt Emoji. Letzte rohe Knoepfe
(Bestelldetail, ApiCore, Inhalte) auf Button umgestellt.

Mobil (390 px): Ueberlauf auf /konto (Datenexport) und /kontakt
(E-Mail-Adresse) behoben. Tippziele unter 24 px vergroessert
(Zurueck-Link, "Zum Honig", Links auf der Produktseite). Wunschliste mit
SVG-Stern statt Unicode-Zeichen.

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 21:24:55 +02:00
1 parent b7a75dddca
commit 11eccec01b
16 files changed
+221 -155

No files matched your search

+6 -5
View File
@@ -15,6 +15,7 @@ import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
import { isDhlConfigured } from "@/lib/dhl";
import { OrderStatusBadge, OrderStatusTimeline } from "@/components/account-ui";
import { Button } from "@/components/ui";
export default async function AdminOrderDetailPage(props: PageProps<"/admin/bestellungen/[id]">) {
const t = getDictionary(await getLocale());
@@ -69,9 +70,9 @@ export default async function AdminOrderDetailPage(props: PageProps<"/admin/best
</option>
))}
</select>
<button type="submit" className="bg-forest text-cream px-4 py-2 text-sm">
<Button type="submit" className="px-5 py-2.5">
{t.adminOrderDetailPage.saveStatus}
</button>
</Button>
</form>
</div>
@@ -192,9 +193,9 @@ export default async function AdminOrderDetailPage(props: PageProps<"/admin/best
className="mt-1 w-56 border border-ink/20 bg-background px-3 py-2 text-sm"
/>
</label>
<button type="submit" className="bg-forest text-cream px-4 py-2 text-sm">
<Button type="submit" className="px-5 py-2.5">
{t.adminOrderDetailPage.saveTracking}
</button>
</Button>
</form>
</div>
) : null}
@@ -281,7 +282,7 @@ export default async function AdminOrderDetailPage(props: PageProps<"/admin/best
<span>{order.invoice.invoiceNumber}</span>
<a
href={`/rechnung/${order.invoice.invoiceNumber}/pdf`}
className="text-forest underline text-sm"
className="link-draw text-sm text-forest"
>
{t.adminOrderDetailPage.downloadPdf}
</a>
+41 -40
View File
@@ -11,6 +11,7 @@ import { getDictionary } from "@/i18n/get-dictionary";
import { isArchiveView } from "@/lib/archive";
import { ensureAutoArchived } from "@/lib/archive-runner";
import { AdminArchiveTabs, AdminArchiveToggle } from "@/components/admin-archive";
import { AdminEmptyState, AdminPageHeader, AdminSearchForm } from "@/components/admin-list";
export default async function AdminOrdersPage(props: PageProps<"/admin/bestellungen">) {
const t = getDictionary(await getLocale());
@@ -49,7 +50,7 @@ export default async function AdminOrdersPage(props: PageProps<"/admin/bestellun
return (
<div>
<BackLink href="/admin" label={t.adminOrdersPage.backToDashboard} />
<h1 className="text-2xl text-forest mb-8">{t.adminOrdersPage.title}</h1>
<AdminPageHeader title={t.adminOrdersPage.title} />
{/* Die DHL-Aktionen leiten bei einer nicht mehr auffindbaren Bestellung
hierher zurueck (admin/actions/dhl.ts). Ohne diese Ausgabe sah das
aus wie ein folgenloser Klick. */}
@@ -67,18 +68,16 @@ export default async function AdminOrdersPage(props: PageProps<"/admin/bestellun
/>
{isArchive ? <p className="mb-6 text-xs text-ink/60 max-w-2xl">{t.adminArchive.autoRuleOrders}</p> : null}
<form method="GET" className="flex flex-wrap items-end gap-3 mb-6">
{isArchive ? <input type="hidden" name="ansicht" value="archiv" /> : null}
<label className="text-sm">
<span className="block text-ink/65 text-xs mb-1">{t.adminOrdersPage.searchLabel}</span>
<input
type="text"
name="q"
defaultValue={query}
placeholder={t.adminOrdersPage.searchPlaceholder}
className="border border-ink/20 bg-background px-3 py-2 text-sm w-64"
/>
</label>
<AdminSearchForm
query={query}
label={t.adminOrdersPage.searchLabel}
placeholder={t.adminOrdersPage.searchPlaceholder}
submitLabel={t.adminOrdersPage.filterButton}
resetHref={isArchive ? "/admin/bestellungen?ansicht=archiv" : "/admin/bestellungen"}
resetLabel={t.adminOrdersPage.resetFilter}
hiddenFields={isArchive ? { ansicht: "archiv" } : {}}
isFiltered={Boolean(query || statusFilter)}
>
<label className="text-sm">
<span className="block text-ink/65 text-xs mb-1">{t.adminOrdersPage.statusLabel}</span>
<select
@@ -94,18 +93,7 @@ export default async function AdminOrdersPage(props: PageProps<"/admin/bestellun
))}
</select>
</label>
<button type="submit" className="bg-forest text-cream px-4 py-2 text-sm">
{t.adminOrdersPage.filterButton}
</button>
{query || statusFilter ? (
<Link
href={isArchive ? "/admin/bestellungen?ansicht=archiv" : "/admin/bestellungen"}
className="text-sm text-ink/65 underline"
>
{t.adminOrdersPage.resetFilter}
</Link>
) : null}
</form>
</AdminSearchForm>
<div className="overflow-x-auto bg-background border border-ink/10">
<table className="w-full text-sm">
@@ -125,12 +113,19 @@ export default async function AdminOrdersPage(props: PageProps<"/admin/bestellun
{orders.map((order) => (
<tr key={order.id} className="transition-colors hover:bg-ink/[0.03]">
<td className="p-4 text-forest whitespace-nowrap">
<Link href={`/admin/bestellungen/${order.id}`} className="underline">
<Link href={`/admin/bestellungen/${order.id}`} className="link-draw">
{order.orderNumber}
</Link>
{order.giftMessage ? (
<span className="ml-1.5" title={t.adminOrdersPage.giftOrderTitle}>
🎁
<span
className="ml-1.5 inline-flex align-[-2px] text-honey-dark"
title={t.adminOrdersPage.giftOrderTitle}
>
<svg viewBox="0 0 24 24" className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinejoin="round" aria-hidden="true">
<rect x="3.5" y="8.5" width="17" height="4" />
<path d="M5 12.5v8h14v-8M12 8.5v12M12 8.5c-1.5-3.5-5-4-5-1.5 0 1.5 2.5 1.5 5 1.5zm0 0c1.5-3.5 5-4 5-1.5 0 1.5-2.5 1.5-5 1.5z" />
</svg>
<span className="sr-only">{t.adminOrdersPage.giftOrderTitle}</span>
</span>
) : null}
</td>
@@ -139,9 +134,9 @@ export default async function AdminOrdersPage(props: PageProps<"/admin/bestellun
<br />
<span className="text-ink/65 text-xs">{order.email}</span>
</td>
<td className="p-4">{formatDate(order.createdAt)}</td>
<td className="p-4">{order.items.length}</td>
<td className="p-4">{formatPrice(order.totalCents, order.currency)}</td>
<td className="p-4 whitespace-nowrap tabular-nums">{formatDate(order.createdAt)}</td>
<td className="p-4 tabular-nums">{order.items.length}</td>
<td className="p-4 whitespace-nowrap tabular-nums">{formatPrice(order.totalCents, order.currency)}</td>
<td className="p-4">
<OrderStatusBadge status={order.status} label={statusLabels[order.status] ?? order.status} />
<form action={updateOrderStatusAction} className="mt-2 flex items-center gap-2">
@@ -149,7 +144,8 @@ export default async function AdminOrdersPage(props: PageProps<"/admin/bestellun
<select
name="status"
defaultValue={order.status}
className="border border-ink/20 bg-background px-2 py-1 text-xs"
aria-label={t.adminOrdersPage.statusLabel}
className="border border-ink/20 bg-background px-2 py-1.5 text-xs"
>
{Object.entries(statusLabels).map(([value, label]) => (
<option key={value} value={value}>
@@ -157,7 +153,10 @@ export default async function AdminOrdersPage(props: PageProps<"/admin/bestellun
</option>
))}
</select>
<button type="submit" className="text-xs text-forest underline">
<button
type="submit"
className="border border-forest px-3 py-1.5 text-xs text-forest transition-colors duration-300 hover:bg-forest hover:text-cream"
>
{t.adminOrdersPage.save}
</button>
</form>
@@ -184,13 +183,15 @@ export default async function AdminOrdersPage(props: PageProps<"/admin/bestellun
</tbody>
</table>
{orders.length === 0 ? (
<p className="p-6 text-ink/65">
{query || statusFilter
? t.adminOrdersPage.noResultsFiltered
: isArchive
? t.adminArchive.emptyArchive
: t.adminOrdersPage.noOrdersYet}
</p>
<AdminEmptyState
message={
query || statusFilter
? t.adminOrdersPage.noResultsFiltered
: isArchive
? t.adminArchive.emptyArchive
: t.adminOrdersPage.noOrdersYet
}
/>
) : null}
</div>
</div>
+3 -2
View File
@@ -8,6 +8,7 @@ import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
import type { Dictionary } from "@/i18n/dictionary-type";
import type { Locale } from "@/i18n/config";
import { Button } from "@/components/ui";
const ROWS_BY_TYPE: Record<ContentFieldDef["type"], number> = {
text: 1,
@@ -190,9 +191,9 @@ function GroupForm({
<FieldEditor key={def.key} def={def} row={current.get(def.key)} locale={locale} t={t} />
))}
<button type="submit" className="bg-forest text-cream px-6 py-2 text-sm">
<Button type="submit" className="px-5 py-2.5">
{t.adminContentPage.save}
</button>
</Button>
</form>
);
}
+50 -40
View File
@@ -1,4 +1,3 @@
import Link from "next/link";
import { prisma } from "@/lib/prisma";
import { BackLink } from "@/components/back-link";
import { formatPrice } from "@/lib/format";
@@ -10,6 +9,15 @@ import { getDictionary } from "@/i18n/get-dictionary";
import { isArchiveView } from "@/lib/archive";
import { ensureAutoArchived } from "@/lib/archive-runner";
import { AdminArchiveTabs, AdminArchiveToggle } from "@/components/admin-archive";
import {
AdminActionButton,
AdminActionLink,
AdminActionNote,
AdminBadge,
AdminEmptyState,
AdminPageHeader,
AdminRowActions,
} from "@/components/admin-list";
export default async function AdminProductsPage(props: PageProps<"/admin/produkte">) {
const t = getDictionary(await getLocale());
@@ -34,15 +42,12 @@ export default async function AdminProductsPage(props: PageProps<"/admin/produkt
prisma.product.count({ where: { archivedAt: { not: null } } }),
]);
const newProduct = { href: "/admin/produkte/neu", label: t.adminProductsPage.newProduct };
return (
<div>
<BackLink href="/admin" label={t.adminProductsPage.backToDashboard} />
<div className="flex items-center justify-between mb-8">
<h1 className="text-2xl text-forest">{t.adminProductsPage.title}</h1>
<Link href="/admin/produkte/neu" className="bg-forest text-cream px-5 py-2 text-sm">
{t.adminProductsPage.newProduct}
</Link>
</div>
<AdminPageHeader title={t.adminProductsPage.title} action={newProduct} />
<ApiCoreSyncResult searchParams={searchParams} />
@@ -63,44 +68,51 @@ export default async function AdminProductsPage(props: PageProps<"/admin/produkt
<table className="w-full text-sm">
<thead className="text-left text-ink/65 border-b border-ink/10">
<tr>
<th className="p-4">{t.adminProductsPage.colProduct}</th>
<th className="p-4">{t.adminProductsPage.colCategory}</th>
<th className="p-4">{t.adminProductsPage.colHarvest}</th>
<th className="p-4">{t.adminProductsPage.colBatch}</th>
<th className="p-4">{t.adminProductsPage.colWeight}</th>
<th className="p-4">{t.adminProductsPage.colPrice}</th>
<th className="p-4">{t.adminProductsPage.colStock}</th>
<th className="p-4">{t.adminProductsPage.colStatus}</th>
<th className="p-4" />
<th className="px-3 py-4">{t.adminProductsPage.colProduct}</th>
<th className="px-3 py-4">{t.adminProductsPage.colCategory}</th>
<th className="px-3 py-4">{t.adminProductsPage.colHarvest}</th>
<th className="px-3 py-4">{t.adminProductsPage.colBatch}</th>
<th className="px-3 py-4">{t.adminProductsPage.colWeight}</th>
<th className="px-3 py-4">{t.adminProductsPage.colPrice}</th>
<th className="px-3 py-4">{t.adminProductsPage.colStock}</th>
<th className="px-3 py-4">{t.adminProductsPage.colStatus}</th>
<th className="px-3 py-4" />
</tr>
</thead>
<tbody className="divide-y divide-ink/10">
{products.map((product) => (
<tr key={product.id} className="transition-colors hover:bg-ink/[0.03]">
<td className="p-4 text-forest">{product.name}</td>
<td className="p-4">{t.common.categories[product.category]}</td>
<td className="p-4">
<td className="px-3 py-4">
<AdminActionLink href={`/admin/produkte/${product.id}`}>{product.name}</AdminActionLink>
</td>
<td className="px-3 py-4">{t.common.categories[product.category]}</td>
<td className="px-3 py-4">
{product.harvest ? `${product.harvest.type} ${product.harvest.year}` : "—"}
</td>
<td className="p-4">{product.batch?.code ?? "—"}</td>
<td className="p-4">{product.weightGrams} g</td>
<td className="p-4">{formatPrice(product.priceCents, product.currency)}</td>
<td className="p-4">{product.inventory?.quantity ?? 0}</td>
<td className="p-4">
{product.isActive ? t.adminProductsPage.statusActive : t.adminProductsPage.statusInactive}
<td className="px-3 py-4 whitespace-nowrap">{product.batch?.code ?? "—"}</td>
<td className="px-3 py-4 whitespace-nowrap tabular-nums">{product.weightGrams} g</td>
<td className="px-3 py-4 whitespace-nowrap tabular-nums">{formatPrice(product.priceCents, product.currency)}</td>
<td className={`px-3 py-4 tabular-nums ${(product.inventory?.quantity ?? 0) === 0 ? "text-ink/55" : ""}`}>
{product.inventory?.quantity ?? 0}
</td>
<td className="px-3 py-4">
{product.isActive ? (
<AdminBadge tone="positive">{t.adminProductsPage.statusActive}</AdminBadge>
) : (
<AdminBadge tone="neutral">{t.adminProductsPage.statusInactive}</AdminBadge>
)}
{/* Ein Produkt ohne Preis laesst sich nicht aktiv schalten
(admin/actions/products.ts). Aus ApiCore importierte
Entwuerfe sehen sonst wie ein normales inaktives Produkt
aus, und es bliebe unklar, WARUM das Aktivieren scheitert. */}
{product.priceCents <= 0 ? (
<span className="block text-xs text-honey-dark">{t.adminProductsPage.draftHint}</span>
<span className="mt-1.5 block text-xs text-honey-dark">{t.adminProductsPage.draftHint}</span>
) : null}
</td>
<td className="p-4 whitespace-nowrap">
<Link href={`/admin/produkte/${product.id}`} className="text-forest underline mr-4">
{t.adminProductsPage.edit}
</Link>
<span className="mr-4">
<td className="px-3 py-4">
{/* Bearbeiten ueber den Produktnamen links — hier nur die
selteneren Aktionen, damit die Tabelle nicht seitlich scrollt. */}
<AdminRowActions>
<AdminArchiveToggle
kind="products"
id={product.id}
@@ -113,7 +125,6 @@ export default async function AdminProductsPage(props: PageProps<"/admin/produkt
manualHint: t.adminArchive.manualHint,
}}
/>
</span>
{/* Loeschen nur, solange nie bestellt: OrderItem verweist mit
onDelete: Restrict auf das Produkt, damit gestellte
Rechnungen ihren Bezug behalten (siehe
@@ -123,25 +134,24 @@ export default async function AdminProductsPage(props: PageProps<"/admin/produkt
<ConfirmForm
action={deleteProductAction}
confirmMessage={t.adminProductsPage.deleteConfirm(product.name)}
className="inline"
>
<input type="hidden" name="id" value={product.id} />
<button type="submit" className="text-red-700 underline">
{t.adminProductsPage.delete}
</button>
<AdminActionButton tone="danger">{t.adminProductsPage.delete}</AdminActionButton>
</ConfirmForm>
) : (
<span className="text-xs text-ink/50">{t.adminProductsPage.notDeletable}</span>
<AdminActionNote>{t.adminProductsPage.notDeletable}</AdminActionNote>
)}
</AdminRowActions>
</td>
</tr>
))}
</tbody>
</table>
{products.length === 0 ? (
<p className="p-6 text-ink/65">
{isArchive ? t.adminArchive.emptyArchive : t.adminProductsPage.noProductsYet}
</p>
<AdminEmptyState
message={isArchive ? t.adminArchive.emptyArchive : t.adminProductsPage.noProductsYet}
action={isArchive ? undefined : newProduct}
/>
) : null}
</div>
+69 -50
View File
@@ -1,4 +1,3 @@
import Link from "next/link";
import { prisma } from "@/lib/prisma";
import { BackLink } from "@/components/back-link";
import { formatDate, formatPrice } from "@/lib/format";
@@ -9,6 +8,23 @@ import { getDictionary } from "@/i18n/get-dictionary";
import { isArchiveView } from "@/lib/archive";
import { ensureAutoArchived } from "@/lib/archive-runner";
import { AdminArchiveTabs, AdminArchiveToggle } from "@/components/admin-archive";
import { ConfirmForm } from "@/components/confirm-form";
import { Button } from "@/components/ui";
import {
AdminActionButton,
AdminBadge,
AdminEmptyState,
AdminPageHeader,
AdminSearchForm,
type AdminBadgeTone,
} from "@/components/admin-list";
const STATUS_TONES: Record<string, AdminBadgeTone> = {
ANGEFRAGT: "pending",
ANGENOMMEN: "pending",
ABGELEHNT: "neutral",
ERSTATTET: "positive",
};
export default async function AdminRetourenPage(props: PageProps<"/admin/retouren">) {
const t = getDictionary(await getLocale());
@@ -53,7 +69,7 @@ export default async function AdminRetourenPage(props: PageProps<"/admin/retoure
return (
<div>
<BackLink href="/admin" label={t.adminReturnsPage.backToDashboard} />
<h1 className="text-2xl text-forest mb-8">{t.adminReturnsPage.title}</h1>
<AdminPageHeader title={t.adminReturnsPage.title} />
<AdminArchiveTabs
basePath="/admin/retouren"
@@ -68,30 +84,15 @@ export default async function AdminRetourenPage(props: PageProps<"/admin/retoure
/>
{isArchive ? <p className="mb-6 text-xs text-ink/60 max-w-2xl">{t.adminArchive.autoRuleReturns}</p> : null}
<form method="GET" className="flex flex-wrap items-end gap-3 mb-6">
{isArchive ? <input type="hidden" name="ansicht" value="archiv" /> : null}
<label className="text-sm">
<span className="block text-ink/65 text-xs mb-1">{t.adminReturnsPage.searchLabel}</span>
<input
type="text"
name="q"
defaultValue={query}
placeholder={t.adminReturnsPage.searchPlaceholder}
className="border border-ink/20 bg-background px-3 py-2 text-sm w-64"
/>
</label>
<button type="submit" className="bg-forest text-cream px-4 py-2 text-sm">
{t.adminReturnsPage.filterButton}
</button>
{query ? (
<Link
href={isArchive ? "/admin/retouren?ansicht=archiv" : "/admin/retouren"}
className="text-sm text-ink/65 underline"
>
{t.adminReturnsPage.resetFilter}
</Link>
) : null}
</form>
<AdminSearchForm
query={query}
label={t.adminReturnsPage.searchLabel}
placeholder={t.adminReturnsPage.searchPlaceholder}
submitLabel={t.adminReturnsPage.filterButton}
resetHref={isArchive ? "/admin/retouren?ansicht=archiv" : "/admin/retouren"}
resetLabel={t.adminReturnsPage.resetFilter}
hiddenFields={isArchive ? { ansicht: "archiv" } : {}}
/>
<div className="space-y-4">
{returnRequests.map((request) => (
@@ -102,11 +103,16 @@ export default async function AdminRetourenPage(props: PageProps<"/admin/retoure
<p className="text-sm text-ink/70">
{request.order.firstName} {request.order.lastName} · {request.order.email}
</p>
<p className="text-xs text-ink/65 mt-1">{formatDate(request.createdAt)}</p>
<p className="text-xs text-ink/65 mt-1 tabular-nums">{formatDate(request.createdAt)}</p>
</div>
<div className="text-right">
<p className="text-sm">{statusLabels[request.status] ?? request.status}</p>
<div className="mt-2">
<div className="flex flex-col items-end gap-2">
<AdminBadge
tone={STATUS_TONES[request.status] ?? "neutral"}
pulse={request.status === "ANGEFRAGT"}
>
{statusLabels[request.status] ?? request.status}
</AdminBadge>
<div>
<AdminArchiveToggle
kind="returns"
id={request.id}
@@ -130,46 +136,59 @@ export default async function AdminRetourenPage(props: PageProps<"/admin/retoure
</li>
))}
</ul>
<p className="mt-2 text-forest">{formatPrice(request.order.totalCents, request.order.currency)}</p>
<p className="mt-2 text-forest tabular-nums">{formatPrice(request.order.totalCents, request.order.currency)}</p>
<p className="mt-4 text-sm text-ink/70 border-t border-ink/10 pt-4">{request.reason}</p>
{request.status === "ANGEFRAGT" ? (
<div className="mt-4 flex gap-4">
<div className="mt-4 flex flex-wrap items-center gap-6 text-sm">
<form action={updateReturnRequestStatusAction}>
<input type="hidden" name="id" value={request.id} />
<input type="hidden" name="status" value="ANGENOMMEN" />
<button type="submit" className="text-xs text-forest underline">
<Button type="submit" variant="secondary" className="px-5 py-2.5">
{t.adminReturnsPage.accept}
</button>
</Button>
</form>
<form action={updateReturnRequestStatusAction}>
<ConfirmForm
action={updateReturnRequestStatusAction}
confirmMessage={t.adminReturnsPage.rejectConfirm(request.order.orderNumber)}
>
<input type="hidden" name="id" value={request.id} />
<input type="hidden" name="status" value="ABGELEHNT" />
<button type="submit" className="text-xs text-red-700 underline">
{t.adminReturnsPage.reject}
</button>
</form>
<AdminActionButton tone="danger">{t.adminReturnsPage.reject}</AdminActionButton>
</ConfirmForm>
</div>
) : null}
{request.status === "ANGENOMMEN" ? (
<form action={refundReturnRequestAction} className="mt-4">
// Erstattung bewegt echtes Geld und laesst sich nicht zuruecknehmen —
// deshalb mit Rueckfrage samt Betrag statt Ein-Klick-Link.
<ConfirmForm
action={refundReturnRequestAction}
confirmMessage={t.adminReturnsPage.refundConfirm(
request.order.orderNumber,
formatPrice(request.order.totalCents, request.order.currency)
)}
className="mt-4"
>
<input type="hidden" name="id" value={request.id} />
<button type="submit" className="text-xs text-forest underline">
<Button type="submit" className="px-5 py-2.5">
{t.adminReturnsPage.refundNow}
</button>
</form>
</Button>
</ConfirmForm>
) : null}
</div>
))}
{returnRequests.length === 0 ? (
<p className="text-ink/65">
{query
? t.adminReturnsPage.noResultsFiltered
: isArchive
? t.adminArchive.emptyArchive
: t.adminReturnsPage.noRequestsYet}
</p>
<AdminEmptyState
bordered
message={
query
? t.adminReturnsPage.noResultsFiltered
: isArchive
? t.adminArchive.emptyArchive
: t.adminReturnsPage.noRequestsYet
}
/>
) : null}
</div>
</div>
+19 -5
View File
@@ -292,7 +292,7 @@ export default async function ProductDetailPage(
</p>
<Link
href="/versand-zahlung"
className="mt-2 inline-block text-xs text-ink/65 underline decoration-ink/30 hover:text-forest hover:decoration-forest"
className="mt-1 inline-block py-1 text-xs text-ink/65 underline decoration-ink/30 hover:text-forest hover:decoration-forest"
>
{t.productPage.shippingHint(
baseShippingCents === 0 ? t.cartPage.free : formatPrice(baseShippingCents, "EUR", locale)
@@ -367,7 +367,21 @@ export default async function ProductDetailPage(
<form action={toggleWishlistAction} className="mt-3">
<input type="hidden" name="productId" value={product.id} />
<input type="hidden" name="redirectPath" value={`/honig/${product.slug}`} />
<button type="submit" className="text-sm text-ink/70 hover:text-forest underline">
<button
type="submit"
className="inline-flex items-center gap-2 py-1.5 text-sm text-ink/70 underline hover:text-forest"
>
<svg
viewBox="0 0 20 20"
className={`h-4 w-4 ${isWishlisted ? "text-honey" : "text-honey-dark"}`}
fill={isWishlisted ? "currentColor" : "none"}
stroke="currentColor"
strokeWidth="1.3"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M10 1.5l2.47 5.27 5.66.6-4.24 3.9 1.15 5.6L10 14.9l-5.04 2.97 1.15-5.6-4.24-3.9 5.66-.6z" />
</svg>
{isWishlisted ? t.productPage.removeFromWishlist : t.productPage.addToWishlist}
</button>
</form>
@@ -413,11 +427,11 @@ export default async function ProductDetailPage(
{isHoney ? (
<div className="mt-6 flex flex-wrap items-baseline gap-x-3 gap-y-1 text-sm">
<span className="text-ink/65">{t.productPage.learnMoreLabel}</span>
<Link href="/wissen/warum-kristallisiert-honig" className="text-forest underline">
<Link href="/wissen/warum-kristallisiert-honig" className="inline-block py-0.5 text-forest underline">
{t.wissenArticles["warum-kristallisiert-honig"].title}
</Link>
{harvestWissenSlug ? (
<Link href={`/wissen/${harvestWissenSlug}`} className="text-forest underline">
<Link href={`/wissen/${harvestWissenSlug}`} className="inline-block py-0.5 text-forest underline">
{t.wissenArticles[harvestWissenSlug].title}
</Link>
) : null}
@@ -427,7 +441,7 @@ export default async function ProductDetailPage(
{isHoney && product.batch ? (
<Link
href={`/charge/${product.batch.code}`}
className="mt-3 inline-block text-sm text-forest underline"
className="mt-2.5 inline-block py-0.5 text-sm text-forest underline"
>
{t.productPage.viewBatchOrigin}
</Link>
+2 -2
View File
@@ -28,13 +28,13 @@ export default async function KontaktPage() {
<div className="mt-12 grid gap-6 md:grid-cols-5">
{/* Kontaktkarte auf Wabenraster: die E-Mail ist der Hauptweg und
steht deshalb gross in Serifenschrift. */}
<ScrollReveal delay={0.1} className="md:col-span-3">
<ScrollReveal delay={0.1} className="min-w-0 md:col-span-3">
<div className="relative h-full overflow-hidden bg-forest px-8 py-10 text-cream md:px-10">
<HoneycombPattern className="text-honey-light/[0.1]" size={24} fade="right" />
<div className="relative">
<a
href={`mailto:${company.email}`}
className="group inline-flex items-center gap-3 font-serif text-3xl md:text-4xl text-cream"
className="group inline-flex items-center gap-3 font-serif text-2xl sm:text-3xl md:text-4xl text-cream"
>
<span className="link-draw pb-1">{company.email}</span>
<svg
+1 -1
View File
@@ -209,7 +209,7 @@ export default async function KontoPage() {
</form>
<h2 className="text-2xl text-forest mb-4">{t.account.privacy.heading}</h2>
<div className="border border-ink/10 p-6 flex items-center justify-between gap-4">
<div className="border border-ink/10 p-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
<div>
<h3 className="text-lg text-forest">{t.account.privacy.downloadDataHeading}</h3>
<p className="mt-1 text-sm text-ink/70">{t.account.privacy.downloadDataDescription}</p>