Files
NextChair/src/app/book/review/page.tsx
T
2026-09-05 09:32:05 +01:00

201 lines
7.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 type { Metadata } from "next";
import Link from "next/link";
import { redirect } from "next/navigation";
import { auth } from "@/auth";
import { TopNav } from "@/components/navigation/top-nav";
import { Card } from "@/components/ui/card";
import { readDraft } from "@/lib/booking-draft/cookie";
import { getPrimaryBusiness } from "@/lib/business/get-primary-business";
import { prisma } from "@/lib/db/prisma";
import { getLoyaltySummary } from "@/services/loyalty/loyalty.service";
import { ConfirmBookingButton } from "@/features/booking/components/confirm-booking-button";
export const metadata: Metadata = { title: "Review Booking" };
export const dynamic = "force-dynamic";
export default async function BookReviewPage() {
const business = await getPrimaryBusiness();
const draft = await readDraft(business.id);
if (draft.serviceIds.length === 0) redirect("/book");
if (!draft.startAt) redirect("/book/barber-time");
const session = await auth();
if (!session?.user && !draft.guest) redirect("/book/details");
if (!draft.policyAcknowledged) redirect("/book/details");
const [services, staff, customerProfile] = await Promise.all([
prisma.service.findMany({ where: { id: { in: draft.serviceIds } } }),
draft.staffId
? prisma.staffProfile.findUnique({
where: { id: draft.staffId },
include: { user: { select: { name: true } } },
})
: null,
session?.user?.id
? prisma.customerProfile.findUnique({ where: { userId: session.user.id } })
: null,
]);
const totalDuration = services.reduce((sum, s) => sum + s.durationMinutes, 0);
const totalPrice = services.reduce((sum, s) => sum + Number(s.price), 0);
const startAt = new Date(draft.startAt);
const endAt = new Date(startAt.getTime() + totalDuration * 60_000);
const loyalty = customerProfile
? await getLoyaltySummary(prisma, { customerId: customerProfile.id, businessId: business.id })
: null;
const customerName = draft.guest
? `${draft.guest.firstName} ${draft.guest.lastName}`
: `${customerProfile?.firstName ?? session?.user?.name ?? ""} ${customerProfile?.lastName ?? ""}`.trim();
const customerEmail = draft.guest?.email ?? customerProfile?.email ?? session?.user?.email ?? "";
const customerPhone = draft.guest?.phone ?? customerProfile?.phone ?? "";
return (
<div className="pb-12">
<TopNav title="Review Booking" onBack={true} sticky className="bg-stone" />
<main className="mx-auto max-w-2xl space-y-4 px-6 py-6">
<Card className="space-y-6 p-6">
<div className="hidden flex items-start justify-between">
<span className="text-muted text-[10px] font-bold tracking-widest uppercase">
Business
</span>
<Link
href="/home"
className="text-ink text-[10px] font-bold tracking-widest uppercase underline"
>
{business.name}
</Link>
</div>
<div className="space-y-4 pt-2">
<div className="flex items-center justify-between">
<div>
<span className="text-muted block text-[10px] font-bold tracking-widest uppercase">
Services
</span>
<ul className="mt-1 text-sm font-bold">
{services.map((s) => (
<li key={s.id}>{s.name}</li>
))}
</ul>
</div>
<Link
href="/book"
className="text-ink text-[10px] font-bold tracking-widest uppercase underline"
>
Edit
</Link>
</div>
<div className="flex items-center justify-between">
<div>
<span className="text-muted block text-[10px] font-bold tracking-widest uppercase">
Barber
</span>
<p className="text-sm font-bold">{staff?.user.name ?? "Any available barber"}</p>
</div>
<Link
href="/book/barber-time"
className="text-ink text-[10px] font-bold tracking-widest uppercase underline"
>
Edit
</Link>
</div>
<div className="flex items-center justify-between">
<div>
<span className="text-muted block text-[10px] font-bold tracking-widest uppercase">
Date &amp; Time
</span>
<p className="text-sm font-bold">
{startAt.toLocaleDateString("en-GB", {
weekday: "short",
day: "numeric",
month: "short",
timeZone: business.timezone,
})}
{" · "}
{startAt.toLocaleTimeString("en-GB", {
hour: "2-digit",
minute: "2-digit",
timeZone: business.timezone,
})}
{"–"}
{endAt.toLocaleTimeString("en-GB", {
hour: "2-digit",
minute: "2-digit",
timeZone: business.timezone,
})}
</p>
</div>
<Link
href="/book/barber-time"
className="text-ink text-[10px] font-bold tracking-widest uppercase underline"
>
Edit
</Link>
</div>
</div>
<div className="border-line grid grid-cols-2 gap-6 border-t pt-6">
<div>
<span className="text-muted block text-[10px] font-bold tracking-widest uppercase">
Duration
</span>
<p className="text-sm font-bold">{totalDuration} min</p>
</div>
<div className="text-right">
<span className="text-muted block text-[10px] font-bold tracking-widest uppercase">
Total
</span>
<p className="text-sm font-bold">£{totalPrice.toFixed(2)}</p>
</div>
</div>
<div className="border-line border-t pt-6">
<div className="flex items-center justify-between">
<span className="text-muted block text-[10px] font-bold tracking-widest uppercase">
Your Details
</span>
<Link
href="/book/details"
className="text-ink text-[10px] font-bold tracking-widest uppercase underline"
>
Edit
</Link>
</div>
<p className="mt-1 text-sm font-bold">{customerName}</p>
<p className="text-muted text-xs">
{customerEmail}
{customerPhone ? ` · ${customerPhone}` : ""}
</p>
</div>
{loyalty?.availableReward && (
<div className="border-line bg-accent-loyalty/10 rounded-lg border-t p-4">
<p className="text-sm font-bold">You have a reward available</p>
<p className="text-muted text-xs">
Mention it to your barber at check-in — {loyalty.availableReward.label}.
</p>
</div>
)}
{draft.notes && (
<div className="border-line border-t pt-6">
<span className="text-muted block text-[10px] font-bold tracking-widest uppercase">
Notes
</span>
<p className="text-muted mt-1 text-sm italic">{draft.notes}</p>
</div>
)}
</Card>
<ConfirmBookingButton businessId={business.id} />
</main>
</div>
);
}