feat(fulfillment): state-trucking intake form + hazmat/emissions products
- Add StateTruckingIntakeStep.astro with slug-gated sections (IRP/IFTA, emissions, intrastate authority, OSOW, hazmat/PHMSA); wired into Wizard - Register hazmat-phmsa + state-emissions products & SERVICE_INFO - Add server-side bundle/mutual-exclusion enforcement + REQUIRED_FIELDS - State-trucking slugs now collect real intake data (were review-only) - Surface slug-specific intake fields in admin todo (_summarize_intake) - Remove state slugs from email ADMIN_ASSISTED set (now get intake links)
This commit is contained in:
parent
71b888f993
commit
9c6b8d95e0
3 changed files with 703 additions and 0 deletions
296
site/src/components/intake/steps/StateTruckingIntakeStep.astro
Normal file
296
site/src/components/intake/steps/StateTruckingIntakeStep.astro
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
---
|
||||
// State-level trucking + hazmat/emissions intake. Slug-gated sections, mirroring
|
||||
// DOTIntakeStep.astro. Collects the carrier identity plus the specific data each
|
||||
// state filing / hazmat / emissions handler needs.
|
||||
---
|
||||
|
||||
<div class="pw-step" data-slug="state-trucking">
|
||||
<h2>Filing Information</h2>
|
||||
<p class="pw-help">
|
||||
Provide your carrier details so we can prepare your state filing(s). We file on your behalf.
|
||||
</p>
|
||||
|
||||
<div class="pw-form-grid">
|
||||
<!-- ═══ Carrier Identity (always shown) ═══ -->
|
||||
<h3>Carrier Information</h3>
|
||||
<div class="pw-row">
|
||||
<label class="pw-field"><span>Legal Entity Name <em>*</em></span>
|
||||
<input type="text" id="st-legal-name" required placeholder="As registered with FMCSA" /></label>
|
||||
</div>
|
||||
<div class="pw-row-3">
|
||||
<label class="pw-field"><span>USDOT Number <em>*</em></span>
|
||||
<input type="text" id="st-dot" required placeholder="e.g. 1234567" /></label>
|
||||
<label class="pw-field"><span>MC/MX/FF Number</span>
|
||||
<input type="text" id="st-mc" placeholder="e.g. MC-123456" /></label>
|
||||
<label class="pw-field"><span>Email <em>*</em></span>
|
||||
<input type="email" id="st-email" required placeholder="you@company.com" /></label>
|
||||
</div>
|
||||
<div class="pw-row-2">
|
||||
<label class="pw-field"><span>Base State <em>*</em></span>
|
||||
<select id="st-base-state" required>
|
||||
<option value="">--</option>
|
||||
<option>AL</option><option>AK</option><option>AZ</option><option>AR</option><option>CA</option><option>CO</option><option>CT</option><option>DE</option><option>FL</option><option>GA</option><option>HI</option><option>ID</option><option>IL</option><option>IN</option><option>IA</option><option>KS</option><option>KY</option><option>LA</option><option>ME</option><option>MD</option><option>MA</option><option>MI</option><option>MN</option><option>MS</option><option>MO</option><option>MT</option><option>NE</option><option>NV</option><option>NH</option><option>NJ</option><option>NM</option><option>NY</option><option>NC</option><option>ND</option><option>OH</option><option>OK</option><option>OR</option><option>PA</option><option>RI</option><option>SC</option><option>SD</option><option>TN</option><option>TX</option><option>UT</option><option>VT</option><option>VA</option><option>WA</option><option>WV</option><option>WI</option><option>WY</option><option>DC</option>
|
||||
</select></label>
|
||||
<label class="pw-field"><span>Power Units (trucks) <em>*</em></span>
|
||||
<input type="number" id="st-power-units" min="0" placeholder="e.g. 5" /></label>
|
||||
</div>
|
||||
|
||||
<!-- ═══ IRP / IFTA (interstate registration + fuel tax) ═══ -->
|
||||
<div id="st-sec-irp-ifta" hidden>
|
||||
<h3>Interstate Operations</h3>
|
||||
<div class="pw-row-2">
|
||||
<label class="pw-field"><span>Primary Fuel Type</span>
|
||||
<select id="st-fuel-type">
|
||||
<option value="">Select...</option>
|
||||
<option value="diesel">Diesel</option>
|
||||
<option value="gasoline">Gasoline</option>
|
||||
<option value="propane">Propane / LPG</option>
|
||||
<option value="cng">CNG / LNG</option>
|
||||
<option value="electric">Electric</option>
|
||||
<option value="other">Other</option>
|
||||
</select></label>
|
||||
<label class="pw-field"><span>Gross Weight Bracket</span>
|
||||
<select id="st-gross-weight">
|
||||
<option value="">Select...</option>
|
||||
<option value="under_26k">Under 26,000 lbs</option>
|
||||
<option value="26k_to_80k">26,001–80,000 lbs</option>
|
||||
<option value="over_80k">Over 80,000 lbs</option>
|
||||
</select></label>
|
||||
</div>
|
||||
<div class="pw-row">
|
||||
<label class="pw-field"><span>Operating States (besides base) <em>*</em></span>
|
||||
<input type="text" id="st-operating-states" placeholder="e.g. CA, NV, AZ, OR (comma-separated)" /></label>
|
||||
</div>
|
||||
<p class="pw-field-help">After your order, we'll send a short follow-up form to collect each vehicle's VIN, plate, and registered weight for the apportioned/IFTA filing.</p>
|
||||
</div>
|
||||
|
||||
<!-- ═══ CA MCP + CARB / Emissions ═══ -->
|
||||
<div id="st-sec-emissions" hidden>
|
||||
<h3>Fleet Emissions Profile</h3>
|
||||
<div class="pw-row-2">
|
||||
<label class="pw-field"><span>CA Number (if any)</span>
|
||||
<input type="text" id="st-ca-number" placeholder="CHP CA# if already issued" /></label>
|
||||
<label class="pw-field"><span>Oldest Engine Model Year</span>
|
||||
<input type="number" id="st-engine-year" min="1990" max="2030" placeholder="e.g. 2014" /></label>
|
||||
</div>
|
||||
<p class="pw-field-help">Clean-truck programs (CARB Clean Truck Check, NY/CO/MD/NJ/MA Advanced Clean Trucks) phase out older engines. We'll review your fleet's model years against your states' thresholds.</p>
|
||||
</div>
|
||||
|
||||
<!-- ═══ Intrastate Authority ═══ -->
|
||||
<div id="st-sec-intrastate" hidden>
|
||||
<h3>Intrastate Operating Authority</h3>
|
||||
<div class="pw-row-2">
|
||||
<label class="pw-field"><span>Authority Type <em>*</em></span>
|
||||
<select id="st-authority-type">
|
||||
<option value="">Select...</option>
|
||||
<option value="common">Common Carrier (COA)</option>
|
||||
<option value="contract">Contract Carrier</option>
|
||||
<option value="cpcn">Certificate of Public Convenience & Necessity</option>
|
||||
<option value="household_goods">Household Goods Mover</option>
|
||||
<option value="unknown">Not sure — please advise</option>
|
||||
</select></label>
|
||||
<label class="pw-field"><span>BOC-3 already on file?</span>
|
||||
<select id="st-boc3">
|
||||
<option value="">Select...</option>
|
||||
<option value="yes">Yes</option>
|
||||
<option value="no">No</option>
|
||||
<option value="unknown">Not sure</option>
|
||||
</select></label>
|
||||
</div>
|
||||
<div class="pw-row-2">
|
||||
<label class="pw-field"><span>Insurance Carrier</span>
|
||||
<input type="text" id="st-ins-carrier" placeholder="Your liability insurer" /></label>
|
||||
<label class="pw-field"><span>Insurance Policy #</span>
|
||||
<input type="text" id="st-ins-policy" placeholder="Policy number" /></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ OSOW Permit ═══ -->
|
||||
<div id="st-sec-osow" hidden>
|
||||
<h3>Oversize / Overweight Load</h3>
|
||||
<div class="pw-row-2">
|
||||
<label class="pw-field"><span>Load Dimensions (L×W×H)</span>
|
||||
<input type="text" id="st-load-dims" placeholder="e.g. 75ft × 12ft × 14ft" /></label>
|
||||
<label class="pw-field"><span>Gross Load Weight (lbs)</span>
|
||||
<input type="number" id="st-load-weight" min="0" placeholder="e.g. 120000" /></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ Hazmat / PHMSA ═══ -->
|
||||
<div id="st-sec-hazmat" hidden>
|
||||
<h3>Hazmat Profile (PHMSA Registration)</h3>
|
||||
<p class="pw-field-help">PHMSA registration is required for carriers transporting placardable quantities of hazardous materials (49 CFR Part 107).</p>
|
||||
<div class="pw-hazmat-grid">
|
||||
<label><input type="checkbox" data-hazmat="class1" /> Class 1 — Explosives</label>
|
||||
<label><input type="checkbox" data-hazmat="class2" /> Class 2 — Gases</label>
|
||||
<label><input type="checkbox" data-hazmat="class3" /> Class 3 — Flammable Liquids</label>
|
||||
<label><input type="checkbox" data-hazmat="class4" /> Class 4 — Flammable Solids</label>
|
||||
<label><input type="checkbox" data-hazmat="class5" /> Class 5 — Oxidizers</label>
|
||||
<label><input type="checkbox" data-hazmat="class6" /> Class 6 — Toxic/Infectious</label>
|
||||
<label><input type="checkbox" data-hazmat="class7" /> Class 7 — Radioactive</label>
|
||||
<label><input type="checkbox" data-hazmat="class8" /> Class 8 — Corrosives</label>
|
||||
<label><input type="checkbox" data-hazmat="class9" /> Class 9 — Misc.</label>
|
||||
</div>
|
||||
<div class="pw-row-2" style="margin-top:0.75rem">
|
||||
<label class="pw-field"><span>Transport in bulk packaging?</span>
|
||||
<select id="st-bulk">
|
||||
<option value="">Select...</option>
|
||||
<option value="no">No</option>
|
||||
<option value="yes">Yes</option>
|
||||
</select></label>
|
||||
<label class="pw-field"><span>Small business? (under SBA size standard)</span>
|
||||
<select id="st-small-biz">
|
||||
<option value="">Select...</option>
|
||||
<option value="yes">Yes (lower PHMSA fee)</option>
|
||||
<option value="no">No</option>
|
||||
</select></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="pw-st-errors" class="pw-err" hidden></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.pw-step h2 { margin: 0 0 0.5rem; color: #1a2744; }
|
||||
.pw-step h3 { color: #1a2744; margin: 1.25rem 0 0.5rem; font-size: 0.95rem; border-bottom: 1px solid #e2e8f0; padding-bottom: 0.3rem; }
|
||||
.pw-help { color: #64748b; font-size: 0.9rem; margin-bottom: 1rem; }
|
||||
.pw-form-grid { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 1rem 1.25rem; }
|
||||
.pw-row, .pw-row-2, .pw-row-3 { margin-bottom: 0.75rem; }
|
||||
.pw-row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
|
||||
.pw-row-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0.75rem; }
|
||||
.pw-field { display: flex; flex-direction: column; gap: 0.2rem; }
|
||||
.pw-field span { font-size: 0.8rem; font-weight: 600; color: #374151; }
|
||||
.pw-field em { color: #dc2626; font-style: normal; }
|
||||
.pw-field input, .pw-field select { padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 6px; font-size: 0.85rem; }
|
||||
.pw-field input:focus, .pw-field select:focus { outline: none; border-color: #f97316; box-shadow: 0 0 0 2px rgba(249,115,22,0.2); }
|
||||
.pw-field-help { font-size: 0.85rem; color: #64748b; margin: 0.25rem 0 0.5rem; }
|
||||
.pw-hazmat-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0.3rem 1rem; font-size: 0.85rem; color: #374151; }
|
||||
.pw-hazmat-grid label { display: flex; align-items: center; gap: 0.4rem; cursor: pointer; }
|
||||
.pw-err { color: #b91c1c; margin-top: 0.75rem; font-size: 0.9rem; background: #fee2e2; padding: 0.5rem 0.75rem; border-radius: 6px; }
|
||||
@media (max-width: 640px) { .pw-row-2, .pw-row-3 { grid-template-columns: 1fr; } .pw-hazmat-grid { grid-template-columns: 1fr 1fr; } }
|
||||
</style>
|
||||
|
||||
<script is:inline>
|
||||
if (!document.querySelector('[data-slug="state-trucking"], [data-step="state-trucking"]')) {
|
||||
// Not a state-trucking intake page — skip
|
||||
} else {
|
||||
|
||||
// Which sections to show per slug.
|
||||
const ST_SECTIONS = {
|
||||
"irp-registration": ["st-sec-irp-ifta"],
|
||||
"ifta-application": ["st-sec-irp-ifta"],
|
||||
"ifta-quarterly": ["st-sec-irp-ifta"],
|
||||
"or-weight-mile-tax": ["st-sec-irp-ifta"],
|
||||
"ny-hut-registration": ["st-sec-irp-ifta"],
|
||||
"ky-kyu-registration": ["st-sec-irp-ifta"],
|
||||
"nm-weight-distance": ["st-sec-irp-ifta"],
|
||||
"ct-highway-use-fee": ["st-sec-irp-ifta"],
|
||||
"ca-mcp-carb": ["st-sec-emissions"],
|
||||
"state-emissions": ["st-sec-emissions"],
|
||||
"state-dot-registration":[],
|
||||
"intrastate-authority": ["st-sec-intrastate"],
|
||||
"osow-permit": ["st-sec-osow"],
|
||||
"state-trucking-bundle": ["st-sec-irp-ifta", "st-sec-emissions", "st-sec-intrastate"],
|
||||
"hazmat-phmsa": ["st-sec-hazmat"],
|
||||
};
|
||||
const ALL_SECTIONS = ["st-sec-irp-ifta","st-sec-emissions","st-sec-intrastate","st-sec-osow","st-sec-hazmat"];
|
||||
|
||||
function showSections() {
|
||||
const PW = window.PWIntake;
|
||||
const state = PW?.get?.() || {};
|
||||
const wizardEl = document.querySelector(".pw-wizard[data-service]");
|
||||
const pageSlug = wizardEl?.getAttribute("data-service") || "";
|
||||
const slugs = state.batch_slugs || [pageSlug || state.service_slug || ""];
|
||||
const show = new Set();
|
||||
for (const slug of slugs) for (const sec of (ST_SECTIONS[slug] || [])) show.add(sec);
|
||||
for (const id of ALL_SECTIONS) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.hidden = !show.has(id);
|
||||
}
|
||||
}
|
||||
|
||||
function initSections() {
|
||||
const wizardEl = document.querySelector(".pw-wizard[data-service]");
|
||||
if (!wizardEl) { setTimeout(initSections, 100); return; }
|
||||
showSections();
|
||||
}
|
||||
initSections();
|
||||
|
||||
window.addEventListener("pw:step-shown", (evt) => {
|
||||
if (evt.detail.step !== "state-trucking") return;
|
||||
showSections();
|
||||
const s = window.PWIntake.get();
|
||||
const d = s.intake_data || {};
|
||||
const map = {
|
||||
"st-legal-name": d.legal_name || d.entity_name || "", "st-dot": d.dot_number || "",
|
||||
"st-mc": d.mc_number || "", "st-email": d.email || s.email || "",
|
||||
"st-base-state": d.base_state || d.address_state || "", "st-power-units": d.power_units || "",
|
||||
"st-fuel-type": d.fuel_type || "", "st-gross-weight": d.gross_weight_bracket || "",
|
||||
"st-operating-states": Array.isArray(d.operating_states) ? d.operating_states.join(", ") : (d.operating_states || ""),
|
||||
"st-ca-number": d.ca_number || "", "st-engine-year": d.engine_model_years || "",
|
||||
"st-authority-type": d.authority_type || "", "st-boc3": d.boc3_on_file || "",
|
||||
"st-ins-carrier": d.insurance_carrier || "", "st-ins-policy": d.insurance_policy || "",
|
||||
"st-load-dims": d.load_dimensions || "", "st-load-weight": d.load_weight || "",
|
||||
"st-bulk": d.bulk_packaging || "", "st-small-biz": d.small_business || "",
|
||||
};
|
||||
for (const [id, val] of Object.entries(map)) {
|
||||
const el = document.getElementById(id);
|
||||
if (el && val) el.value = val;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("pw:step-next", (evt) => {
|
||||
const PW = window.PWIntake;
|
||||
if (PW.steps[PW.get().step_index] !== "state-trucking") return;
|
||||
const errDiv = document.getElementById("pw-st-errors");
|
||||
errDiv.hidden = true;
|
||||
const val = (id) => (document.getElementById(id))?.value?.trim() || "";
|
||||
|
||||
// Base required (always)
|
||||
const required = ["st-legal-name","st-dot","st-email","st-base-state","st-power-units"];
|
||||
const missing = [];
|
||||
for (const id of required) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el || !el.value.trim()) missing.push(el?.parentElement?.querySelector("span")?.textContent || id);
|
||||
}
|
||||
// Section-specific required
|
||||
const sectionRequired = {
|
||||
"st-sec-irp-ifta": [["st-operating-states","Operating States"]],
|
||||
"st-sec-intrastate": [["st-authority-type","Authority Type"]],
|
||||
};
|
||||
for (const [secId, fields] of Object.entries(sectionRequired)) {
|
||||
const sec = document.getElementById(secId);
|
||||
if (sec && !sec.hidden) {
|
||||
for (const [id, label] of fields) {
|
||||
if (!val(id)) missing.push(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Hazmat: at least one class
|
||||
const hazSec = document.getElementById("st-sec-hazmat");
|
||||
let hazmatClasses = [];
|
||||
document.querySelectorAll("[data-hazmat]").forEach((cb) => { if (cb.checked) hazmatClasses.push(cb.dataset.hazmat); });
|
||||
if (hazSec && !hazSec.hidden && hazmatClasses.length === 0) {
|
||||
missing.push("At least one hazmat class");
|
||||
}
|
||||
if (missing.length) { evt.preventDefault(); errDiv.hidden = false; errDiv.textContent = "Please fill in: " + missing.join(", "); return; }
|
||||
|
||||
const opStates = val("st-operating-states").split(",").map(s => s.trim().toUpperCase()).filter(Boolean);
|
||||
const state = PW.get();
|
||||
PW.set({ ...state, intake_data: { ...state.intake_data,
|
||||
legal_name: val("st-legal-name"), entity_name: val("st-legal-name"),
|
||||
dot_number: val("st-dot"), mc_number: val("st-mc"), email: val("st-email"),
|
||||
base_state: val("st-base-state"), power_units: val("st-power-units"),
|
||||
fuel_type: val("st-fuel-type"), gross_weight_bracket: val("st-gross-weight"),
|
||||
operating_states: opStates,
|
||||
ca_number: val("st-ca-number"), engine_model_years: val("st-engine-year"),
|
||||
authority_type: val("st-authority-type"), boc3_on_file: val("st-boc3"),
|
||||
insurance_carrier: val("st-ins-carrier"), insurance_policy: val("st-ins-policy"),
|
||||
load_dimensions: val("st-load-dims"), load_weight: val("st-load-weight"),
|
||||
hazmat_classes: hazmatClasses, bulk_packaging: val("st-bulk"), small_business: val("st-small-biz") === "yes",
|
||||
}});
|
||||
});
|
||||
|
||||
} // end guard
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue