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:
parent
316b9cc6c7
commit
d420c49818
2 changed files with 85 additions and 0 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 || "";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue