new-site/site/src/components/intake/steps/DOTIntakeStep.astro
justin 73a10d4a0f Simplify photo ID upload: clear instructions for truckers
- Yellow instruction box explains 3 methods in plain English
  (phone photo, computer upload, scanner)
- One big orange "Add Photo of Your ID" button
- Webcam + QR code kept in code but simplified UI
- Accepted formats note

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 16:11:49 -05:00

661 lines
36 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
// Unified DOT intake form — shows relevant sections based on services ordered.
// Handles: MCS-150, UCR, D&A, BOC-3, USDOT, MC Authority, Audit Prep, bundles.
---
<div class="pw-step" data-slug="dot-intake">
<h2>DOT Filing Information</h2>
<p class="pw-help">
Provide your carrier information. We will prepare and submit your filings on your behalf.
</p>
<div class="pw-security-notice">
<svg style="width:16px;height:16px;flex-shrink:0;margin-top:2px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"/></svg>
<span>All personal information is transmitted over 256-bit SSL encryption and stored encrypted at rest. We never share your data with third parties.</span>
</div>
<div class="pw-form-grid">
<!-- ═══ SECTION: Company Info (always shown) ═══ -->
<h3>Company Information</h3>
<div class="pw-row">
<label class="pw-field"><span>Legal Entity Name <em>*</em></span>
<input type="text" id="dot-legal-name" required placeholder="As registered with FMCSA" /></label>
</div>
<div class="pw-row">
<label class="pw-field"><span>DBA / Trade Name</span>
<input type="text" id="dot-dba" placeholder="If different from legal name" /></label>
</div>
<div class="pw-row-3">
<label class="pw-field"><span>USDOT Number <em>*</em></span>
<input type="text" id="dot-dot" required placeholder="e.g. 1234567" /></label>
<label class="pw-field"><span>MC/MX/FF Number</span>
<input type="text" id="dot-mc" placeholder="e.g. MC-123456" /></label>
<label class="pw-field"><span>EIN (Tax ID)</span>
<input type="text" id="dot-ein" placeholder="XX-XXXXXXX" maxlength="12" /></label>
</div>
<h3>Principal Business Address</h3>
<div class="pw-row">
<label class="pw-field"><span>Street Address <em>*</em></span>
<input type="text" id="dot-street" required placeholder="123 Main St" /></label>
</div>
<div class="pw-row-3">
<label class="pw-field"><span>City <em>*</em></span>
<input type="text" id="dot-city" required /></label>
<label class="pw-field"><span>State <em>*</em></span>
<select id="dot-state" required>
<option value="">--</option>
<option value="AL">AL</option><option value="AK">AK</option><option value="AZ">AZ</option><option value="AR">AR</option><option value="CA">CA</option><option value="CO">CO</option><option value="CT">CT</option><option value="DE">DE</option><option value="FL">FL</option><option value="GA">GA</option><option value="HI">HI</option><option value="ID">ID</option><option value="IL">IL</option><option value="IN">IN</option><option value="IA">IA</option><option value="KS">KS</option><option value="KY">KY</option><option value="LA">LA</option><option value="ME">ME</option><option value="MD">MD</option><option value="MA">MA</option><option value="MI">MI</option><option value="MN">MN</option><option value="MS">MS</option><option value="MO">MO</option><option value="MT">MT</option><option value="NE">NE</option><option value="NV">NV</option><option value="NH">NH</option><option value="NJ">NJ</option><option value="NM">NM</option><option value="NY">NY</option><option value="NC">NC</option><option value="ND">ND</option><option value="OH">OH</option><option value="OK">OK</option><option value="OR">OR</option><option value="PA">PA</option><option value="RI">RI</option><option value="SC">SC</option><option value="SD">SD</option><option value="TN">TN</option><option value="TX">TX</option><option value="UT">UT</option><option value="VT">VT</option><option value="VA">VA</option><option value="WA">WA</option><option value="WV">WV</option><option value="WI">WI</option><option value="WY">WY</option><option value="DC">DC</option>
</select></label>
<label class="pw-field"><span>ZIP <em>*</em></span>
<input type="text" id="dot-zip" required maxlength="10" placeholder="12345" /></label>
</div>
<div class="pw-row-2">
<label class="pw-field"><span>Phone <em>*</em></span>
<input type="tel" id="dot-phone" required placeholder="(555) 123-4567" /></label>
<label class="pw-field"><span>Email <em>*</em></span>
<input type="email" id="dot-email" required placeholder="you@company.com" /></label>
</div>
<!-- ═══ SECTION: Entity & Operations (MCS-150, USDOT, MC Auth, bundles) ═══ -->
<div id="dot-sec-operations" hidden>
<h3>Entity & Operations</h3>
<div class="pw-row-2">
<label class="pw-field"><span>Entity Type <em>*</em></span>
<select id="dot-entity-type" required>
<option value="">Select...</option>
<option value="sole_proprietorship">Sole Proprietorship</option>
<option value="partnership">Partnership</option>
<option value="corporation">Corporation</option>
<option value="llc">LLC</option>
<option value="other">Other</option>
</select></label>
<label class="pw-field"><span>Carrier Operation <em>*</em></span>
<select id="dot-carrier-op" required>
<option value="">Select...</option>
<option value="authorized_for_hire">Authorized For-Hire</option>
<option value="exempt_for_hire">Exempt For-Hire</option>
<option value="private_property">Private (Property)</option>
<option value="private_passengers">Private (Passengers)</option>
</select></label>
</div>
<div class="pw-row-2">
<label class="pw-field"><span>Interstate / Intrastate <em>*</em></span>
<select id="dot-interstate" required>
<option value="">Select...</option>
<option value="interstate">Interstate (across state lines)</option>
<option value="intrastate_hazmat">Intrastate — Hazmat</option>
<option value="intrastate_non_hazmat">Intrastate — Non-hazmat</option>
</select></label>
<label class="pw-field"><span>Hazmat? <em>*</em></span>
<select id="dot-hazmat" required>
<option value="">Select...</option>
<option value="no">No</option>
<option value="yes">Yes</option>
</select></label>
</div>
</div>
<!-- ═══ SECTION: Fleet (MCS-150, UCR, bundles) ═══ -->
<div id="dot-sec-fleet" hidden>
<h3>Fleet Information</h3>
<div class="pw-row-3">
<label class="pw-field"><span>Power Units (trucks) <em>*</em></span>
<input type="number" id="dot-power-units" required min="0" placeholder="e.g. 5" /></label>
<label class="pw-field"><span>Drivers <em>*</em></span>
<input type="number" id="dot-drivers" required min="0" placeholder="e.g. 6" /></label>
<label class="pw-field"><span>Annual Miles</span>
<input type="number" id="dot-miles" min="0" placeholder="e.g. 250000" /></label>
</div>
</div>
<!-- ═══ SECTION: UCR Fleet Bracket (UCR only) ═══ -->
<div id="dot-sec-ucr" hidden>
<h3>UCR Registration</h3>
<p class="pw-field-help">UCR fees are based on the number of qualifying commercial motor vehicles (over 10,001 lbs GVWR, 10+ passengers, or placardable hazmat).</p>
<div class="pw-row-2">
<label class="pw-field"><span>Fleet Size Bracket <em>*</em></span>
<select id="dot-ucr-bracket">
<option value="">Select...</option>
<option value="0-2">02 vehicles ($76 gov fee)</option>
<option value="3-5">35 vehicles ($227 gov fee)</option>
<option value="6-20">620 vehicles ($452 gov fee)</option>
<option value="21-100">21100 vehicles ($1,576 gov fee)</option>
<option value="101-1000">1011,000 vehicles ($7,511 gov fee)</option>
<option value="1001+">1,001+ vehicles ($73,346 gov fee)</option>
</select></label>
<label class="pw-field"><span>Base State <em>*</em></span>
<input type="text" id="dot-ucr-state" readonly placeholder="From address above" /></label>
</div>
</div>
<!-- ═══ SECTION: Cargo Types (MCS-150, bundles) ═══ -->
<div id="dot-sec-cargo" hidden>
<h3>Cargo Types (check all that apply)</h3>
<div class="pw-cargo-grid">
<label><input type="checkbox" data-cargo="general" /> General Freight</label>
<label><input type="checkbox" data-cargo="household" /> Household Goods</label>
<label><input type="checkbox" data-cargo="metal" /> Metal/Sheets/Coils</label>
<label><input type="checkbox" data-cargo="motor_vehicles" /> Motor Vehicles</label>
<label><input type="checkbox" data-cargo="drivetow" /> Drive/Tow Away</label>
<label><input type="checkbox" data-cargo="logs" /> Logs/Poles/Lumber</label>
<label><input type="checkbox" data-cargo="building_materials" /> Building Materials</label>
<label><input type="checkbox" data-cargo="mobile_homes" /> Mobile Homes</label>
<label><input type="checkbox" data-cargo="machinery" /> Machinery/Large Objects</label>
<label><input type="checkbox" data-cargo="fresh_produce" /> Fresh Produce</label>
<label><input type="checkbox" data-cargo="liquids" /> Liquids/Gases</label>
<label><input type="checkbox" data-cargo="intermodal" /> Intermodal Containers</label>
<label><input type="checkbox" data-cargo="passengers" /> Passengers</label>
<label><input type="checkbox" data-cargo="oilfield" /> Oilfield Equipment</label>
<label><input type="checkbox" data-cargo="livestock" /> Livestock</label>
<label><input type="checkbox" data-cargo="grain" /> Grain/Feed/Hay</label>
<label><input type="checkbox" data-cargo="coal" /> Coal/Coke</label>
<label><input type="checkbox" data-cargo="meat" /> Meat</label>
<label><input type="checkbox" data-cargo="garbage" /> Garbage/Refuse</label>
<label><input type="checkbox" data-cargo="chemicals" /> Chemicals</label>
<label><input type="checkbox" data-cargo="refrigerated" /> Refrigerated Food</label>
<label><input type="checkbox" data-cargo="beverages" /> Beverages</label>
<label><input type="checkbox" data-cargo="construction" /> Construction</label>
<label><input type="checkbox" data-cargo="other" /> Other</label>
</div>
</div>
<!-- ═══ SECTION: D&A Program (D&A, bundles) ═══ -->
<div id="dot-sec-da" hidden>
<h3>Drug & Alcohol Compliance Program</h3>
<p class="pw-field-help">Required for all carriers with CDL drivers. We will enroll you in a DOT-compliant consortium and set up your program.</p>
<div class="pw-row-3">
<label class="pw-field"><span>CDL Drivers <em>*</em></span>
<input type="number" id="dot-cdl-drivers" min="0" placeholder="Number of CDL holders" /></label>
<label class="pw-field"><span>Owner-Operators <em>*</em></span>
<input type="number" id="dot-owner-ops" min="0" value="0" /></label>
<label class="pw-field"><span>DER Name</span>
<input type="text" id="dot-der-name" placeholder="Designated Employer Rep (optional)" /></label>
</div>
<div class="pw-row">
<label class="pw-field"><span>Current D&A Program Provider</span>
<input type="text" id="dot-current-da" placeholder="Leave blank if none / first time" /></label>
</div>
</div>
<!-- ═══ SECTION: BOC-3 (BOC-3, bundles) ═══ -->
<div id="dot-sec-boc3" hidden>
<h3>BOC-3 Process Agent</h3>
<p class="pw-field-help">We will designate a blanket process agent covering all 48 contiguous states + DC.</p>
<div class="pw-row-2">
<label class="pw-field"><span>Docket Type</span>
<select id="dot-docket-type">
<option value="">Select if applicable...</option>
<option value="MC">MC (Motor Carrier)</option>
<option value="FF">FF (Freight Forwarder)</option>
<option value="MX">MX (Mexican Carrier)</option>
</select></label>
<label class="pw-field"><span>Docket Number</span>
<input type="text" id="dot-docket-num" placeholder="e.g. 123456" /></label>
</div>
</div>
<!-- ═══ SECTION: Authorized Signer (always shown) ═══ -->
<h3>Authorized Signer</h3>
<div class="pw-row-2">
<label class="pw-field"><span>Full Name <em>*</em></span>
<input type="text" id="dot-signer-name" required placeholder="Owner or officer" /></label>
<label class="pw-field"><span>Title <em>*</em></span>
<input type="text" id="dot-signer-title" required placeholder="e.g. Owner, President" /></label>
</div>
<!-- ═══ SECTION: Photo ID (MCS-150, MC Auth) ═══ -->
<div id="dot-sec-photo-id" hidden>
<h3>Government-Issued Photo ID</h3>
<p class="pw-field-help">FMCSA requires a copy of your government-issued photo ID (driver's license, passport, or state ID) to verify the signer's identity.</p>
<div style="background:#fefce8;border:1px solid #fbbf24;border-radius:8px;padding:12px 14px;margin-bottom:12px;font-size:13px;color:#854d0e;line-height:1.6">
<strong>How to add your ID:</strong><br>
&#8226; <strong>On your phone?</strong> Tap the button below and take a picture of the front of your ID<br>
&#8226; <strong>On a computer?</strong> Take a photo of your ID with your phone, email or text it to yourself, save it, then click the button below to upload it<br>
&#8226; <strong>Have a scanner?</strong> Scan your ID, save the file, then click the button below to upload it
</div>
<div class="pw-upload-area">
<input type="file" id="dot-photo-id" accept="image/*,.pdf" style="display:none" />
<!-- Preview + quality check -->
<div id="dot-id-preview" hidden>
<div style="text-align:center;margin-bottom:12px">
<img id="dot-id-img" style="max-width:320px;max-height:240px;border-radius:8px;border:2px solid #d1d5db;display:block;margin:0 auto" />
<p id="dot-id-resolution" style="font-size:11px;color:#94a3b8;margin:6px 0 0"></p>
</div>
<div id="dot-id-quality-check" style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:14px;margin-bottom:12px">
<p style="font-size:13px;font-weight:600;color:#1a2744;margin:0 0 10px">Photo ID Quality Check</p>
<div id="dot-id-checks" style="font-size:12px;color:#374151;line-height:2"></div>
<div id="dot-id-quality-warn" hidden style="background:#fef3c7;border:1px solid #fbbf24;border-radius:6px;padding:10px;margin-top:10px">
<p style="font-size:12px;color:#92400e;margin:0;font-weight:600">&#9888; Image quality issue detected</p>
<p id="dot-id-quality-msg" style="font-size:11px;color:#92400e;margin:4px 0 0"></p>
</div>
<div id="dot-id-quality-ok" hidden style="background:#f0fdf4;border:1px solid #86efac;border-radius:6px;padding:10px;margin-top:10px">
<p style="font-size:12px;color:#166534;margin:0">&#10004; Image looks good — clear and properly sized for submission.</p>
</div>
</div>
<div style="display:flex;gap:8px;justify-content:center">
<button type="button" id="dot-id-accept" style="padding:8px 24px;background:#059669;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer">Accept &amp; Continue</button>
<button type="button" id="dot-id-retake" style="padding:8px 24px;background:#fff;color:#374151;border:1px solid #d1d5db;border-radius:6px;font-size:13px;cursor:pointer">Retake / Re-upload</button>
</div>
</div>
<div id="dot-id-upload-options">
<button type="button" id="dot-id-btn" style="display:flex;align-items:center;justify-content:center;gap:10px;width:100%;padding:16px 24px;background:#f97316;color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:15px;font-weight:600;box-shadow:0 2px 8px rgba(249,115,22,0.3)">
<svg style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6.827 6.175A2.31 2.31 0 015.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 00-1.134-.175 2.31 2.31 0 01-1.64-1.055l-.822-1.316a2.192 2.192 0 00-1.736-1.039 48.774 48.774 0 00-5.232 0 2.192 2.192 0 00-1.736 1.039l-.821 1.316z"/><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12.75a4.5 4.5 0 11-9 0 4.5 4.5 0 019 0zM18.75 10.5h.008v.008h-.008V10.5z"/></svg>
Add Photo of Your ID
</button>
<p style="font-size:11px;color:#94a3b8;text-align:center;margin:8px 0 0">Accepted formats: JPEG, PNG, PDF &middot; Max 15MB</p>
</div>
</div>
<div class="pw-security-notice" style="margin-top:8px">
<svg style="width:14px;height:14px;flex-shrink:0;margin-top:2px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"/></svg>
<span style="font-size:11px">Your ID is encrypted in transit and at rest. Used only for FMCSA identity verification and automatically deleted after filing.</span>
</div>
</div>
</div>
<div id="pw-dot-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.8rem; color: #64748b; margin: 0 0 0.5rem; }
.pw-cargo-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0.3rem 1rem; font-size: 0.85rem; color: #374151; }
.pw-cargo-grid label { display: flex; align-items: center; gap: 0.4rem; cursor: pointer; }
.pw-security-notice { display: flex; gap: 8px; align-items: flex-start; background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 8px; padding: 10px 14px; margin-bottom: 1rem; font-size: 12px; color: #1e40af; line-height: 1.5; }
.pw-upload-area { border: 2px dashed #d1d5db; border-radius: 8px; padding: 1rem; text-align: center; }
.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-cargo-grid { grid-template-columns: 1fr 1fr; } }
</style>
<script is:inline>
// Guard: only run on pages that have the DOT intake step
if (!document.querySelector('[data-slug="dot-intake"], [data-step="dot-intake"]')) {
// Not a DOT intake page — skip all initialization
} else {
// Determine which sections to show based on services in the batch
const DOT_SECTIONS = {
"mcs150-update": ["dot-sec-operations","dot-sec-fleet","dot-sec-cargo","dot-sec-photo-id"],
"ucr-registration": ["dot-sec-fleet","dot-sec-ucr"],
"dot-drug-alcohol": ["dot-sec-da"],
"boc3-filing": ["dot-sec-boc3"],
"dot-registration": ["dot-sec-operations"],
"mc-authority": ["dot-sec-operations","dot-sec-photo-id"],
"dot-audit-prep": ["dot-sec-operations","dot-sec-fleet","dot-sec-da"],
"dot-full-compliance": ["dot-sec-operations","dot-sec-fleet","dot-sec-cargo","dot-sec-ucr","dot-sec-da","dot-sec-boc3","dot-sec-photo-id"],
};
function showRelevantSections() {
const PW = (window).PWIntake;
const state = PW?.get?.() || {};
// Get service slug from wizard element or state
const wizardEl = document.querySelector(".pw-wizard[data-service]");
const pageSlug = wizardEl?.getAttribute("data-service") || "";
const slugs = state.batch_slugs || [pageSlug || state.service_slug || ""];
// Collect all sections to show
const show = new Set();
for (const slug of slugs) {
for (const sec of (DOT_SECTIONS[slug] || [])) {
show.add(sec);
}
}
// Default: always show operations + fleet if nothing specific
if (show.size === 0) {
show.add("dot-sec-operations");
show.add("dot-sec-fleet");
}
// Show/hide sections
for (const id of Object.keys(DOT_SECTIONS).flatMap(k => DOT_SECTIONS[k])) {
const el = document.getElementById(id);
if (el) el.hidden = !show.has(id);
}
// Auto-fill UCR base state from address
const stateEl = document.getElementById("dot-state");
const ucrState = document.getElementById("dot-ucr-state");
if (stateEl && ucrState) {
ucrState.value = stateEl.value;
stateEl.addEventListener("change", () => { ucrState.value = stateEl.value; });
}
}
// Show sections — retry until wizard element is found
function initSections() {
const wizardEl = document.querySelector(".pw-wizard[data-service]");
if (!wizardEl) { setTimeout(initSections, 100); return; }
showRelevantSections();
}
initSections();
window.addEventListener("pw:step-shown", (evt) => {
if (evt.detail.step !== "dot-intake") return;
showRelevantSections();
// Hydrate from existing intake data
const s = (window).PWIntake.get();
const d = s.intake_data || {};
const map = {
"dot-legal-name": d.legal_name || "", "dot-dba": d.dba_name || "",
"dot-dot": d.dot_number || "", "dot-mc": d.mc_number || "", "dot-ein": d.ein || "",
"dot-street": d.address_street || "", "dot-city": d.address_city || "",
"dot-state": d.address_state || "", "dot-zip": d.address_zip || "",
"dot-phone": d.phone || "", "dot-email": d.email || s.email || "",
"dot-entity-type": d.entity_type || "", "dot-carrier-op": d.carrier_operation || "",
"dot-interstate": d.interstate_intrastate || "", "dot-hazmat": d.hazmat || "",
"dot-power-units": d.power_units || "", "dot-drivers": d.drivers || "",
"dot-miles": d.annual_miles || "", "dot-signer-name": d.signer_name || "",
"dot-signer-title": d.signer_title || "", "dot-cdl-drivers": d.cdl_drivers || "",
"dot-owner-ops": d.owner_operators || "0", "dot-der-name": d.der_name || "",
"dot-current-da": d.current_da_provider || "", "dot-ucr-bracket": d.fleet_size_bracket || "",
};
for (const [id, val] of Object.entries(map)) {
const el = document.getElementById(id);
if (el && val) el.value = val;
}
});
// Save all data on step-next
window.addEventListener("pw:step-next", (evt) => {
const PW = (window).PWIntake;
if (PW.steps[PW.get().step_index] !== "dot-intake") return;
const errDiv = document.getElementById("pw-dot-errors");
errDiv.hidden = true;
// Validate required fields (only visible ones)
const required = ["dot-legal-name","dot-dot","dot-street","dot-city","dot-state","dot-zip","dot-phone","dot-email","dot-signer-name","dot-signer-title"];
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);
}
// Validate visible-section-specific required fields
const visibleRequired = {
"dot-sec-operations": ["dot-entity-type","dot-carrier-op","dot-interstate","dot-hazmat"],
"dot-sec-fleet": ["dot-power-units","dot-drivers"],
};
for (const [secId, fields] of Object.entries(visibleRequired)) {
const sec = document.getElementById(secId);
if (sec && !sec.hidden) {
for (const id of fields) {
const el = document.getElementById(id);
if (!el || !el.value.trim()) missing.push(el?.parentElement?.querySelector("span")?.textContent || id);
}
}
}
if (missing.length) { evt.preventDefault(); errDiv.hidden = false; errDiv.textContent = "Please fill in: " + missing.join(", "); return; }
// Collect cargo types
const cargoTypes = [];
document.querySelectorAll("[data-cargo]").forEach(function(cb) {
if (cb.checked) cargoTypes.push(cb.dataset.cargo);
});
const val = (id) => (document.getElementById(id))?.value?.trim() || "";
const state = PW.get();
PW.set({ ...state, intake_data: { ...state.intake_data,
legal_name: val("dot-legal-name"), dba_name: val("dot-dba"),
dot_number: val("dot-dot"), mc_number: val("dot-mc"), ein: val("dot-ein"),
address_street: val("dot-street"), address_city: val("dot-city"),
address_state: val("dot-state"), address_zip: val("dot-zip"),
phone: val("dot-phone"), email: val("dot-email"),
entity_type: val("dot-entity-type"), carrier_operation: val("dot-carrier-op"),
interstate_intrastate: val("dot-interstate"), hazmat: val("dot-hazmat"),
power_units: val("dot-power-units"), drivers: val("dot-drivers"),
annual_miles: val("dot-miles"), cargo_types: cargoTypes,
signer_name: val("dot-signer-name"), signer_title: val("dot-signer-title"),
fleet_size_bracket: val("dot-ucr-bracket"), base_state: val("dot-ucr-state"),
cdl_drivers: val("dot-cdl-drivers"), owner_operators: val("dot-owner-ops"),
der_name: val("dot-der-name"), current_da_provider: val("dot-current-da"),
docket_type: val("dot-docket-type"), docket_number: val("dot-docket-num"),
photo_id_uploaded: !!(window).__dotPhotoId,
}});
});
// Photo ID upload (elements may not exist if section is hidden)
const idBtn = document.getElementById("dot-id-btn");
const idInput = document.getElementById("dot-photo-id");
const idPreview = document.getElementById("dot-id-preview");
const idImg = document.getElementById("dot-id-img");
const idRemove = document.getElementById("dot-id-remove");
const idQrBtn = document.getElementById("dot-id-qr-btn");
const idQrContainer = document.getElementById("dot-id-qr-container");
const idUploadOpts = document.getElementById("dot-id-upload-options");
// Upload button — opens file picker
idBtn?.addEventListener("click", function() { if (idInput) idInput.click(); });
// Webcam camera button
var idCamBtn = document.getElementById("dot-id-cam-btn");
var idWebcam = document.getElementById("dot-id-webcam");
var idVideo = document.getElementById("dot-id-video");
var idCaptureBtn = document.getElementById("dot-id-capture");
var idCamCancel = document.getElementById("dot-id-cam-cancel");
var idCanvas = document.getElementById("dot-id-canvas");
var webcamStream = null;
idCamBtn?.addEventListener("click", function() {
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
alert("Camera not available in this browser. Please use Upload File instead.");
return;
}
navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: "environment" } })
.then(function(stream) {
webcamStream = stream;
idVideo.srcObject = stream;
if (idWebcam) idWebcam.hidden = false;
if (idUploadOpts) {
// Hide upload options but keep webcam visible
var buttons = idUploadOpts.querySelector("div");
if (buttons) buttons.style.display = "none";
}
})
.catch(function(err) {
alert("Could not access camera: " + err.message + ". Please use Upload File instead.");
});
});
idCaptureBtn?.addEventListener("click", function() {
if (!idVideo || !idCanvas) return;
idCanvas.width = idVideo.videoWidth;
idCanvas.height = idVideo.videoHeight;
var ctx = idCanvas.getContext("2d");
ctx.drawImage(idVideo, 0, 0);
// Stop webcam
if (webcamStream) {
webcamStream.getTracks().forEach(function(t) { t.stop(); });
webcamStream = null;
}
if (idWebcam) idWebcam.hidden = true;
// Convert to blob and handle as file
idCanvas.toBlob(function(blob) {
var file = new File([blob], "photo-id-capture.jpg", { type: "image/jpeg" });
handleIdFile(file);
}, "image/jpeg", 0.92);
});
idCamCancel?.addEventListener("click", function() {
if (webcamStream) {
webcamStream.getTracks().forEach(function(t) { t.stop(); });
webcamStream = null;
}
if (idWebcam) idWebcam.hidden = true;
if (idUploadOpts) {
var buttons = idUploadOpts.querySelector("div");
if (buttons) buttons.style.display = "";
}
});
// QR code button — show QR with current page URL for phone upload
idQrBtn?.addEventListener("click", function() {
if (!idQrContainer) return;
var showing = !idQrContainer.hidden;
idQrContainer.hidden = showing;
if (!showing) {
// Generate QR code using canvas
var canvas = document.getElementById("dot-id-qr-canvas");
if (canvas && !canvas.dataset.rendered) {
var url = window.location.href;
// Use a simple QR code API (Google Charts - no library needed)
var img = new Image();
img.crossOrigin = "anonymous";
img.onload = function() {
canvas.width = 200;
canvas.height = 200;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, 200, 200);
canvas.dataset.rendered = "1";
};
img.src = "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=" + encodeURIComponent(url);
}
}
});
var idAcceptBtn = document.getElementById("dot-id-accept");
var idRetakeBtn = document.getElementById("dot-id-retake");
var idQualityWarn = document.getElementById("dot-id-quality-warn");
var idQualityOk = document.getElementById("dot-id-quality-ok");
var idQualityMsg = document.getElementById("dot-id-quality-msg");
var idChecksDiv = document.getElementById("dot-id-checks");
var idResolution = document.getElementById("dot-id-resolution");
var idAccepted = false;
function runQualityCheck(file, imgEl) {
var checks = [];
var warnings = [];
// File size check
var sizeMB = (file.size / 1024 / 1024).toFixed(1);
if (file.size < 50000) {
checks.push({ok: false, text: "File size: " + sizeMB + "MB — too small, may be low quality"});
warnings.push("Image file is very small (" + sizeMB + "MB). Please use a higher resolution.");
} else if (file.size > 15000000) {
checks.push({ok: false, text: "File size: " + sizeMB + "MB — very large"});
warnings.push("File is over 15MB. Consider a smaller image.");
} else {
checks.push({ok: true, text: "File size: " + sizeMB + "MB"});
}
// File type check
var validTypes = ["image/jpeg","image/png","image/heic","image/heif","image/webp","application/pdf"];
if (validTypes.indexOf(file.type) >= 0 || file.type.startsWith("image/")) {
checks.push({ok: true, text: "File type: " + file.type.split("/")[1].toUpperCase()});
} else {
checks.push({ok: false, text: "File type: " + file.type + " — not a recognized image format"});
warnings.push("Please upload a JPEG, PNG, or PDF image of your ID.");
}
// Resolution check (for images)
if (imgEl && imgEl.naturalWidth) {
var w = imgEl.naturalWidth;
var h = imgEl.naturalHeight;
if (idResolution) idResolution.textContent = w + " × " + h + " pixels";
if (w < 400 || h < 250) {
checks.push({ok: false, text: "Resolution: " + w + "×" + h + " — too low, text may be unreadable"});
warnings.push("Image resolution is too low. FMCSA may not accept an unreadable ID.");
} else if (w < 800 || h < 500) {
checks.push({ok: true, text: "Resolution: " + w + "×" + h + " — acceptable"});
} else {
checks.push({ok: true, text: "Resolution: " + w + "×" + h + " — good"});
}
// Aspect ratio check (ID cards are roughly 1.6:1)
var ratio = Math.max(w,h) / Math.min(w,h);
if (ratio > 0.8 && ratio < 2.5) {
checks.push({ok: true, text: "Aspect ratio: looks like an ID card"});
} else {
checks.push({ok: false, text: "Aspect ratio: " + ratio.toFixed(1) + ":1 — doesn't look like a standard ID"});
warnings.push("This doesn't appear to be a photo of an ID card. Please upload a clear photo of the front of your government-issued ID.");
}
}
// Render checks
if (idChecksDiv) {
idChecksDiv.innerHTML = checks.map(function(c) {
return '<div style="display:flex;align-items:center;gap:6px">' +
(c.ok ? '<span style="color:#059669">&#10004;</span>' : '<span style="color:#dc2626">&#10008;</span>') +
'<span>' + c.text + '</span></div>';
}).join("");
}
if (warnings.length > 0) {
if (idQualityWarn) { idQualityWarn.hidden = false; idQualityMsg.textContent = warnings.join(" "); }
if (idQualityOk) idQualityOk.hidden = true;
} else {
if (idQualityWarn) idQualityWarn.hidden = true;
if (idQualityOk) idQualityOk.hidden = false;
}
}
function handleIdFile(file) {
if (!file) return;
window.__dotPhotoId = file;
idAccepted = false;
if (file.type.startsWith("image/") && idImg) {
var reader = new FileReader();
reader.onload = function(e) {
idImg.src = e.target?.result;
// Wait for image to load to check dimensions
idImg.onload = function() { runQualityCheck(file, idImg); };
};
reader.readAsDataURL(file);
} else {
// PDF or other — can't preview but accept
if (idResolution) idResolution.textContent = "PDF document";
runQualityCheck(file, null);
}
if (idPreview) idPreview.hidden = false;
if (idUploadOpts) idUploadOpts.style.display = "none";
}
// Accept button
idAcceptBtn?.addEventListener("click", function() {
idAccepted = true;
if (idPreview) {
// Collapse to small preview
var qualCheck = document.getElementById("dot-id-quality-check");
if (qualCheck) qualCheck.hidden = true;
idAcceptBtn.hidden = true;
idRetakeBtn.style.fontSize = "11px";
idRetakeBtn.textContent = "Change ID";
if (idImg) { idImg.style.maxWidth = "150px"; idImg.style.maxHeight = "100px"; }
}
});
// Retake button
idRetakeBtn?.addEventListener("click", function() {
window.__dotPhotoId = null;
idAccepted = false;
if (idInput) idInput.value = "";
// Stop webcam if running
if (webcamStream) { webcamStream.getTracks().forEach(function(t) { t.stop(); }); webcamStream = null; }
if (idWebcam) idWebcam.hidden = true;
if (idPreview) idPreview.hidden = true;
if (idUploadOpts) idUploadOpts.style.display = "";
// Reset quality check UI
var qualCheck = document.getElementById("dot-id-quality-check");
if (qualCheck) qualCheck.hidden = false;
if (idAcceptBtn) idAcceptBtn.hidden = false;
if (idRetakeBtn) { idRetakeBtn.style.fontSize = "13px"; idRetakeBtn.textContent = "Retake / Re-upload"; }
if (idImg) { idImg.style.maxWidth = "320px"; idImg.style.maxHeight = "240px"; }
});
idInput?.addEventListener("change", function() {
handleIdFile(idInput.files?.[0]);
});
// (webcam capture handled above via getUserMedia)
} // end guard
</script>