"use client"; import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode, } from "react"; import { DEMO_BOOKING_REFS } from "@/lib/mocks/guestData"; const STORAGE_SESSION = "shitaye_session_v1"; const STORAGE_ORDERS = "shitaye_orders_v1"; export type OrderCategory = "room-service" | "laundry" | "gym" | "spa"; export type OrderRecord = { id: string; category: OrderCategory; title: string; detail: string; totalUsd: number; placedAt: string; status: "pending" | "confirmed" | "completed"; }; export type MemberSession = { kind: "member"; email: string; displayName: string; points: number; tier: "Gold" | "Silver"; /** How they signed in — for display only */ authMethod: "otp" | "password" | "google" | "apple" | "facebook"; }; export type BookingRefSession = { kind: "bookingRef"; bookingRef: string; guestName: string; roomLabel: string; checkOut: string; }; export type GuestSession = MemberSession | BookingRefSession; type AuthContextValue = { session: GuestSession | null; orders: OrderRecord[]; isHydrated: boolean; /** Demo OTP is always 123456 */ requestOtp: (email: string) => Promise<{ ok: boolean; message: string }>; verifyOtp: (email: string, code: string) => Promise<{ ok: boolean; message: string }>; loginPassword: (email: string, password: string) => Promise<{ ok: boolean; message: string }>; loginSocial: (provider: "google" | "apple" | "facebook") => void; loginBookingRef: (ref: string) => { ok: boolean; message: string }; logout: () => void; addOrder: (o: Omit & { status?: OrderRecord["status"] }) => void; awardPoints: (points: number) => void; }; const AuthContext = createContext(null); function loadOrders(): OrderRecord[] { if (typeof window === "undefined") return []; try { const raw = localStorage.getItem(STORAGE_ORDERS); if (!raw) return seedOrders(); const parsed = JSON.parse(raw) as OrderRecord[]; return Array.isArray(parsed) ? parsed : seedOrders(); } catch { return seedOrders(); } } function seedOrders(): OrderRecord[] { return [ { id: "seed-rs-1", category: "room-service", title: "Room service · American breakfast ×2", detail: "Delivered 07:15 · Room charge", totalUsd: 36, placedAt: new Date(Date.now() - 86400000 * 2).toISOString(), status: "completed", }, { id: "seed-l-1", category: "laundry", title: "Laundry · Express + 3 shirts", detail: "Returned same evening", totalUsd: 27, placedAt: new Date(Date.now() - 86400000).toISOString(), status: "completed", }, { id: "seed-sp-1", category: "spa", title: "Spa · Signature Swedish 60 min", detail: "Apr 4 · 15:00", totalUsd: 85, placedAt: new Date(Date.now() - 86400000 * 3).toISOString(), status: "confirmed", }, ]; } function loadSession(): GuestSession | null { if (typeof window === "undefined") return null; try { const raw = localStorage.getItem(STORAGE_SESSION); if (!raw) return null; return JSON.parse(raw) as GuestSession; } catch { return null; } } function persistSession(s: GuestSession | null) { if (typeof window === "undefined") return; if (s) localStorage.setItem(STORAGE_SESSION, JSON.stringify(s)); else localStorage.removeItem(STORAGE_SESSION); } function persistOrders(orders: OrderRecord[]) { if (typeof window === "undefined") return; localStorage.setItem(STORAGE_ORDERS, JSON.stringify(orders)); } export function AuthProvider({ children }: { children: ReactNode }) { const [session, setSession] = useState(null); const [orders, setOrders] = useState([]); const [isHydrated, setIsHydrated] = useState(false); useEffect(() => { setSession(loadSession()); setOrders(loadOrders()); setIsHydrated(true); }, []); const requestOtp = useCallback(async (email: string) => { if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { return { ok: false, message: "Enter a valid email address." }; } return { ok: true, message: "Demo code sent. Use OTP 123456 to continue." }; }, []); const verifyOtp = useCallback(async (email: string, code: string) => { const trimmed = code.replace(/\s/g, ""); if (trimmed !== "123456") { return { ok: false, message: "Invalid code. Demo OTP is 123456." }; } const local = email.split("@")[0] ?? "Guest"; const name = local.charAt(0).toUpperCase() + local.slice(1); const next: MemberSession = { kind: "member", email: email.toLowerCase(), displayName: name, points: 2400, tier: "Gold", authMethod: "otp", }; setSession(next); persistSession(next); return { ok: true, message: "Signed in." }; }, []); const loginPassword = useCallback(async (email: string, password: string) => { if (!email || !password) { return { ok: false, message: "Email and password required." }; } if (password !== "shitaye" && password !== "demo123") { return { ok: false, message: "Incorrect password. Try demo password: shitaye", }; } const local = email.split("@")[0] ?? "Guest"; const name = local.charAt(0).toUpperCase() + local.slice(1); const next: MemberSession = { kind: "member", email: email.toLowerCase(), displayName: name, points: 2400, tier: "Gold", authMethod: "password", }; setSession(next); persistSession(next); return { ok: true, message: "Signed in." }; }, []); const loginSocial = useCallback((provider: "google" | "apple" | "facebook") => { const names: Record = { google: "Google Guest", apple: "Apple Guest", facebook: "Facebook Guest", }; const next: MemberSession = { kind: "member", email: `guest.${provider}@shitaye.demo`, displayName: names[provider], points: 2100, tier: "Silver", authMethod: provider, }; setSession(next); persistSession(next); }, []); const loginBookingRef = useCallback((ref: string) => { const key = ref.trim().toUpperCase(); const row = DEMO_BOOKING_REFS[key]; if (!row) { return { ok: false, message: "Reference not found. Try SHITAYE-2026-DEMO or GUEST-1234.", }; } const next: BookingRefSession = { kind: "bookingRef", bookingRef: key, guestName: row.guestName, roomLabel: row.room, checkOut: row.checkOut, }; setSession(next); persistSession(next); return { ok: true, message: "Linked to your stay." }; }, []); const logout = useCallback(() => { setSession(null); persistSession(null); }, []); const addOrder = useCallback( ( o: Omit & { status?: OrderRecord["status"]; }, ) => { const rec: OrderRecord = { ...o, id: `ord-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, placedAt: new Date().toISOString(), status: o.status ?? "pending", }; setOrders((prev) => { const next = [rec, ...prev]; persistOrders(next); return next; }); if (session?.kind === "member") { const bonus = Math.min(150, Math.round(o.totalUsd * 2)); setSession((s) => { if (!s || s.kind !== "member") return s; const u = { ...s, points: s.points + bonus }; persistSession(u); return u; }); } }, [session], ); const awardPoints = useCallback((points: number) => { setSession((s) => { if (!s || s.kind !== "member") return s; const u = { ...s, points: s.points + points }; persistSession(u); return u; }); }, []); const value = useMemo( () => ({ session, orders, isHydrated, requestOtp, verifyOtp, loginPassword, loginSocial, loginBookingRef, logout, addOrder, awardPoints, }), [ session, orders, isHydrated, requestOtp, verifyOtp, loginPassword, loginSocial, loginBookingRef, logout, addOrder, awardPoints, ], ); return {children}; } export function useAuth() { const ctx = useContext(AuthContext); if (!ctx) throw new Error("useAuth must be used within AuthProvider"); return ctx; }