// Hotels (Duffel Stays) search panel — additive. Renders inside the existing glass search // panel when mode === "hotels". Self-contained: search → results → booking overlay, so the // flight state machine in app.jsx is never touched. Accent: --sky. const HotelSearchPanel = () => { const [city, setCity] = useState(DESTINATIONS[0]); const [cityOpen, setCityOpen] = useState(false); const [checkIn, setCheckIn] = useState(""); const [checkOut, setCheckOut] = useState(""); const [rooms, setRooms] = useState(1); const [adults, setAdults] = useState(2); const [loading, setLoading] = useState(false); const [results, setResults] = useState(null); // null | [] | [stay] const [booking, setBooking] = useState(null); // selected stay for booking overlay const cityChoices = useMemo(() => { // merge featured destinations + airport cities into one pickable list const seen = new Set(); const out = []; for (const d of DESTINATIONS) { if (!seen.has(d.code)) { seen.add(d.code); out.push({ code: d.code, city: d.city, country: d.country }); } } for (const a of AIRPORTS) { if (!seen.has(a.code)) { seen.add(a.code); out.push({ code: a.code, city: a.city, country: a.name }); } } return out; }, []); async function runSearch() { setLoading(true); setResults(null); const list = await EHT_API.searchStays({ cityCode: city.code, cityLabel: city.city, checkInDate: checkIn || undefined, checkOutDate: checkOut || undefined, rooms, adults, }); setLoading(false); setResults(list); } const dropdownOpen = cityOpen; return (
{/* Field row — white row with sky accent, matches flights style */}
} accent="var(--sky)" value={`${city.city}`} sub={city.country} onClick={() => setCityOpen(o => !o)} open={cityOpen}> {cityOpen && ( { setCity(c); setCityOpen(false); }} placeholder="Search a city" /> )} 1 ? "s" : ""} · ${rooms} room${rooms > 1 ? "s" : ""}`} steppers={[ { label: "Adults", value: adults, set: setAdults, min: 1, max: 9 }, { label: "Rooms", value: rooms, set: setRooms, min: 1, max: 6 }, ]} />
{/* ── Push-content spacer (animates when dropdown is open) ── */}
{/* Quick filter chips — light theme */}
{["Free cancellation", "Breakfast included", "Pool", "5-star", "Near centre"].map((c) => ( ))}
{(loading || results) && ( setResults(null)} loading={loading} count={results?.length || 0} accent="var(--sky)"> {results?.map((s) => ( setBooking(s)} /> ))} {results && results.length === 0 && } )} {booking && ( setBooking(null)} onConfirm={async (guest) => { // Real Duffel Stays needs search→rates→quote→booking; without a live quote we confirm a demo booking. return { reference: "EHT-STAY-" + Math.random().toString(36).slice(2, 8).toUpperCase(), _mock: booking._mock !== false }; }} /> )}
); }; window.HotelSearchPanel = HotelSearchPanel;