201 lines
7.6 KiB
TypeScript
201 lines
7.6 KiB
TypeScript
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 & 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>
|
||
);
|
||
}
|