Add DOT/FMCSA compliance checker frontend page
Static page at /tools/dot-compliance-check with: - DOT number lookup with live FMCSA API checks - Name search against local census database - Color-coded check cards (green/yellow/red) for each filing - Auto-fill from ?dot= URL param - CTA to email for help when issues found - Mobile responsive, orange/trucking theme Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b1d21c2ba7
commit
6e0a84418f
1 changed files with 278 additions and 0 deletions
278
site/public/tools/dot-compliance-check/index.html
Normal file
278
site/public/tools/dot-compliance-check/index.html
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Free DOT/FMCSA motor carrier compliance check. Verify MCS-150 status, insurance, operating authority, and safety rating instantly.">
|
||||
<title>DOT Compliance Check — Free Motor Carrier Filing Status | Performance West Inc.</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:'Inter',system-ui,sans-serif;background:#f8fafc;color:#1f2937;min-height:100vh}
|
||||
.container{max-width:800px;margin:0 auto;padding:24px 16px}
|
||||
.header{background:#1a2744;color:white;padding:32px 0;text-align:center}
|
||||
.header h1{font-size:28px;font-weight:700;margin-bottom:8px}
|
||||
.header p{color:#94a3b8;font-size:14px}
|
||||
.search-box{background:white;border-radius:12px;box-shadow:0 2px 12px rgba(0,0,0,0.08);padding:24px;margin:-32px auto 24px;max-width:600px;position:relative;z-index:10}
|
||||
.search-row{display:flex;gap:8px}
|
||||
.search-row input{flex:1;border:1px solid #cbd5e1;border-radius:8px;padding:12px 16px;font-size:15px;outline:none;font-family:monospace}
|
||||
.search-row input:focus{border-color:#f97316;box-shadow:0 0 0 3px rgba(249,115,22,0.15)}
|
||||
.search-row button{background:#f97316;color:white;border:none;border-radius:8px;padding:12px 24px;font-weight:700;font-size:14px;cursor:pointer;white-space:nowrap}
|
||||
.search-row button:hover{background:#ea580c}
|
||||
.search-row button:disabled{background:#9ca3af;cursor:not-allowed}
|
||||
.tabs{display:flex;gap:0;margin-bottom:16px;border-bottom:2px solid #e5e7eb}
|
||||
.tab{padding:8px 16px;font-size:13px;font-weight:600;color:#6b7280;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-2px}
|
||||
.tab.active{color:#f97316;border-bottom-color:#f97316}
|
||||
.name-results{margin-top:12px;max-height:300px;overflow-y:auto}
|
||||
.name-result{padding:12px;border:1px solid #e5e7eb;border-radius:8px;margin-bottom:8px;cursor:pointer;transition:all 0.15s}
|
||||
.name-result:hover{border-color:#f97316;background:#fff7ed}
|
||||
.hidden{display:none!important}
|
||||
.loading{text-align:center;padding:40px}
|
||||
.loading .spinner{width:40px;height:40px;border:3px solid #e5e7eb;border-top-color:#f97316;border-radius:50%;animation:spin 0.8s linear infinite;margin:0 auto 16px}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
.error-box{background:#fef2f2;border:1px solid #fca5a5;border-radius:8px;padding:16px;color:#991b1b;margin-top:16px}
|
||||
.results{margin-top:24px}
|
||||
.entity-card{background:white;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,0.06);padding:24px;margin-bottom:16px}
|
||||
.entity-name{font-size:22px;font-weight:700;color:#1a2744}
|
||||
.entity-meta{font-size:13px;color:#6b7280;margin-top:4px}
|
||||
.entity-meta span{margin-right:16px}
|
||||
.check-card{border-radius:10px;padding:16px;margin-bottom:10px;display:flex;gap:12px;align-items:flex-start}
|
||||
.check-card.green{background:#f0fdf4;border:1px solid #bbf7d0}
|
||||
.check-card.yellow{background:#fefce8;border:1px solid #fde68a}
|
||||
.check-card.red{background:#fef2f2;border:1px solid #fecaca}
|
||||
.check-card.unknown{background:#f8fafc;border:1px solid #e2e8f0}
|
||||
.check-icon{width:24px;height:24px;flex-shrink:0;margin-top:2px}
|
||||
.check-label{font-weight:600;font-size:14px;margin-bottom:2px}
|
||||
.check-detail{font-size:13px;color:#374151;line-height:1.5}
|
||||
.green .check-label{color:#166534}
|
||||
.yellow .check-label{color:#854d0e}
|
||||
.red .check-label{color:#991b1b}
|
||||
.summary-bar{display:flex;gap:12px;padding:16px;background:white;border-radius:10px;box-shadow:0 1px 4px rgba(0,0,0,0.06);margin-bottom:16px;justify-content:center}
|
||||
.summary-item{font-size:13px;font-weight:600;display:flex;align-items:center;gap:4px}
|
||||
.cta-box{background:linear-gradient(135deg,#fff7ed,#ffedd5);border:2px solid #f97316;border-radius:12px;padding:24px;text-align:center;margin-top:24px}
|
||||
.cta-box h3{font-size:18px;font-weight:700;color:#1a2744;margin-bottom:8px}
|
||||
.cta-box p{font-size:13px;color:#6b7280;margin-bottom:16px}
|
||||
.cta-btn{display:inline-block;background:#f97316;color:white;font-weight:700;padding:14px 36px;border-radius:8px;text-decoration:none;font-size:15px;box-shadow:0 4px 12px rgba(249,115,22,0.3)}
|
||||
.cta-btn:hover{background:#ea580c}
|
||||
.footer{text-align:center;padding:24px;font-size:11px;color:#9ca3af}
|
||||
.footer a{color:#6b7280}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="container">
|
||||
<a href="/" style="display:inline-block;margin-bottom:12px"><img src="/images/logo.png" alt="Performance West" style="height:44px"></a>
|
||||
<h1>DOT / FMCSA Compliance Check</h1>
|
||||
<p>Free motor carrier filing status check — results in seconds</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="search-box">
|
||||
<div class="tabs">
|
||||
<div class="tab active" data-tab="dot">Search by DOT #</div>
|
||||
<div class="tab" data-tab="name">Search by Name</div>
|
||||
</div>
|
||||
|
||||
<div id="tab-dot">
|
||||
<div class="search-row">
|
||||
<input type="text" id="dot-input" placeholder="Enter USDOT number (e.g. 1234567)" maxlength="10">
|
||||
<button type="button" id="dot-btn">Check Compliance</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tab-name" class="hidden">
|
||||
<div class="search-row">
|
||||
<input type="text" id="name-input" placeholder="Enter carrier name (e.g. Swift Transportation)">
|
||||
<button type="button" id="name-btn">Search</button>
|
||||
</div>
|
||||
<div id="name-results" class="name-results hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="loading" class="loading hidden">
|
||||
<div class="spinner"></div>
|
||||
<p style="color:#6b7280">Checking compliance status...</p>
|
||||
</div>
|
||||
|
||||
<div id="error-box" class="error-box hidden"></div>
|
||||
|
||||
<div id="results" class="results hidden"></div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>Performance West Inc. · 525 Randall Ave Ste 100-1195, Cheyenne, WY 82001 · <a href="https://performancewest.net">performancewest.net</a> · (888) 411-0383</p>
|
||||
<p style="margin-top:4px">Performance West is a regulatory compliance consulting firm, not a law firm. This tool does not constitute legal advice.</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var API = window.__PW_API || "https://api.performancewest.net";
|
||||
|
||||
// Tab switching
|
||||
document.querySelectorAll(".tab").forEach(function(tab) {
|
||||
tab.addEventListener("click", function() {
|
||||
document.querySelectorAll(".tab").forEach(function(t) { t.classList.remove("active"); });
|
||||
tab.classList.add("active");
|
||||
document.getElementById("tab-dot").classList.toggle("hidden", tab.dataset.tab !== "dot");
|
||||
document.getElementById("tab-name").classList.toggle("hidden", tab.dataset.tab !== "name");
|
||||
});
|
||||
});
|
||||
|
||||
// DOT lookup
|
||||
var dotInput = document.getElementById("dot-input");
|
||||
var dotBtn = document.getElementById("dot-btn");
|
||||
var loadingEl = document.getElementById("loading");
|
||||
var errorBox = document.getElementById("error-box");
|
||||
var resultsEl = document.getElementById("results");
|
||||
|
||||
dotBtn.addEventListener("click", runDotCheck);
|
||||
dotInput.addEventListener("keydown", function(e) { if (e.key === "Enter") runDotCheck(); });
|
||||
|
||||
async function runDotCheck() {
|
||||
var raw = dotInput.value.trim().replace(/\D/g, "");
|
||||
if (!raw) { showError("Please enter a DOT number."); return; }
|
||||
|
||||
loadingEl.classList.remove("hidden");
|
||||
resultsEl.classList.add("hidden");
|
||||
errorBox.classList.add("hidden");
|
||||
|
||||
try {
|
||||
var res = await fetch(API + "/api/v1/dot/lookup?dot=" + raw);
|
||||
var data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || "Lookup failed");
|
||||
renderResults(data);
|
||||
} catch (e) {
|
||||
showError(e.message || "Something went wrong. Try again.");
|
||||
} finally {
|
||||
loadingEl.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
// Name search
|
||||
var nameInput = document.getElementById("name-input");
|
||||
var nameBtn = document.getElementById("name-btn");
|
||||
var nameResults = document.getElementById("name-results");
|
||||
|
||||
nameBtn.addEventListener("click", runNameSearch);
|
||||
nameInput.addEventListener("keydown", function(e) { if (e.key === "Enter") runNameSearch(); });
|
||||
|
||||
async function runNameSearch() {
|
||||
var name = nameInput.value.trim();
|
||||
if (!name || name.length < 2) return;
|
||||
|
||||
nameResults.classList.remove("hidden");
|
||||
nameResults.innerHTML = '<p style="color:#6b7280;font-size:13px">Searching...</p>';
|
||||
|
||||
try {
|
||||
var res = await fetch(API + "/api/v1/dot/search?name=" + encodeURIComponent(name));
|
||||
var data = await res.json();
|
||||
if (!data.results || data.results.length === 0) {
|
||||
nameResults.innerHTML = '<p style="color:#6b7280;font-size:13px">No carriers found matching "' + name + '"</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
var html = "";
|
||||
data.results.forEach(function(r) {
|
||||
var overdue = r.mcs150_parsed && new Date(r.mcs150_parsed) < new Date(Date.now() - 2 * 365.25 * 86400000);
|
||||
html += '<div class="name-result" data-dot="' + r.dot_number + '">';
|
||||
html += '<div style="font-weight:600;color:#1a2744">' + r.legal_name + '</div>';
|
||||
html += '<div style="font-size:12px;color:#6b7280;margin-top:2px">';
|
||||
html += 'DOT# ' + r.dot_number;
|
||||
if (r.phy_city) html += ' · ' + r.phy_city + ', ' + r.phy_state;
|
||||
if (r.nbr_power_unit) html += ' · ' + r.nbr_power_unit + ' trucks';
|
||||
if (overdue) html += ' · <span style="color:#dc2626;font-weight:600">MCS-150 OVERDUE</span>';
|
||||
html += '</div></div>';
|
||||
});
|
||||
nameResults.innerHTML = html;
|
||||
|
||||
nameResults.querySelectorAll(".name-result").forEach(function(el) {
|
||||
el.addEventListener("click", function() {
|
||||
dotInput.value = el.dataset.dot;
|
||||
document.querySelector('.tab[data-tab="dot"]').click();
|
||||
runDotCheck();
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
nameResults.innerHTML = '<p style="color:#dc2626;font-size:13px">Search failed. Try again.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
errorBox.textContent = msg;
|
||||
errorBox.classList.remove("hidden");
|
||||
resultsEl.classList.add("hidden");
|
||||
}
|
||||
|
||||
var statusIcons = {
|
||||
green: '<svg class="check-icon" fill="none" stroke="#16a34a" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>',
|
||||
yellow: '<svg class="check-icon" fill="none" stroke="#ca8a04" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"/></svg>',
|
||||
red: '<svg class="check-icon" fill="none" stroke="#dc2626" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/></svg>',
|
||||
unknown: '<svg class="check-icon" fill="none" stroke="#6b7280" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z"/></svg>'
|
||||
};
|
||||
|
||||
function renderResults(data) {
|
||||
var html = '';
|
||||
|
||||
// Entity card
|
||||
html += '<div class="entity-card">';
|
||||
html += '<div class="entity-name">' + data.legal_name + '</div>';
|
||||
if (data.dba_name) html += '<div style="font-size:14px;color:#6b7280;margin-top:2px">d/b/a ' + data.dba_name + '</div>';
|
||||
html += '<div class="entity-meta">';
|
||||
html += '<span>DOT# ' + data.dot_number + '</span>';
|
||||
if (data.phy_city) html += '<span>' + data.phy_city + ', ' + data.phy_state + '</span>';
|
||||
if (data.fleet && data.fleet.power_units) html += '<span>' + data.fleet.power_units + ' power units</span>';
|
||||
if (data.fleet && data.fleet.drivers) html += '<span>' + data.fleet.drivers + ' drivers</span>';
|
||||
html += '</div></div>';
|
||||
|
||||
// Summary bar
|
||||
var s = data.summary || {};
|
||||
html += '<div class="summary-bar">';
|
||||
if (s.red) html += '<div class="summary-item" style="color:#dc2626">' + statusIcons.red + ' ' + s.red + ' issue' + (s.red > 1 ? 's' : '') + '</div>';
|
||||
if (s.yellow) html += '<div class="summary-item" style="color:#ca8a04">' + statusIcons.yellow + ' ' + s.yellow + ' warning' + (s.yellow > 1 ? 's' : '') + '</div>';
|
||||
if (s.green) html += '<div class="summary-item" style="color:#16a34a">' + statusIcons.green + ' ' + s.green + ' passed</div>';
|
||||
html += '</div>';
|
||||
|
||||
// Check cards
|
||||
(data.checks || []).forEach(function(c) {
|
||||
html += '<div class="check-card ' + c.status + '">';
|
||||
html += statusIcons[c.status] || statusIcons.unknown;
|
||||
html += '<div><div class="check-label">' + c.label + '</div>';
|
||||
html += '<div class="check-detail">' + c.detail + '</div></div></div>';
|
||||
});
|
||||
|
||||
// CTA
|
||||
var redCount = (s.red || 0);
|
||||
if (redCount > 0) {
|
||||
html += '<div class="cta-box">';
|
||||
html += '<h3>' + redCount + ' compliance issue' + (redCount > 1 ? 's' : '') + ' found</h3>';
|
||||
html += '<p>Performance West can help you resolve these issues. We handle the paperwork so you can focus on driving.</p>';
|
||||
html += '<a class="cta-btn" href="mailto:info@performancewest.net?subject=DOT Compliance Help — ' + encodeURIComponent(data.legal_name) + ' (DOT ' + data.dot_number + ')">Get Help With My Filings →</a>';
|
||||
html += '<p style="font-size:11px;color:#9ca3af;margin-top:10px">Or call (888) 411-0383</p>';
|
||||
html += '</div>';
|
||||
} else {
|
||||
html += '<div style="background:#f0fdf4;border:2px solid #86efac;border-radius:12px;padding:24px;text-align:center;margin-top:24px">';
|
||||
html += '<h3 style="font-size:18px;font-weight:700;color:#166534;margin-bottom:4px">Looking good!</h3>';
|
||||
html += '<p style="font-size:13px;color:#374151">Your DOT filings appear current. No action needed at this time.</p>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
resultsEl.innerHTML = html;
|
||||
resultsEl.classList.remove("hidden");
|
||||
}
|
||||
|
||||
// Auto-fill from URL
|
||||
(function() {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var dot = params.get("dot");
|
||||
if (dot) {
|
||||
dotInput.value = dot;
|
||||
runDotCheck();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue