import type { Metadata } from "next"; import { Star } from "lucide-react"; import Link from "next/link"; import { auth } from "@/auth"; import { AppointmentCard } from "@/components/ui/appointment-card"; import { BarberCard } from "@/components/ui/barber-card"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { ServiceCard } from "@/components/ui/service-card"; import { Weekday } from "@/generated/prisma/enums"; import type { Prisma } from "@/generated/prisma/client"; import { computeOpeningStatus } from "@/lib/business/opening-status"; import { getPrimaryBusiness } from "@/lib/business/get-primary-business"; import { prisma } from "@/lib/db/prisma"; import { getAvailableServices } from "@/services/scheduling/availability.service"; import { getLoyaltySummary, type LoyaltySummary } from "@/services/loyalty/loyalty.service"; import { getBusinessReviews } from "@/services/reviews/reviews.service"; import type { AppointmentStatus } from "@/types/domain"; export const metadata: Metadata = { title: "Home" }; export const dynamic = "force-dynamic"; const WEEKDAY_ORDER: Weekday[] = [ Weekday.MONDAY, Weekday.TUESDAY, Weekday.WEDNESDAY, Weekday.THURSDAY, Weekday.FRIDAY, Weekday.SATURDAY, Weekday.SUNDAY, ]; const WEEKDAY_LABEL: Record = { MONDAY: "Monday", TUESDAY: "Tuesday", WEDNESDAY: "Wednesday", THURSDAY: "Thursday", FRIDAY: "Friday", SATURDAY: "Saturday", SUNDAY: "Sunday", }; const STATUS_MAP: Record = { PENDING: "pending_approval", CONFIRMED: "confirmed", CHECKED_IN: "arrived", IN_PROGRESS: "in_progress", }; export default async function CustomerHomePage() { const [business, session] = await Promise.all([getPrimaryBusiness(), auth()]); const [services, staff, reviews] = await Promise.all([ getAvailableServices(prisma, { businessId: business.id }), prisma.staffProfile.findMany({ where: { businessId: business.id, status: "ACTIVE", isBookableOnline: true }, include: { user: { select: { name: true, image: true } } }, }), getBusinessReviews(business.id), ]); const openingStatus = computeOpeningStatus(business.hours, business.timezone); const location = business.locations[0]; const previewServices = services.slice(0, 4); type UpcomingAppointment = Prisma.AppointmentGetPayload<{ include: { services: { include: { service: true } }; staff: { include: { user: true } } }; }>; let customerProfile: Awaited> = null; let upcomingAppointment: UpcomingAppointment | null = null; let loyalty: LoyaltySummary | null = null; let preferredBarberName: string | null = null; let lastCompletedAppointment: Awaited> = null; if (session?.user?.id) { customerProfile = await prisma.customerProfile.findUnique({ where: { userId: session.user.id }, }); if (customerProfile) { [upcomingAppointment, loyalty, lastCompletedAppointment] = await Promise.all([ prisma.appointment.findFirst({ where: { customerId: customerProfile.id, status: { in: ["PENDING", "CONFIRMED", "CHECKED_IN"] }, startAt: { gte: new Date() }, }, orderBy: { startAt: "asc" }, include: { services: { include: { service: true } }, staff: { include: { user: true } } }, }), getLoyaltySummary(prisma, { customerId: customerProfile.id, businessId: business.id }), prisma.appointment.findFirst({ where: { customerId: customerProfile.id, status: "COMPLETED" }, orderBy: { startAt: "desc" }, }), ]); if (customerProfile.preferredBarberId) { const preferred = await prisma.staffProfile.findUnique({ where: { id: customerProfile.preferredBarberId }, include: { user: { select: { name: true } } }, }); preferredBarberName = preferred?.user.name ?? null; } } } return (
{/* Hero */}
{business.logoUrl ? ( // eslint-disable-next-line @next/next/no-img-element ) : null}
{session?.user && (

Good to see you, {session.user.name?.split(" ")[0] ?? "there"}

)}

{business.name}

{location && (

{location.addressLine1}, {location.city} {location.postalCode}

)}
· {reviews.totalReviews.toLocaleString()} reviews {openingStatus.label}
{business.description && (

{business.description}

)} {/* Upcoming appointment */} {upcomingAppointment && (

Your Next Visit

s.service.name).join(" + ")} person={{ id: upcomingAppointment.staffId, name: upcomingAppointment.staff.user.name ?? "Barber", }} formattedDate={upcomingAppointment.startAt.toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short", timeZone: business.timezone, })} formattedTime={upcomingAppointment.startAt.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", timeZone: business.timezone, })} actions={ View details } />
)} {/* Loyalty summary */} {loyalty?.isEnabled && loyalty.accountId && ( {loyalty.availableReward ? (

Reward available — {loyalty.availableReward.label}

View
) : ( <>

Loyalty Progress

{loyalty.currentStamps}/{loyalty.stampsRequired} stamps
)} )} {/* Preferred barber + book again */} {(preferredBarberName || lastCompletedAppointment) && (
{preferredBarberName && (

Preferred Barber

{preferredBarberName}

)} {lastCompletedAppointment && ( )}
)} {/* Booking CTA */} {/* Services */}

Popular Services

See all
{previewServices.map((service) => ( Book } /> ))}
{/* Barbers */}

Our Barbers

{staff.map((s) => ( ))}
{/* Reviews */}

Reviews

{reviews.isDemoData && ( Demo data )}
{reviews.reviews.map((review) => (

{review.authorName}

{review.relativeDate}
{Array.from({ length: review.rating }, (_, i) => (

{review.text}

))}
{/* Hours */}

Opening Hours

{WEEKDAY_ORDER.map((day) => { const dayHours = business.hours.find((h) => h.dayOfWeek === day); return (
{WEEKDAY_LABEL[day]} {!dayHours || dayHours.isClosed || dayHours.opensAtMinutes === null || dayHours.closesAtMinutes === null ? "Closed" : `${String(Math.floor(dayHours.opensAtMinutes / 60)).padStart(2, "0")}:${String(dayHours.opensAtMinutes % 60).padStart(2, "0")} – ${String(Math.floor(dayHours.closesAtMinutes / 60)).padStart(2, "0")}:${String(dayHours.closesAtMinutes % 60).padStart(2, "0")}`}
); })}
{/* Contact */}

Contact

{business.phone && ( Call )} {business.email && ( Email )}
); }