From 39fb1c99982bfc79ac4d7ab9a4d0f317557ea5bf Mon Sep 17 00:00:00 2001 From: justin Date: Sat, 30 May 2026 16:09:02 -0500 Subject: [PATCH] Webcam capture for photo ID via getUserMedia MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Desktop users can now use their webcam to photograph their ID: - Click "Use Camera" → browser requests webcam permission - Live video preview with orange guide rectangle for ID placement - Capture button takes high-res JPEG (1280x720) - Cancel button stops webcam and returns to upload options - Captured image goes through same quality check flow - Works on Chrome, Firefox, Edge, Safari (desktop + mobile) - No libraries needed — native WebRTC API Co-Authored-By: Claude Opus 4.6 (1M context) --- .../intake/steps/DOTIntakeStep.astro | 91 ++++++++++++++++--- 1 file changed, 79 insertions(+), 12 deletions(-) diff --git a/site/src/components/intake/steps/DOTIntakeStep.astro b/site/src/components/intake/steps/DOTIntakeStep.astro index 431e728..04c4ade 100644 --- a/site/src/components/intake/steps/DOTIntakeStep.astro +++ b/site/src/components/intake/steps/DOTIntakeStep.astro @@ -238,12 +238,24 @@ Upload File - - + + +
@@ -451,10 +463,66 @@ // Upload button — opens file picker idBtn?.addEventListener("click", function() { if (idInput) idInput.click(); }); - // 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(); }); + // 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() { @@ -597,7 +665,9 @@ window.__dotPhotoId = null; idAccepted = false; if (idInput) idInput.value = ""; - if (idScanInput) idScanInput.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 @@ -612,10 +682,7 @@ handleIdFile(idInput.files?.[0]); }); - // Scanner input handler - idScanInput?.addEventListener("change", function() { - handleIdFile(idScanInput.files?.[0]); - }); + // (webcam capture handled above via getUserMedia) } // end guard