// backlot-form.jsx — the actual request form // ── BACKEND ENDPOINT ──────────────────────────────────────────────────────── // Point this at your submit.php. Leave "" to run in demo mode (no real send). // • Same folder as the page: "submit.php" // • Different host/subdomain: "https://yourdomain.com/backlot/submit.php" const SUBMIT_ENDPOINT = (typeof window !== "undefined" && window.BACKLOT_CFG && window.BACKLOT_CFG.endpoint) || "submit.php"; // ───────────────────────────────────────────────────────────────────────────── const TOYOTA_MODELS = [ "4Runner", "Avalon", "Camry", "Corolla", "Corolla Cross", "Crown", "GR86", "GR Corolla", "GR Supra", "Highlander", "Grand Highlander", "Land Cruiser", "Mirai", "Prius", "Prius Prime", "RAV4", "RAV4 Prime", "Sequoia", "Sienna", "Tacoma", "Tundra", "Venza", "bZ4X", ]; const URGENCY_OPTIONS = [ { v: "rush", label: "Rush — needed in 48 hrs", note: "Air freight, premium" }, { v: "standard", label: "Standard — needed this week", note: "Best availability" }, { v: "scheduled", label: "Scheduled — within 2 weeks", note: "Lowest cost" }, ]; function RequestForm({ onSubmit, accent = "#EB0A1E" }) { const [partInput, setPartInput] = React.useState(""); const [parts, setParts] = React.useState([]); // start empty — a seeded demo part // was riding along on every real request const [year, setYear] = React.useState(""); const [model, setModel] = React.useState(""); const [trim, setTrim] = React.useState(""); const [shop, setShop] = React.useState(""); const [contact, setContact] = React.useState(""); const [email, setEmail] = React.useState(""); const [phone, setPhone] = React.useState(""); const [urgency, setUrgency] = React.useState("standard"); const [address, setAddress] = React.useState(""); const [notes, setNotes] = React.useState(""); const [submitting, setSubmitting] = React.useState(false); const [error, setError] = React.useState(""); const [hp, setHp] = React.useState(""); // honeypot const partRef = React.useRef(null); function addPart(){ const v = partInput.trim().toUpperCase(); if(!v) return; if(parts.includes(v)) { setPartInput(""); return; } setParts(p => [...p, v]); setPartInput(""); } function removePart(i){ setParts(p => p.filter((_, idx) => idx !== i)); } function handleKey(e){ if(e.key === "Enter" || e.key === ",") { e.preventDefault(); addPart(); } if(e.key === "Backspace" && !partInput && parts.length) { setParts(p => p.slice(0, -1)); } } async function submit(e){ e.preventDefault(); if(submitting) return; // guard against a double submit setError(""); // Fold in a part number that was typed but never committed with Enter or a // comma. Clicking Submit straight from the input used to silently drop it. const pending = partInput.trim().toUpperCase(); const allParts = pending && !parts.includes(pending) ? [...parts, pending] : parts; if(!allParts.length){ setError("Add at least one Toyota OEM part number."); return; } if(pending){ setParts(allParts); setPartInput(""); } setSubmitting(true); // Trim everything — a trailing space on the email made the server reject the // whole request with "A valid email is required." const payload = { parts: allParts, year, model, urgency, trim: trim.trim(), shop: shop.trim(), contact: contact.trim(), email: email.trim(), phone: phone.trim(), address: address.trim(), notes: notes.trim(), company_website: hp, }; // Demo mode: no endpoint configured — just show the receipt. if(!SUBMIT_ENDPOINT){ setTimeout(() => { setSubmitting(false); onSubmit && onSubmit(payload); }, 900); return; } try { const res = await fetch(SUBMIT_ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const json = await res.json().catch(() => ({})); if(!res.ok || !json.ok){ throw new Error(json.error || ("Server returned " + res.status)); } setSubmitting(false); onSubmit && onSubmit({ ...payload, ref: json.ref }); } catch(err) { setSubmitting(false); // This is read by collision shops, not developers — never surface setup // instructions here, and always give them a way to reach a human. setError( /Failed to fetch|NetworkError|Load failed/i.test(err.message) ? "We couldn't reach our server, so this request was NOT submitted. Please call 210-870-1814 and we'll take it over the phone." : err.message + " — if this keeps happening, call 210-870-1814." ); } } const valid = (parts.length > 0 || partInput.trim()) && shop.trim() && contact.trim() && email.trim(); const years = Array.from({length: 22}, (_, i) => 2026 - i); return (
{/* Parts */}
partRef.current && partRef.current.focus()} style={{ background:"#0c0c0e", border:"1px solid var(--line-2)", padding:"10px 12px", display:"flex", flexWrap:"wrap", gap:8, minHeight:60, cursor:"text" }} > {parts.map((p, i) => ( {p} { e.stopPropagation(); removePart(i); }}>× ))} setPartInput(e.target.value.toUpperCase())} onKeyDown={handleKey} onBlur={addPart} placeholder={parts.length ? "" : "e.g. 52119-06961"} className="mono" style={{ flex:1, minWidth:160, background:"transparent", border:"none", outline:"none", color:"var(--ink)", padding:"4px 0", fontSize:14 }} />
Press Enter or , to add multiple parts. Tap a chip's × to remove.
{/* Vehicle */}
setTrim(e.target.value)} style={{fontFamily:"Manrope, sans-serif"}}/>
{/* Shop info */}
setShop(e.target.value)} required style={{fontFamily:"Manrope, sans-serif"}}/> setContact(e.target.value)} required style={{fontFamily:"Manrope, sans-serif"}}/>
setEmail(e.target.value)} required style={{fontFamily:"Manrope, sans-serif"}}/> setPhone(e.target.value)} style={{fontFamily:"Manrope, sans-serif"}}/>
{/* Urgency */}
{URGENCY_OPTIONS.map(o => { const sel = urgency === o.v; return ( ); })}
{/* Ship address */}