120 lines
4.4 KiB
TypeScript
120 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { RoomPrice } from "@/components/RoomPrice";
|
|
import type { Room } from "@/types/room";
|
|
import { useBooking } from "@/context/BookingContext";
|
|
|
|
type Props = {
|
|
selected: Room | null;
|
|
onSelect: (roomId: string) => void;
|
|
};
|
|
|
|
export function RoomSelectBooking({ selected, onSelect }: Props) {
|
|
const { rooms } = useBooking();
|
|
const [open, setOpen] = useState(false);
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
function onDoc(e: MouseEvent) {
|
|
if (!ref.current?.contains(e.target as Node)) setOpen(false);
|
|
}
|
|
if (open) document.addEventListener("mousedown", onDoc);
|
|
return () => document.removeEventListener("mousedown", onDoc);
|
|
}, [open]);
|
|
|
|
return (
|
|
<div className="relative" ref={ref}>
|
|
<span className="mb-2 block text-sm font-medium text-[var(--color-text)]">
|
|
Select room
|
|
</span>
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen((o) => !o)}
|
|
className="flex w-full items-center gap-3 rounded-2xl border border-(--color-border) bg-[var(--color-surface)] p-3 text-left shadow-sm transition hover:border-[var(--color-primary)]/40 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary)]"
|
|
aria-expanded={open}
|
|
aria-haspopup="listbox"
|
|
>
|
|
{selected ? (
|
|
<>
|
|
<div className="relative h-14 w-20 shrink-0 overflow-hidden rounded-lg">
|
|
<Image
|
|
src={selected.gallery[0] || "/images/shitaye-logo.png"}
|
|
alt={selected.name}
|
|
fill
|
|
className="object-cover"
|
|
sizes="80px"
|
|
/>
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="font-semibold text-[var(--color-text)]">{selected.name}</p>
|
|
<p className="text-xs text-[var(--color-muted)]">
|
|
From <RoomPrice room={selected} maximumFractionDigits={0} /> / night
|
|
</p>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<span className="text-[var(--color-muted)]">Choose a room</span>
|
|
)}
|
|
<span className="shrink-0 text-[var(--color-muted)]" aria-hidden>
|
|
{open ? "▴" : "▾"}
|
|
</span>
|
|
</button>
|
|
|
|
{open ? (
|
|
<ul
|
|
className="absolute left-0 right-0 top-full z-40 mt-2 max-h-[min(70vh,400px)] overflow-y-auto rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] py-2 shadow-xl"
|
|
role="listbox"
|
|
>
|
|
{rooms.map((room) => (
|
|
<li key={room.id} role="option" aria-selected={selected?.id === room.id}>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
onSelect(room.id);
|
|
setOpen(false);
|
|
}}
|
|
className="flex w-full items-center gap-3 px-3 py-2.5 text-left transition hover:bg-[var(--color-surface-muted)]"
|
|
>
|
|
<div className="relative h-12 w-[4.5rem] shrink-0 overflow-hidden rounded-lg">
|
|
<Image
|
|
src={room.gallery[0] || "/images/shitaye-logo.png"}
|
|
alt={room.name}
|
|
fill
|
|
className="object-cover"
|
|
sizes="72px"
|
|
/>
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="text-sm font-semibold text-[var(--color-text)]">{room.name}</p>
|
|
<p className="text-xs text-[var(--color-muted)]">
|
|
<RoomPrice room={room} maximumFractionDigits={0} />
|
|
/night · max {room.maxGuests} guests
|
|
</p>
|
|
</div>
|
|
</button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : null}
|
|
|
|
{selected ? (
|
|
<Link
|
|
href={
|
|
/^[0-9a-f-]{36}$/i.test(selected.id)
|
|
? `/booking?room=${encodeURIComponent(selected.id)}`
|
|
: `/rooms/${selected.slug}`
|
|
}
|
|
className="mt-2 inline-block text-sm font-semibold text-[var(--color-primary)] hover:underline"
|
|
>
|
|
{/^[0-9a-f-]{36}$/i.test(selected.id)
|
|
? "Back to room selection"
|
|
: "View full room details & amenities"}
|
|
</Link>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|