intake: prefill order form from ?dot= campaign CTA links

Campaign CTA buttons link to /order/<slug>?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.
This commit is contained in:
justin 2026-06-02 12:46:33 -05:00
parent 316b9cc6c7
commit d420c49818
2 changed files with 85 additions and 0 deletions

View file

@ -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;

View file

@ -347,6 +347,45 @@ const STEP_LABELS: Record<string, string> = {
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/<slug>?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 || "";