Photo ID quality check after upload
Shows uploaded image at larger size with automated quality checks: - File size (too small = low quality warning) - File type validation (JPEG, PNG, PDF, HEIC) - Resolution check (minimum 400x250 for readable text) - Aspect ratio check (should look like an ID card) Green checkmark for passing checks, red X for issues. Yellow warning box for quality problems with specific guidance. Accept & Continue button to confirm, Retake to re-upload. After accept, collapses to small preview with "Change ID" option. Front of ID only (sufficient for FMCSA MCS-150 filing). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e8769e4d5d
commit
beb23d777e
1 changed files with 145 additions and 24 deletions
|
|
@ -210,9 +210,27 @@
|
|||
<p class="pw-field-help">Required for FMCSA filings. Driver's license, passport, or state ID.</p>
|
||||
<div class="pw-upload-area">
|
||||
<input type="file" id="dot-photo-id" accept="image/*,.pdf" capture="environment" style="display:none" />
|
||||
<div id="dot-id-preview" hidden style="display:flex;align-items:center;gap:12px;justify-content:center">
|
||||
<img id="dot-id-img" style="max-width:200px;max-height:150px;border-radius:6px;border:1px solid #d1d5db" />
|
||||
<button type="button" id="dot-id-remove" style="background:#fee2e2;color:#991b1b;border:none;padding:4px 12px;border-radius:4px;font-size:12px;cursor:pointer">Remove</button>
|
||||
<!-- 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">⚠ 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">✔ 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 & 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">
|
||||
<div style="display:flex;flex-wrap:wrap;gap:8px;justify-content:center">
|
||||
|
|
@ -433,23 +451,10 @@
|
|||
// Upload button — opens file picker
|
||||
idBtn?.addEventListener("click", function() { if (idInput) idInput.click(); });
|
||||
|
||||
// Scanner/camera button — uses capture attribute to trigger camera or scanner
|
||||
// Scanner/camera button
|
||||
var idScanBtn = document.getElementById("dot-id-scan-btn");
|
||||
var idScanInput = document.getElementById("dot-photo-id-scan");
|
||||
idScanBtn?.addEventListener("click", function() { if (idScanInput) idScanInput.click(); });
|
||||
idScanInput?.addEventListener("change", function() {
|
||||
var file = idScanInput.files?.[0];
|
||||
if (!file) return;
|
||||
// Copy to main input handler
|
||||
window.__dotPhotoId = file;
|
||||
if (file.type.startsWith("image/") && idImg) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) { idImg.src = e.target?.result; };
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
if (idPreview) idPreview.hidden = false;
|
||||
if (idUploadOpts) idUploadOpts.style.display = "none";
|
||||
});
|
||||
|
||||
// QR code button — show QR with current page URL for phone upload
|
||||
idQrBtn?.addEventListener("click", function() {
|
||||
|
|
@ -476,24 +481,140 @@
|
|||
}
|
||||
});
|
||||
|
||||
idInput?.addEventListener("change", function() {
|
||||
var file = idInput.files?.[0];
|
||||
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">✔</span>' : '<span style="color:#dc2626">✘</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;
|
||||
if (file.type.startsWith("image/")) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => { idImg.src = e.target?.result; };
|
||||
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"; }
|
||||
}
|
||||
});
|
||||
idRemove?.addEventListener("click", () => {
|
||||
(window).__dotPhotoId = null;
|
||||
|
||||
// Retake button
|
||||
idRetakeBtn?.addEventListener("click", function() {
|
||||
window.__dotPhotoId = null;
|
||||
idAccepted = false;
|
||||
if (idInput) idInput.value = "";
|
||||
if (idScanInput) idScanInput.value = "";
|
||||
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]);
|
||||
});
|
||||
|
||||
// Scanner input handler
|
||||
idScanInput?.addEventListener("change", function() {
|
||||
handleIdFile(idScanInput.files?.[0]);
|
||||
});
|
||||
|
||||
} // end guard
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue