Shitaye-FrontEnd/src/app/booking/BookingPageClient.tsx

237 lines
9.8 KiB
TypeScript

"use client";
import Image from "next/image";
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import { RoomSelectBooking } from "@/components/RoomSelectBooking";
import { useBooking } from "@/context/BookingContext";
import { rooms } from "@/lib/mocks/rooms";
import { siteConfig } from "@/lib/mocks/site";
import { submitBookingHold } from "@/lib/mocks/api";
export function BookingPageClient() {
const searchParams = useSearchParams();
const router = useRouter();
const {
checkIn,
checkOut,
guests,
guest,
setRoomId,
setGuest,
setHoldReference,
setPayLaterHold,
selectedRoom,
nights,
} = useBooking();
const [pending, setPending] = useState<null | "payment" | "reserve">(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const r = searchParams.get("room");
if (r && rooms.some((x) => x.id === r)) setRoomId(r);
}, [searchParams, setRoomId]);
const canContinue =
selectedRoom &&
guest.firstName.trim() &&
guest.lastName.trim() &&
guest.email.trim() &&
guest.phone.trim() &&
guest.flightBookingNumber.trim() &&
guest.arrivalTime.trim();
async function placeHold(mode: "payment" | "reserve") {
if (!canContinue || !selectedRoom) return;
setError(null);
setPending(mode);
try {
const { reference } = await submitBookingHold({
roomId: selectedRoom.id,
email: guest.email,
flightBookingNumber: guest.flightBookingNumber.trim(),
arrivalTime: guest.arrivalTime.trim(),
});
setHoldReference(reference);
setPayLaterHold(mode === "reserve");
router.push(mode === "payment" ? "/payment" : "/reserve-held");
} catch {
setError("Something went wrong. Please try again.");
} finally {
setPending(null);
}
}
return (
<div className="mx-auto max-w-2xl px-4 py-12 md:py-16">
<p className="text-xs font-semibold uppercase tracking-widest text-[var(--color-primary)]">
Book your stay
</p>
<h1 className="mt-2 font-display text-3xl md:text-4xl">
It only takes a moment
</h1>
<p className="mt-2 text-sm text-[var(--color-muted)]">
Pay now, or reserve first and complete payment later in this session mock only.
</p>
<div className="mt-8 rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] p-5 shadow-sm">
<RoomSelectBooking selected={selectedRoom} onSelect={setRoomId} />
</div>
{selectedRoom ? (
<div className="mt-8 overflow-hidden rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface-muted)]">
<div className="relative aspect-[2/1] w-full">
<Image
src={selectedRoom.gallery[0]!}
alt={selectedRoom.name}
fill
className="object-cover"
sizes="(max-width:768px) 100vw, 672px"
/>
</div>
<div className="space-y-3 p-6 text-sm">
<Row label="Hotel" value={siteConfig.name} />
<Row label="Room" value={selectedRoom.name} />
<Row label="Guests" value={`${guests} guest${guests !== 1 ? "s" : ""}`} />
<Row label="Check-in" value={formatDate(checkIn)} />
<Row label="Check-out" value={formatDate(checkOut)} />
<Row label="Nights" value={String(nights)} />
</div>
</div>
) : (
<div className="mt-8 rounded-2xl border border-dashed border-[var(--color-border)] bg-[var(--color-surface)] p-8 text-center">
<p className="text-[var(--color-muted)]">Select a room to continue.</p>
<Link
href="/#rooms"
className="mt-4 inline-block text-sm font-semibold text-[var(--color-primary)] hover:underline"
>
Browse rooms
</Link>
</div>
)}
<div className="mt-10 rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] p-6 shadow-sm">
<h2 className="text-lg font-semibold">Who&apos;s checking in?</h2>
<div className="mt-4 grid gap-4 sm:grid-cols-2">
<label className="block text-sm">
<span className="mb-1 block text-[var(--color-muted)]">First name</span>
<input
value={guest.firstName}
onChange={(e) => setGuest({ firstName: e.target.value })}
className="w-full rounded-xl border border-[var(--color-border)] px-3 py-2.5 focus:border-[var(--color-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20"
autoComplete="given-name"
/>
</label>
<label className="block text-sm">
<span className="mb-1 block text-[var(--color-muted)]">Last name</span>
<input
value={guest.lastName}
onChange={(e) => setGuest({ lastName: e.target.value })}
className="w-full rounded-xl border border-[var(--color-border)] px-3 py-2.5 focus:border-[var(--color-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20"
autoComplete="family-name"
/>
</label>
<label className="block text-sm sm:col-span-2">
<span className="mb-1 block text-[var(--color-muted)]">Email</span>
<input
type="email"
value={guest.email}
onChange={(e) => setGuest({ email: e.target.value })}
className="w-full rounded-xl border border-[var(--color-border)] px-3 py-2.5 focus:border-[var(--color-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20"
autoComplete="email"
/>
</label>
<label className="block text-sm sm:col-span-2">
<span className="mb-1 block text-[var(--color-muted)]">Phone</span>
<input
type="tel"
value={guest.phone}
onChange={(e) => setGuest({ phone: e.target.value })}
className="w-full rounded-xl border border-[var(--color-border)] px-3 py-2.5 focus:border-[var(--color-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20"
autoComplete="tel"
/>
</label>
</div>
</div>
<div className="mt-8 rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] p-6 shadow-sm">
<h2 className="text-lg font-semibold">Flight arrival</h2>
<p className="mt-1 text-sm text-[var(--color-muted)]">
So we can coordinate your airport shuttle and room readiness.
</p>
<div className="mt-4 grid gap-4 sm:grid-cols-2">
<label className="block text-sm sm:col-span-2">
<span className="mb-1 block text-[var(--color-muted)]">Flight booking / PNR / ticket number</span>
<input
value={guest.flightBookingNumber}
onChange={(e) => setGuest({ flightBookingNumber: e.target.value })}
placeholder="e.g. ABC123 or airline record locator"
className="w-full rounded-xl border border-[var(--color-border)] px-3 py-2.5 focus:border-[var(--color-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20"
autoComplete="off"
/>
</label>
<label className="block text-sm sm:col-span-2">
<span className="mb-1 block text-[var(--color-muted)]">Arrival time (local)</span>
<input
type="time"
value={guest.arrivalTime}
onChange={(e) => setGuest({ arrivalTime: e.target.value })}
className="w-full max-w-[12rem] rounded-xl border border-[var(--color-border)] px-3 py-2.5 focus:border-[var(--color-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20"
/>
</label>
</div>
</div>
{error ? <p className="mt-4 text-sm text-red-700">{error}</p> : null}
<div className="mt-8 flex flex-col gap-3">
<button
type="button"
disabled={!canContinue || pending !== null}
aria-busy={pending === "payment"}
onClick={() => placeHold("payment")}
className="w-full rounded-full bg-[var(--color-text)] py-4 text-sm font-semibold text-white transition hover:bg-[var(--color-primary)] disabled:cursor-not-allowed disabled:opacity-50"
>
{pending === "payment" ? "Please wait…" : "Continue to payment"}
</button>
<button
type="button"
disabled={!canContinue || pending !== null}
aria-busy={pending === "reserve"}
onClick={() => placeHold("reserve")}
className="w-full rounded-full border-2 border-[var(--color-primary)] bg-transparent py-3.5 text-sm font-semibold text-[var(--color-primary)] transition hover:bg-[var(--color-primary)] hover:text-[var(--color-on-primary)] disabled:cursor-not-allowed disabled:opacity-50"
>
{pending === "reserve" ? "Saving your hold…" : "Reserve now — pay later"}
</button>
<p className="text-center text-xs text-[var(--color-muted)]">
Pay later keeps your details and hold reference; finish checkout from the next screen
whenever you&apos;re ready.
</p>
</div>
</div>
);
}
function Row({ label, value }: { label: string; value: string }) {
return (
<div className="flex justify-between gap-4 border-b border-[var(--color-border)]/80 pb-2 last:border-0">
<span className="text-[var(--color-muted)]">{label}</span>
<span className="text-right font-medium text-[var(--color-text)]">{value}</span>
</div>
);
}
function formatDate(iso: string) {
try {
return new Intl.DateTimeFormat("en-GB", {
day: "numeric",
month: "short",
year: "numeric",
}).format(new Date(iso + "T12:00:00"));
} catch {
return iso;
}
}