// Hotel Walks — Send a walk request. // Multi-step flow: // 1. Guest + nights → 2. Routing strategy + partners → 3. Review + send // 4. Live broadcast view with timer and (faked) partner responses. (function() { const { useState, useEffect, useMemo } = React; const { Caps, Btn, Field, Input, Stepper, Pill, Toggle, Hr, money } = window.UI; const { HOTELS, HOTEL_BY_ID, PARTNERS, ME } = window.HW_DATA; const STEPS = ["Guest", "Routing", "Review", "Broadcast"]; function Stepper7({ step }) { return (
{STEPS.map((s, i) => { const done = i < step; const active = i === step; return (
{String(i+1).padStart(2,"0")} {s} {done && }
); })}
); } function PageShell({ step, title, sub, children, footer }) { return (
New walk request
{title}
{sub &&

{sub}

}
{children}
{footer && (
{footer}
)}
); } /* ────────────────── STEP 1 ────────────────── */ function StepGuest({ form, setForm, onNext }) { return ( ← Cancel Routing → }>
setForm({...form, guest: e.target.value})} placeholder="Maya Klein" /> setForm({...form, confirmation: e.target.value})} placeholder="WY-882103" />
setForm({...form, party: v})} min={1} max={8} width="100%" /> setForm({...form, nights: v})} min={1} max={14} width="100%" /> setForm({...form, maxDays: v})} min={1} max={form.nights} width="100%" />
setForm({...form, arrival: e.target.value})} type="date" />
Reason
{["Oversold","Mechanical","VIP bump","Guest request"].map(r => ( ))}
setForm({...form, note: e.target.value})} placeholder="Late arrival, ETA 10pm" />
); } /* ────────────────── STEP 2 ────────────────── */ function StepRouting({ form, setForm, onNext, onBack }) { const selectedPartners = form.partners; const togglePartner = (id) => { const cur = new Set(selectedPartners); cur.has(id) ? cur.delete(id) : cur.add(id); setForm({...form, partners: Array.from(cur)}); }; return ( ← Back Review → }> {/* Controls */}
setForm({...form, targetRate: e.target.value.replace(/[^0-9]/g,"")})} placeholder="425" />
{[["preference","By preference"], ["rate","By best rate"]].map(([v, l]) => ( ))}
setForm({...form, timer: v})} min={0} max={180} step={5} prefix="" width="100%" />
Urgent mode
Bypass timers. Broadcast to all partners at once.
setForm({...form, urgent: v})} />
{/* Partners list */}
Partners · {selectedPartners.length} selected
{PARTNERS.map(p => { const hotel = HOTEL_BY_ID[p.id]; const checked = selectedPartners.includes(p.id); return ( ); })}
); } /* ────────────────── STEP 3 ────────────────── */ function StepReview({ form, onBack, onSend }) { const lines = [ ["Guest", form.guest || "—"], ["Confirmation", form.confirmation || "—"], ["Party · nights", `${form.party} · ${form.nights}`], ["Arrival", form.arrival || "Today"], ["Reason", form.reason], ["Max days covered", `${form.maxDays} of ${form.nights}`], ["Target rate", form.targetRate ? money(form.targetRate) : "Open"], ["Strategy", form.strategy === "preference" ? "By partner preference" : "By best rate"], ["Escalation", form.urgent ? "URGENT — broadcast simultaneously" : `${form.timer} min per partner`], ["Partners", form.partners.length + " selected"], ]; return ( ← Back Send walk request }>
Walk envelope
{lines.map(([k, v], i) => (
{k}
{v}
))}
Partners · in order
    {form.partners.map((id, i) => { const h = HOTEL_BY_ID[id]; return (
  1. {String(i+1).padStart(2,"0")} {h.name}
    {form.urgent ? "Now" : `+${i * form.timer}m`}
  2. ); })}
Estimated cost · to {ME.property.name}
$4.00 + 10%
per walked guest
); } /* ────────────────── STEP 4 — Live broadcast ────────────────── */ function StepBroadcast({ form, onDone }) { // Simulate partner responses over time. const [responses, setResponses] = useState( form.partners.map(id => ({ id, state: "pending" })) ); const [elapsed, setElapsed] = useState(0); const acceptedIdx = responses.findIndex(r => r.state === "accepted"); useEffect(() => { const tick = setInterval(() => setElapsed(e => e + 1), 1000); return () => clearInterval(tick); }, []); useEffect(() => { // Fake response timeline based on partner stats const timers = form.partners.map((id, i) => { const p = PARTNERS.find(pp => pp.id === id); const accepts = Math.random() < (p.acceptance); const wait = (form.urgent ? 0 : i * 1.2) + (p.avgMin * 0.4); // seconds return setTimeout(() => { setResponses(prev => prev.map(r => r.id === id ? {...r, state: accepts ? (prev.some(x => x.state === "accepted") ? "passed" : "accepted") : "passed"} : r)); }, wait * 1000); }); return () => timers.forEach(clearTimeout); }, []); const accepted = responses.find(r => r.state === "accepted"); const acceptedHotel = accepted ? HOTEL_BY_ID[accepted.id] : null; return ( ← Back to today Generate walk letter → ) : ( <> { if(confirm("Cancel this walk?")) onDone(); }}>Cancel broadcast Elapsed · {elapsed}s ) }>
Partner responses
{responses.map((r, i) => { const h = HOTEL_BY_ID[r.id]; const isAccepted = r.state === "accepted"; const isPassed = r.state === "passed"; const isPending = r.state === "pending"; return (
{String(i+1).padStart(2,"0")}
{h.name}
{h.area}
{isPending && Waiting…} {isAccepted && Accepted} {isPassed && Passed}
{isAccepted ? money(form.targetRate || h.rate) : isPending ? "—" : "—"}
); })}
{accepted ? (
Accepted
{acceptedHotel.name}
{acceptedHotel.area}

= 3 ? "1" : "1"} dark />
) : (
Live status
Waiting for the first accept.

{form.urgent ? "All partners notified simultaneously. The first to accept wins the rooms." : `If the top partner doesn't respond in ${form.timer} minutes, the next one is offered.`}

r.state==="pending").length)} /> r.state==="passed").length)} />
)}
); } function Stat({ label, value }) { return (
{label}
{value}
); } function Row({ k, v, dark, mono }) { return (
{k}
{v}
); } /* ────────────────── Root ────────────────── */ function SendWalk() { const [step, setStep] = useState(0); const [form, setForm] = useState({ guest: "Maya Klein", confirmation: "WY-882103", party: 2, nights: 1, maxDays: 1, arrival: "2026-05-21", reason: "Oversold", note: "Late arrival, ETA 10pm", targetRate: "425", strategy: "preference", timer: 30, urgent: false, partners: ["1HBB","HOXBK","PNHST","WMBG"], }); if (step === 0) return setStep(1)} />; if (step === 1) return setStep(2)} onBack={() => setStep(0)} />; if (step === 2) return setStep(1)} onSend={() => setStep(3)} />; if (step === 3) return window.HWgo("/app")} />; return null; } const responsiveCSS = ` @media (max-width: 880px) { .form-cols, .route-controls, .review-cols { grid-template-columns: 1fr !important; gap: 32px !important; } .partner-row { grid-template-columns: 24px 30px 1fr !important; gap: 12px !important; } .partner-row > *:nth-child(n+4) { display: none; } } @media (max-width: 720px) { .page-title { font-size: 36px !important; } .send-footer { padding: 14px 18px !important; } .send-footer > * { flex: 1; } .send-footer button, .send-footer a > * { width: 100%; } } `; function WrappedSendWalk() { return ( <> ); } window.Screens = window.Screens || {}; window.Screens.SendWalk = WrappedSendWalk; })();