From d420c49818209fcb440248fbeba252a71f170a07 Mon Sep 17 00:00:00 2001 From: justin Date: Tue, 2 Jun 2026 12:46:33 -0500 Subject: [PATCH] intake: prefill order form from ?dot= campaign CTA links Campaign CTA buttons link to /order/?dot=1234567. Add a fast local-only GET /api/v1/dot/census endpoint (vs the heavy 12s live /dot/lookup) and a ?dot= branch in the Wizard that seeds intake_data from the carrier's cached FMCSA census record (name, email, base state, city/street/zip, power units). The existing StateTrucking step already prefills its inputs from intake_data, so the form now shows up pre-populated. Best-effort: only fills empty fields, never blocks the form, never overwrites visitor input. --- api/src/routes/dot-lookup.ts | 46 +++++++++++++++++++++++++ site/src/components/intake/Wizard.astro | 39 +++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/api/src/routes/dot-lookup.ts b/api/src/routes/dot-lookup.ts index 0b35e03..55a34db 100644 --- a/api/src/routes/dot-lookup.ts +++ b/api/src/routes/dot-lookup.ts @@ -936,4 +936,50 @@ router.get("/api/v1/dot/state-requirements", async (req, res) => { } }); +// ── Fast census lookup (local DB only, for form prefill) ───────────── +// Unlike /api/v1/dot/lookup (which does live FMCSA + compliance analysis and +// can take up to 12s), this returns ONLY the locally-cached census record so +// campaign CTA links like /order/ca-mcp-carb?dot=1234567 can instantly prefill +// the intake form. Keys mirror the StateTrucking/DOT intake-step field map. +router.get("/api/v1/dot/census", async (req, res) => { + const rawDot = (req.query.dot as string || "").trim().replace(/\D/g, ""); + if (!rawDot) { + res.status(400).json({ error: "Provide a DOT number." }); + return; + } + try { + const { rows } = await pool.query( + `SELECT dot_number, legal_name, dba_name, carrier_operation, + authorized_for_hire, email_address, telephone, + phy_street, phy_city, phy_state, phy_zip, + nbr_power_unit, driver_total + FROM fmcsa_carriers WHERE dot_number = $1`, + [rawDot], + ); + const c = rows[0]; + if (!c) { + res.status(404).json({ error: `DOT# ${rawDot} not found.` }); + return; + } + res.json({ + dot_number: c.dot_number, + legal_name: c.legal_name || null, + dba_name: c.dba_name || null, + email: c.email_address || null, + telephone: c.telephone || null, + phy_street: c.phy_street || null, + phy_city: c.phy_city || null, + phy_state: c.phy_state || null, + phy_zip: c.phy_zip || null, + power_units: c.nbr_power_unit ?? null, + drivers: c.driver_total ?? null, + for_hire: c.authorized_for_hire || false, + carrier_operation: c.carrier_operation || null, + }); + } catch (err) { + console.error("[dot-census] Error:", err); + res.status(500).json({ error: "Census lookup failed." }); + } +}); + export default router; diff --git a/site/src/components/intake/Wizard.astro b/site/src/components/intake/Wizard.astro index 1c68470..0698c18 100644 --- a/site/src/components/intake/Wizard.astro +++ b/site/src/components/intake/Wizard.astro @@ -347,6 +347,45 @@ const STEP_LABELS: Record = { const token = params.get("token"); const frn = params.get("frn"); const orderParam = params.get("order"); + + // ── Pre-fill from ?dot=NNNN (cold campaign CTA links) ── + // Campaign emails link to /order/?dot=1234567. Pull the carrier's + // cached FMCSA census record and seed intake_data so the trucking step + // shows up pre-populated. Only fills empty fields; never overwrites a + // value the visitor already entered (loadState merge below preserves it). + const dotParam = (params.get("dot") || "").replace(/\D/g, ""); + if (dotParam && !token && !orderParam) { + const API = (window as any).__PW_API || ""; + try { + const r = await fetch(`${API}/api/v1/dot/census?dot=${dotParam}`); + if (r.ok) { + const c = await r.json(); + const state = loadState(); + const d = state.intake_data || {}; + const seeded = { + dot_number: d.dot_number || c.dot_number || dotParam, + legal_name: d.legal_name || c.legal_name || "", + entity_name: d.entity_name || c.legal_name || "", + dba_name: d.dba_name || c.dba_name || "", + email: d.email || c.email || "", + phone: d.phone || c.telephone || "", + base_state: d.base_state || c.phy_state || "", + address_state: d.address_state || c.phy_state || "", + address_city: d.address_city || c.phy_city || "", + address_street: d.address_street || c.phy_street || "", + address_zip: d.address_zip || c.phy_zip || "", + power_units: d.power_units || (c.power_units != null ? String(c.power_units) : ""), + }; + state.intake_data = { ...d, ...seeded }; + if (!state.email && c.email) state.email = c.email; + if (!state.name && c.legal_name) state.name = c.legal_name; + saveState(state); + } + } catch { /* prefill is best-effort; never block the form */ } + renderStep(loadState().step_index || 0); + return; + } + if (!token && !orderParam) return; const API = (window as any).__PW_API || "";