shuttle and image render updates
This commit is contained in:
parent
c1f3461952
commit
f3e7896169
|
|
@ -10,8 +10,10 @@ import {
|
||||||
guestOrders,
|
guestOrders,
|
||||||
guestPointsHistory,
|
guestPointsHistory,
|
||||||
guestSpaBookings,
|
guestSpaBookings,
|
||||||
|
guestShuttles,
|
||||||
type PointLedgerRow,
|
type PointLedgerRow,
|
||||||
type SpaBookingRow,
|
type SpaBookingRow,
|
||||||
|
type ShuttleRow,
|
||||||
} from "@/lib/guest-hotel-api";
|
} from "@/lib/guest-hotel-api";
|
||||||
|
|
||||||
const orderTabs: { id: OrderCategory | "all"; label: string }[] = [
|
const orderTabs: { id: OrderCategory | "all"; label: string }[] = [
|
||||||
|
|
@ -68,6 +70,7 @@ function ProfileContent() {
|
||||||
const [apiLedger, setApiLedger] = useState<PointLedgerRow[]>([]);
|
const [apiLedger, setApiLedger] = useState<PointLedgerRow[]>([]);
|
||||||
const [apiOrders, setApiOrders] = useState<OrderRecord[]>([]);
|
const [apiOrders, setApiOrders] = useState<OrderRecord[]>([]);
|
||||||
const [appointments, setAppointments] = useState<SpaBookingRow[]>([]);
|
const [appointments, setAppointments] = useState<SpaBookingRow[]>([]);
|
||||||
|
const [apiShuttles, setApiShuttles] = useState<ShuttleRow[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!accessToken || !session) return;
|
if (!accessToken || !session) return;
|
||||||
|
|
@ -80,6 +83,17 @@ function ProfileContent() {
|
||||||
const ph = await guestPointsHistory(pid, accessToken);
|
const ph = await guestPointsHistory(pid, accessToken);
|
||||||
const ord = await guestOrders(pid, accessToken);
|
const ord = await guestOrders(pid, accessToken);
|
||||||
const spa = await guestSpaBookings(pid, accessToken);
|
const spa = await guestSpaBookings(pid, accessToken);
|
||||||
|
let shuttles: ShuttleRow[] = [];
|
||||||
|
if (session.bookingId) {
|
||||||
|
try {
|
||||||
|
const sh = await guestShuttles(pid, session.bookingId, accessToken);
|
||||||
|
shuttles = (sh.data ?? []).sort(
|
||||||
|
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// Ignore if shuttles fail (e.g. no access or 404)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setApiBalance(me.balance);
|
setApiBalance(me.balance);
|
||||||
|
|
@ -105,11 +119,13 @@ function ProfileContent() {
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
setAppointments(spa.data ?? []);
|
setAppointments(spa.data ?? []);
|
||||||
|
setApiShuttles(shuttles);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setApiOrders([]);
|
setApiOrders([]);
|
||||||
setAppointments([]);
|
setAppointments([]);
|
||||||
|
setApiShuttles([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
@ -180,7 +196,7 @@ function ProfileContent() {
|
||||||
{(apiBalance ?? session.points).toLocaleString()}
|
{(apiBalance ?? session.points).toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-sm text-[var(--color-muted)]">
|
<p className="mt-1 text-sm text-[var(--color-muted)]">
|
||||||
{apiBalance != null ? "Live balance" : "Balance unavailable"}
|
{apiBalance != null ? "Balance" : "Balance unavailable"}
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -214,6 +230,39 @@ function ProfileContent() {
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{session.bookingId && (
|
||||||
|
<section className="mt-12">
|
||||||
|
<h2 className="font-heading text-2xl text-[var(--color-text)]">Airport shuttle</h2>
|
||||||
|
{apiShuttles.length === 0 ? (
|
||||||
|
<p className="mt-4 rounded-xl border border-dashed border-[var(--color-border)] bg-[var(--color-surface-muted)] px-4 py-8 text-sm text-[var(--color-muted)]">
|
||||||
|
No airport shuttles requested.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<ul className="mt-4 grid gap-3 md:grid-cols-2">
|
||||||
|
{apiShuttles.map((s) => (
|
||||||
|
<li
|
||||||
|
key={s.id}
|
||||||
|
className="rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] p-5 shadow-sm"
|
||||||
|
>
|
||||||
|
<p className="text-xs font-semibold uppercase tracking-wider text-[var(--color-primary)]">
|
||||||
|
{s.status}
|
||||||
|
</p>
|
||||||
|
<p className="mt-2 font-semibold text-[var(--color-text)]">
|
||||||
|
{s.direction === "AIRPORT_TO_HOTEL" ? "Airport pickup (to hotel)" : s.direction === "HOTEL_TO_AIRPORT" ? "Hotel drop-off (to airport)" : s.direction.replace(/_/g, " ")}
|
||||||
|
</p>
|
||||||
|
<p className="mt-1 text-sm text-[var(--color-muted)]">
|
||||||
|
Requested for: {formatWhen(s.requestedAt)}
|
||||||
|
</p>
|
||||||
|
{s.flightRef && (
|
||||||
|
<p className="mt-1 text-sm text-[var(--color-muted)]">Flight: {s.flightRef}</p>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
<section className="mt-12">
|
<section className="mt-12">
|
||||||
<h2 className="font-heading text-2xl text-[var(--color-text)]">Orders</h2>
|
<h2 className="font-heading text-2xl text-[var(--color-text)]">Orders</h2>
|
||||||
<p className="mt-1 text-sm text-[var(--color-muted)]">
|
<p className="mt-1 text-sm text-[var(--color-muted)]">
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ export function RoomSelectBooking({ selected, onSelect }: Props) {
|
||||||
<>
|
<>
|
||||||
<div className="relative h-14 w-20 shrink-0 overflow-hidden rounded-lg">
|
<div className="relative h-14 w-20 shrink-0 overflow-hidden rounded-lg">
|
||||||
<Image
|
<Image
|
||||||
src={selected.gallery[0]!}
|
src={selected.imageKeys?.[0]! || "/images/shitaye-logo.png"}
|
||||||
alt={selected.name}
|
alt={selected.name}
|
||||||
fill
|
fill
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
|
|
@ -56,7 +56,7 @@ export function RoomSelectBooking({ selected, onSelect }: Props) {
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-[var(--color-muted)]">Choose a room category…</span>
|
<span className="text-[var(--color-muted)]">Choose a room</span>
|
||||||
)}
|
)}
|
||||||
<span className="shrink-0 text-[var(--color-muted)]" aria-hidden>
|
<span className="shrink-0 text-[var(--color-muted)]" aria-hidden>
|
||||||
{open ? "▴" : "▾"}
|
{open ? "▴" : "▾"}
|
||||||
|
|
@ -80,7 +80,7 @@ export function RoomSelectBooking({ selected, onSelect }: Props) {
|
||||||
>
|
>
|
||||||
<div className="relative h-12 w-[4.5rem] shrink-0 overflow-hidden rounded-lg">
|
<div className="relative h-12 w-[4.5rem] shrink-0 overflow-hidden rounded-lg">
|
||||||
<Image
|
<Image
|
||||||
src={room.gallery[0]!}
|
src={room.imageKeys?.[0]! || "/images/shitaye-logo.png"}
|
||||||
alt={room.name}
|
alt={room.name}
|
||||||
fill
|
fill
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
|
|
|
||||||
|
|
@ -284,3 +284,26 @@ export async function guestOrders(
|
||||||
return { data: filtered.sort((a, b) => +new Date(b.createdAt) - +new Date(a.createdAt)) };
|
return { data: filtered.sort((a, b) => +new Date(b.createdAt) - +new Date(a.createdAt)) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ShuttleRow = {
|
||||||
|
id: string;
|
||||||
|
bookingId: string;
|
||||||
|
direction: string;
|
||||||
|
requestedAt: string;
|
||||||
|
flightRef: string | null;
|
||||||
|
notes: string | null;
|
||||||
|
status: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function guestShuttles(
|
||||||
|
propertyId: string,
|
||||||
|
bookingId: string,
|
||||||
|
accessToken: string,
|
||||||
|
): Promise<{ data: ShuttleRow[] }> {
|
||||||
|
return apiFetch(`/properties/${propertyId}/hotel/guest/bookings/${bookingId}/shuttles`, {
|
||||||
|
method: "GET",
|
||||||
|
accessToken,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
export type Room = {
|
export type Room = {
|
||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
imageKeys?: string[];
|
||||||
name: string;
|
name: string;
|
||||||
shortDescription: string;
|
shortDescription: string;
|
||||||
longDescription: string;
|
longDescription: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user