feat: Gutschein-Detailseite zeigt, ob der Code gerade einlösbar ist und warum nicht
Docker-Images bauen und veroeffentlichen / build-and-push (push) Successful in 2m54s
Docker-Images bauen und veroeffentlichen / build-and-push (push) Successful in 2m54s
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
7a3e37f96f
commit
20adfaa97e
1 file changed
+24
@@ -3,11 +3,33 @@ import { notFound } from "next/navigation";
|
|||||||
import { connection } from "next/server";
|
import { connection } from "next/server";
|
||||||
import { Flash, PageTitle } from "@/components/admin/AdminUi";
|
import { Flash, PageTitle } from "@/components/admin/AdminUi";
|
||||||
import { VoucherForm } from "@/components/admin/VoucherForm";
|
import { VoucherForm } from "@/components/admin/VoucherForm";
|
||||||
|
import { getCamp, salesStateFor } from "@/lib/camp";
|
||||||
import { getDb } from "@/lib/db";
|
import { getDb } from "@/lib/db";
|
||||||
|
import { stripeModeOrNull } from "@/lib/env";
|
||||||
import { BOOKING_STATUS_LABEL, REDEMPTION_STATUS_LABEL } from "@/lib/labels";
|
import { BOOKING_STATUS_LABEL, REDEMPTION_STATUS_LABEL } from "@/lib/labels";
|
||||||
|
import { evaluateVoucher, VOUCHER_REJECTION_ADMIN_TEXT } from "@/lib/vouchers";
|
||||||
|
|
||||||
export const metadata = { title: "Gutschein" };
|
export const metadata = { title: "Gutschein" };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prueft wie der Buchungsdialog, ob der Code gerade einloesbar ist, und nennt
|
||||||
|
* den Grund, den Kundinnen bewusst nicht sehen (neutrale Ablehnung).
|
||||||
|
* Eine E-Mail-Bindung prueft erst der Checkout.
|
||||||
|
*/
|
||||||
|
async function redeemability(code: string): Promise<{ ok: boolean; text: string }> {
|
||||||
|
const mode = stripeModeOrNull();
|
||||||
|
if (!mode) return { ok: false, text: "Nicht prüfbar: STRIPE_SECRET_KEY fehlt oder ist ungültig." };
|
||||||
|
const db = getDb();
|
||||||
|
const now = new Date();
|
||||||
|
const camp = await getCamp(db);
|
||||||
|
const evaluation = await evaluateVoucher(db, { code, email: null, now, livemode: mode === "live", quota: camp.stammmodelQuota, lock: false });
|
||||||
|
if (!evaluation.ok) return { ok: false, text: `Derzeit nicht einlösbar: ${VOUCHER_REJECTION_ADMIN_TEXT[evaluation.reason]}.` };
|
||||||
|
if (!salesStateFor(camp, mode, now).checkoutOpen) {
|
||||||
|
return { ok: false, text: "Code ist gültig, aber die Buchung ist noch nicht geöffnet. Der Dialog zeigt deshalb keinen Preis und keinen Rabatt." };
|
||||||
|
}
|
||||||
|
return { ok: true, text: "Derzeit einlösbar (10 % auf den gültigen Preis)." };
|
||||||
|
}
|
||||||
|
|
||||||
export default async function VoucherPage({ params, searchParams }: { params: Promise<{ id: string }>; searchParams: Promise<{ ok?: string; fehler?: string }> }) {
|
export default async function VoucherPage({ params, searchParams }: { params: Promise<{ id: string }>; searchParams: Promise<{ ok?: string; fehler?: string }> }) {
|
||||||
await connection();
|
await connection();
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
@@ -17,11 +39,13 @@ export default async function VoucherPage({ params, searchParams }: { params: Pr
|
|||||||
include: { redemptions: { orderBy: { createdAt: "desc" }, include: { booking: { select: { id: true, reference: true, status: true, livemode: true } } } } },
|
include: { redemptions: { orderBy: { createdAt: "desc" }, include: { booking: { select: { id: true, reference: true, status: true, livemode: true } } } } },
|
||||||
});
|
});
|
||||||
if (!voucher) notFound();
|
if (!voucher) notFound();
|
||||||
|
const check = await redeemability(voucher.code);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Link href="/admin/gutscheine" className="text-sm underline">Alle Gutscheine</Link>
|
<Link href="/admin/gutscheine" className="text-sm underline">Alle Gutscheine</Link>
|
||||||
<PageTitle>{voucher.code}</PageTitle>
|
<PageTitle>{voucher.code}</PageTitle>
|
||||||
<Flash ok={flash.ok} fehler={flash.fehler} />
|
<Flash ok={flash.ok} fehler={flash.fehler} />
|
||||||
|
<p className={`mb-6 rounded-lg border p-4 ${check.ok ? "border-line" : "border-rosso/40 text-rosso"}`}>{check.text}</p>
|
||||||
<div className="grid gap-10 lg:grid-cols-2">
|
<div className="grid gap-10 lg:grid-cols-2">
|
||||||
<VoucherForm voucher={voucher} />
|
<VoucherForm voucher={voucher} />
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
Reference in new issue
Block a user