"use client"; import Link from "next/link"; import { useState, useEffect, useCallback } from "react"; import { RequireAuth } from "@/components/RequireAuth"; import { useAuth } from "@/context/AuthContext"; import { guestPlaceLaundry } from "@/lib/guest-hotel-api"; import { formatEtb } from "@/lib/format-etb"; import { useGuestActiveBooking } from "@/lib/useGuestActiveBooking"; import { laundryItems, type LaundryCartItem, SAME_DAY_SURCHARGE } from "@/lib/data/laundryCatalog"; export function LaundryClient() { return ( ); } function LaundryInner() { const { accessToken } = useAuth(); const { bookingId, loading: bookingLoading, propertyId } = useGuestActiveBooking(); // Form states const [cart, setCart] = useState>({}); const [sameDay, setSameDay] = useState(false); const [pickupAt, setPickupAt] = useState(""); const [deliverAt, setDeliverAt] = useState(""); const [notes, setNotes] = useState(""); const [sent, setSent] = useState(false); const [submitErr, setSubmitErr] = useState(null); const [submitting, setSubmitting] = useState(false); const canUseApi = !!(propertyId && accessToken && bookingId); // Compute total const total = useCallback(() => { let sum = 0; for (const [label, qty] of Object.entries(cart)) { if (qty > 0) { const price = laundryItems.find(item => item.label.toLowerCase() === label.toLowerCase())?.price || 0; sum += price * qty; } } if (sameDay) sum += SAME_DAY_SURCHARGE; return sum; }, [cart, sameDay]); const [displayTotal, setDisplayTotal] = useState(0); useEffect(() => { setDisplayTotal(total()); }, [total]); // Build items Json const buildItems = (): LaundryCartItem[] => Object.entries(cart) .filter(([, qty]) => qty > 0) .map(([label, quantity]) => ({ label: laundryItems.find(i => i.label.toLowerCase() === label.toLowerCase())?.label || label, quantity })); async function submit() { if (!canUseApi || buildItems().length === 0) { setSubmitErr("Please select at least one item."); return; } setSubmitErr(null); setSubmitting(true); try { await guestPlaceLaundry(propertyId!, accessToken!, { bookingId: bookingId!, items: buildItems(), sameDay, total: displayTotal, // currency: "ETB", notes: notes.trim() || undefined, pickupAt: pickupAt || undefined, deliverAt: deliverAt || undefined, }); setSubmitting(false); setCart({}); setSameDay(false); setPickupAt(""); setDeliverAt(""); setNotes(""); setSent(true); } catch (e) { setSubmitErr(e instanceof Error ? e.message : "Could not submit laundry request"); setSubmitting(false); } } const needBooking = !bookingLoading && !bookingId; const hasItems = buildItems().length > 0; const updateQty = (label: string, delta: number) => { setCart(prev => { const current = prev[label] || 0; const newQty = Math.max(0, current + delta); const newCart = { ...prev }; if (newQty === 0) delete newCart[label]; else newCart[label] = newQty; return newCart; }); }; return ( Home / Guest hub / Laundry Laundry service Submit a laundry request attached to your active booking. View profile → {needBooking ? ( Sign in with a booking code or use a reservation to sync laundry with the hotel. ) : null} {submitErr ? ( {submitErr} ) : null} {sent ? ( Laundry request submitted successfully. ) : null} {!sent && ( {/* Items Selection */} Select items {laundryItems.map((item) => { const qty = cart[item.label.toLowerCase()] || 0; return ( {item.label} {formatEtb(item.price)} / each updateQty(item.label.toLowerCase(), -1)} disabled={qty === 0} className="w-10 h-10 rounded-lg border bg-[var(--color-surface)] text-[var(--color-text)] hover:bg-[var(--color-accent-soft)] disabled:opacity-50 flex items-center justify-center" > − {qty} updateQty(item.label.toLowerCase(), 1)} className="w-10 h-10 rounded-lg border bg-[var(--color-accent)] text-white hover:bg-[var(--color-accent-dark)] flex items-center justify-center" > + ); })} {/* Summary & Form */} {formatEtb(displayTotal)} {sameDay && (incl. same-day)} setSameDay(e.target.checked)} className="w-4 h-4 rounded" /> Express same-day (+{formatEtb(SAME_DAY_SURCHARGE)}) Pickup setPickupAt(e.target.value)} className="w-full rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-muted)] px-3 py-2 text-sm" /> Delivery setDeliverAt(e.target.value)} className="w-full rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-muted)] px-3 py-2 text-sm" /> setNotes(e.target.value)} rows={3} placeholder="Special instructions (e.g., no starch, delicate)..." className="w-full rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-muted)] px-3 py-2 text-sm" /> {submitting ? "Submitting..." : `Place laundry order (${formatEtb(displayTotal)})`} )} ); }
Submit a laundry request attached to your active booking.