/* ============================================================================ MOWDEW LIVE DEMO — "Approve before work" (the 5th synchronized flow) Homeowner snaps a photo + request → owner turns it into a PRICED quote → homeowner APPROVES the exact price → it lands on the pro's route. The photo and price carry across all three phones via the shared `world.req`. null → requested → quoted → scheduled (+ declined) World/actions/tracker/hints live in Mowdew Live Demo.html; the per-phone UI lives here and is mounted from the demo phone shells. ========================================================================= */ /* the canned quote the owner sends (keeps the demo one-tap) */ const DEMO_QUOTE = { lines: [{ label: 'Waterline tile descale', cents: 14000 }, { label: 'Acid spot-treat', cents: 4000 }], total: 18000, note: 'I can knock this out on your next visit — about 90 min, all-in.', }; const DEMO_REQ = { photo: 'pool-before', desc: 'The waterline tile looks scaled — white crust along the edge.' }; /* ─── HOMEOWNER · the request lifecycle card (sits on the visit feed) ── */ function HomeRequestCard({ world, act, openReq }) { const s = world.req; // idle — the invitation to request work if (!s || s === 'declined') { return (
Something need fixing?
{s === 'declined' ? 'Declined — nothing was scheduled or billed.' : 'Snap it · Diego quotes · you approve the price'}
); } return (
Marta Lawn & Pool
{s === 'requested' && SENT} {s === 'quoted' && QUOTE IN} {s === 'scheduled' && APPROVED}
{/* the photo + ask, carried through */}
"{DEMO_REQ.desc}"
{s === 'requested' && (
Diego's reviewing your photo — a quote is on the way.
)} {(s === 'quoted' || s === 'scheduled') && (
Diego's quote
{DEMO_QUOTE.lines.map((l, i) => (
{l.label}{fmtMoney(l.cents)}
))}
Total
{DEMO_QUOTE.note &&
"{DEMO_QUOTE.note}"
}
)} {s === 'quoted' && (
)} {s === 'quoted' &&
You approve the exact price before anyone shows up.
} {s === 'scheduled' && (
Approved at {fmtMoney(DEMO_QUOTE.total)} — added to your next visit. It only bills once it's done.
)}
); } /* ─── HOMEOWNER · compose sheet (photo + description) ───────────────── */ function HomeRequestSheet({ open, onClose, act }) { const [photo, setPhoto] = React.useState(false); const [desc, setDesc] = React.useState('The waterline tile looks scaled'); const send = () => { onClose && onClose(); act.homeRequestWork(); setTimeout(() => { setPhoto(false); }, 300); }; const quick = ['The waterline tile looks scaled', 'Pump sounds rough', 'Water looks cloudy']; return (
Goes to Diego with your photo. He sends a priced quote — nothing happens until you approve it.
88 Crestline Ave · pool
Diego Marta · your pro
{quick.map(q => )}
); } /* ─── OWNER · request → make-a-quote card (sits in the Inbox tab) ────── */ function OwnerRequestCard({ world, act }) { const s = world.req; if (!s || s === 'declined') { if (s === 'declined') { return (
Requests
Emma declined your quote — nothing was scheduled.
); } return null; } return (
Requests
Emma Walsh · Crestline pool
Customer ask · just now
{s === 'requested' && NEEDS YOU} {s === 'quoted' && WAITING ON EMMA} {s === 'scheduled' && APPROVED}
PHOTO · FROM EMMA
"{DEMO_REQ.desc}"
{(s === 'quoted' || s === 'scheduled') && (
Your quote
{DEMO_QUOTE.lines.map((l, i) => (
{l.label}{fmtMoney(l.cents)}
))}
Total Emma approves
)}
{s === 'requested' && } {s === 'quoted' && } {s === 'scheduled' && }
); } /* ─── PRO · the approved add-on, shown on the Crestline stop ─────────── */ function ProAddedWork({ world }) { if (world.req !== 'scheduled') return null; return (
Added: tile descale + acid treat
Customer-approved · price set by the office
); } Object.assign(window, { DEMO_QUOTE, DEMO_REQ, HomeRequestCard, HomeRequestSheet, OwnerRequestCard, ProAddedWork });