"use client";
import Link from "next/link";
import { useEffect, useMemo, useState } from "react";
import { RequireAuth } from "@/components/RequireAuth";
import { useAuth } from "@/context/AuthContext";
import type { OrderCategory, OrderRecord } from "@/context/AuthContext";
import {
guestMe,
guestOrders,
guestPointsHistory,
guestSpaBookings,
guestShuttles,
type PointLedgerRow,
type SpaBookingRow,
type ShuttleRow,
} from "@/lib/guest-hotel-api";
const orderTabs: { id: OrderCategory | "all"; label: string }[] = [
{ id: "all", label: "All" },
{ id: "room-service", label: "Room service" },
{ id: "laundry", label: "Laundry" },
{ id: "gym", label: "Gym" },
{ id: "spa", label: "Spa" },
];
function formatWhen(iso: string) {
try {
return new Date(iso).toLocaleString(undefined, {
dateStyle: "medium",
timeStyle: "short",
});
} catch {
return iso;
}
}
function OrderRow({ o }: { o: OrderRecord }) {
return (
{o.title}
{o.detail}
{formatWhen(o.placedAt)}
{o.status}
${o.totalUsd.toFixed(0)}
);
}
export function ProfilePageClient() {
return (
);
}
function ProfileContent() {
const { session, logout, accessToken } = useAuth();
const [orderFilter, setOrderFilter] = useState("all");
const [apiBalance, setApiBalance] = useState(null);
const [apiLedger, setApiLedger] = useState([]);
const [apiOrders, setApiOrders] = useState([]);
const [appointments, setAppointments] = useState([]);
const [apiShuttles, setApiShuttles] = useState([]);
useEffect(() => {
if (!accessToken || !session) return;
const pid = session.propertyId;
if (!pid) return;
let cancelled = false;
(async () => {
try {
const me = await guestMe(pid, accessToken);
const ph = await guestPointsHistory(pid, accessToken);
const ord = await guestOrders(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) {
setApiBalance(me.balance);
setApiLedger(ph.data ?? []);
setApiOrders(
(ord.data ?? []).map((o) => ({
id: o.id,
category: o.type,
title:
o.type === "room-service"
? "Room Service Order"
: o.type === "laundry"
? "Laundry Request"
: o.type === "gym"
? "Gym Booking"
: "Spa Booking",
detail: o.detail,
totalUsd: Number(o.total ?? 0),
placedAt: o.createdAt,
status: (["pending", "confirmed", "completed"].includes(o.status.toLowerCase())
? o.status.toLowerCase()
: "pending") as OrderRecord["status"],
})),
);
setAppointments(spa.data ?? []);
setApiShuttles(shuttles);
}
} catch {
if (!cancelled) {
setApiOrders([]);
setAppointments([]);
setApiShuttles([]);
}
}
})();
return () => {
cancelled = true;
};
}, [accessToken, session]);
const filteredOrders = useMemo(() => {
if (orderFilter === "all") return apiOrders;
return apiOrders.filter((o) => o.category === orderFilter);
}, [apiOrders, orderFilter]);
if (!session) {
return null;
}
return (
Hello, {session.displayName}
{session.email}
{session.bookingCode ? (
<>
{" ยท "}
Booking code{" "}
{session.bookingCode}
>
) : null}
Guest hub
Rewards points
<>
{(apiBalance ?? session.points).toLocaleString()}
{apiBalance != null ? "Balance" : "Balance unavailable"}
>
Booked appointments
{appointments.length === 0 ? (
No gym/spa bookings found.
) : (
)}
{session.bookingId && (
Airport shuttle
{apiShuttles.length === 0 ? (
No airport shuttles requested.
) : (
{apiShuttles.map((s) => (
-
{s.status}
{s.direction === "AIRPORT_TO_HOTEL" ? "Airport pickup (to hotel)" : s.direction === "HOTEL_TO_AIRPORT" ? "Hotel drop-off (to airport)" : s.direction.replace(/_/g, " ")}
Requested for: {formatWhen(s.requestedAt)}
{s.flightRef && (
Flight: {s.flightRef}
)}
))}
)}
)}
Orders
Room service, laundry, gym, and spa
{orderTabs.map((t) => (
))}
{filteredOrders.length === 0 ? (
No orders in this category yet.
) : (
{filteredOrders.map((o) => (
))}
)}
Rewards history
{apiLedger.length > 0
? apiLedger.map((r) => (
-
{r.reason.replace(/_/g, " ")}
{formatWhen(r.createdAt)}
= 0 ? "badge-mustard" : "rounded-full bg-red-100 px-3 py-1 text-xs font-semibold text-red-800"
}
>
{r.delta >= 0 ? "+" : ""}
{r.delta} pts
))
: null}
{apiLedger.length === 0 ? (
No rewards history returned yet.
) : null}
);
}