Rev 36686 | Rev 36807 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
<html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"><title>Location Verification - SmartDukaan</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"><style>body {background: #f4f6f9;font-family: 'Segoe UI', Roboto, sans-serif;}.geo-card {max-width: 480px;margin: 30px auto;background: #fff;border-radius: 14px;box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);overflow: hidden;}.geo-header {background: linear-gradient(135deg, #f7931e 0%, #ed1c24 100%);color: #fff;padding: 22px 24px;text-align: center;}.geo-header .brand-row {display: flex;align-items: center;justify-content: center;gap: 10px;margin-bottom: 6px;}.geo-header .brand-mark {width: 36px;height: 36px;background: #fff;color: #ed1c24;border-radius: 8px;display: inline-flex;align-items: center;justify-content: center;font-weight: 800;font-size: 20px;letter-spacing: -1px;box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);}.geo-header h4 {margin: 0;font-weight: 700;letter-spacing: 0.3px;}.geo-header p {margin: 8px 0 0;opacity: 0.95;font-size: 13px;}.geo-body {padding: 24px;}.step {display: none;}.step.active {display: block;}.step-indicator {display: flex;justify-content: center;gap: 8px;margin-bottom: 20px;}.step-dot {width: 10px;height: 10px;border-radius: 50%;background: #ddd;transition: background 0.3s;}.step-dot.done {background: #4caf50;}.step-dot.current {background: #ed1c24;}.btn-geo {background: linear-gradient(135deg, #f7931e 0%, #ed1c24 100%);color: #fff;border: none;border-radius: 8px;padding: 12px 24px;font-size: 16px;width: 100%;cursor: pointer;font-weight: 600;box-shadow: 0 2px 8px rgba(237, 28, 36, 0.25);}.btn-geo:hover {filter: brightness(0.95);color: #fff;}.btn-geo:disabled {background: #b0bec5;cursor: not-allowed;}.loc-info {background: #e8f5e9;border-radius: 8px;padding: 12px;margin: 12px 0;font-size: 13px;color: #2e7d32;}.loc-info.error {background: #fce4ec;color: #c62828;}.upload-area {border: 2px dashed #b0bec5;border-radius: 8px;padding: 24px;text-align: center;cursor: pointer;transition: border-color 0.3s;}.upload-area:hover {border-color: #ed1c24;}.upload-area.uploaded {border-color: #4caf50;background: #e8f5e9;}.preview-img {max-width: 100%;max-height: 200px;border-radius: 8px;margin-top: 12px;}.spinner {display: inline-block;width: 20px;height: 20px;border: 3px solid #fff;border-top-color: transparent;border-radius: 50%;animation: spin 0.8s linear infinite;}@keyframes spin {to {transform: rotate(360deg);}}.form-control:focus {border-color: #ed1c24;box-shadow: 0 0 0 0.2rem rgba(237, 28, 36, 0.20);}/* Live camera view */.camera-wrap {position: relative;background: #000;border-radius: 10px;overflow: hidden;aspect-ratio: 4 / 3;margin-bottom: 12px;}.camera-wrap video,.camera-wrap img {width: 100%;height: 100%;object-fit: cover;display: block;}.camera-wrap .cam-overlay {position: absolute;inset: 0;display: flex;align-items: center;justify-content: center;color: #fff;font-size: 13px;background: rgba(0, 0, 0, 0.5);text-align: center;padding: 16px;}.btn-capture {background: #fff;color: #ed1c24;border: 3px solid #ed1c24;border-radius: 50%;width: 64px;height: 64px;display: inline-flex;align-items: center;justify-content: center;cursor: pointer;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);}.btn-capture:disabled {opacity: 0.5;cursor: not-allowed;}.btn-capture .inner {width: 48px;height: 48px;border-radius: 50%;background: linear-gradient(135deg, #f7931e 0%, #ed1c24 100%);}.capture-row {display: flex;align-items: center;justify-content: center;gap: 14px;margin: 8px 0 14px;}.btn-retake {background: transparent;border: 1px solid #b0bec5;color: #455a64;border-radius: 8px;padding: 8px 14px;cursor: pointer;font-size: 13px;}</style></head><body><div class="geo-card"><div class="geo-header"><div class="brand-row"><span class="brand-mark">S</span><h4>SmartDukaan</h4></div><p>Location Verification</p></div><div class="geo-body"><div class="step-indicator"><div class="step-dot current" id="dot1"></div><div class="step-dot" id="dot2"></div></div><p class="text-center text-muted mb-3" style="font-size:14px;">Hello <strong>$!leadName</strong>, please verify your location.</p><!-- STEP 1: Mobile Verification --><div class="step active" id="step1"><h6 class="mb-3">Step 1: Verify Mobile Number</h6><div class="form-group"><input type="tel" class="form-control" id="mobileNumber" maxlength="10"placeholder="Enter your registered mobile number" autocomplete="off"></div><div id="mobileError" class="loc-info error" style="display:none;"></div><button class="btn btn-geo" id="btnVerifyMobile">Verify Mobile</button></div><!-- STEP 2: Live camera capture (location is grabbed silently when photo is taken) --><div class="step" id="step2"><h6 class="mb-3">Step 2: Capture Your Store Photo</h6><p class="text-muted" style="font-size:13px;">Stand at your store and tap the red button to take a live photo.Your location will be recorded automatically.</p><div class="camera-wrap" id="cameraWrap"><video id="cameraVideo" autoplay playsinline muted></video><img id="photoPreview" style="display:none;" alt="Captured photo"><div class="cam-overlay" id="cameraOverlay">Starting camera...</div></div><div class="capture-row"><button type="button" class="btn-capture" id="btnCapture" disabled><span class="inner"></span></button><button type="button" class="btn-retake" id="btnRetake" style="display:none;">Retake</button></div><div id="captureStatus" class="loc-info" style="display:none;"></div><input type="hidden" id="latitude" value=""><input type="hidden" id="longitude" value=""><input type="hidden" id="imageDocumentId" value="0"><button class="btn btn-geo mt-2" id="btnSubmit" disabled>Submit Location</button></div></div></div><!-- Hidden canvas used to grab a still frame from the live video stream --><canvas id="captureCanvas" style="display:none;"></canvas><input type="hidden" id="leadId" value="$leadId"><script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script><script>var contextPath = "$rc.contextPath";var mediaStream = null; // live camera streamfunction showStep(num) {$('.step').removeClass('active');$('#step' + num).addClass('active');for (var i = 1; i <= 2; i++) {var dot = $('#dot' + i);dot.removeClass('current done');if (i < num) dot.addClass('done');else if (i === num) dot.addClass('current');}}// STEP 1: Verify Mobile$('#btnVerifyMobile').click(function () {var mobile = $('#mobileNumber').val().trim();if (!mobile || mobile.length < 10) {$('#mobileError').text('Please enter a valid 10-digit mobile number').show();return;}var btn = $(this);btn.prop('disabled', true).html('<span class="spinner"></span> Verifying...');$('#mobileError').hide();$.ajax({url: contextPath + '/lead-geo/verify-mobile',method: 'POST',data: {leadId: $('#leadId').val(), mobileNumber: mobile},success: function (resp) {if (resp.response && resp.response.verified) {showStep(2);startCamera();} else {$('#mobileError').text('Verification failed. Please try again.').show();}},error: function (xhr) {var msg = 'Verification failed';try {msg = JSON.parse(xhr.responseText).message || msg;} catch (e) {}$('#mobileError').text(msg).show();},complete: function () {btn.prop('disabled', false).text('Verify Mobile');}});});// STEP 2: Live camera — uses getUserMedia so only an in-page live capture// is possible (no gallery picker, no file input). Geolocation is grabbed// silently when the user taps the capture button, so no separate step.function startCamera() {var overlay = $('#cameraOverlay');if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {overlay.text('Your browser does not support live camera. Please open this link in Chrome/Safari on your phone.');return;}overlay.text('Starting camera...');navigator.mediaDevices.getUserMedia({video: {facingMode: {ideal: 'environment'}}, // prefer rear cameraaudio: false}).then(function (stream) {mediaStream = stream;var video = document.getElementById('cameraVideo');video.srcObject = stream;video.onloadedmetadata = function () {overlay.hide();$('#btnCapture').prop('disabled', false);};}).catch(function (err) {var msg = 'Camera access denied. ';if (err && err.name === 'NotAllowedError') msg += 'Please allow camera permission and reload.';else if (err && err.name === 'NotFoundError') msg += 'No camera was found on this device.';else if (location.protocol !== 'https:' && location.hostname !== 'localhost') {msg = 'Live camera requires HTTPS. Please open this link over a secure connection.';}overlay.text(msg);});}function stopCamera() {if (mediaStream) {mediaStream.getTracks().forEach(function (t) {t.stop();});mediaStream = null;}}// Capture frame + silently grab geolocation$('#btnCapture').click(function () {var btn = $(this);btn.prop('disabled', true);$('#captureStatus').removeClass('error').text('Capturing photo and your location...').show();var video = document.getElementById('cameraVideo');var canvas = document.getElementById('captureCanvas');canvas.width = video.videoWidth;canvas.height = video.videoHeight;canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);// Show the still preview, hide the live video, allow retakevar dataUrl = canvas.toDataURL('image/jpeg', 0.9);$('#photoPreview').attr('src', dataUrl).show();$('#cameraVideo').hide();$('#btnRetake').show();canvas.toBlob(function (blob) {if (!blob) {btn.prop('disabled', false);$('#captureStatus').addClass('error').text('Could not capture photo. Try again.').show();return;}// Upload photo + grab geolocation in parallelvar uploadDone = uploadPhoto(blob);var geoDone = captureGeo();$.when(uploadDone, geoDone).done(function () {$('#captureStatus').removeClass('error').text('Photo and location captured.').show();$('#btnSubmit').prop('disabled', false);stopCamera();}).fail(function (msg) {btn.prop('disabled', false);$('#captureStatus').addClass('error').text(msg || 'Capture failed. Try again.').show();});}, 'image/jpeg', 0.9);});$('#btnRetake').click(function () {$('#photoPreview').hide();$('#cameraVideo').show();$(this).hide();$('#imageDocumentId').val('0');$('#latitude').val('');$('#longitude').val('');$('#btnSubmit').prop('disabled', true);$('#captureStatus').hide();if (!mediaStream) startCamera();else $('#btnCapture').prop('disabled', false);});function uploadPhoto(blob) {var d = $.Deferred();var formData = new FormData();formData.append('file', blob, 'store-photo.jpg');$.ajax({url: contextPath + '/lead-geo/upload-image',method: 'POST',data: formData,processData: false,contentType: false,success: function (resp) {if (resp.response && resp.response.document_id) {$('#imageDocumentId').val(resp.response.document_id);d.resolve();} else {d.reject('Upload failed.');}},error: function () {d.reject('Upload failed. Please try again.');}});return d.promise();}function captureGeo() {var d = $.Deferred();if (!navigator.geolocation) {d.reject('Geolocation not supported.');return d.promise();}navigator.geolocation.getCurrentPosition(function (pos) {$('#latitude').val(pos.coords.latitude);$('#longitude').val(pos.coords.longitude);d.resolve();},function (err) {var msg = 'Unable to get location. ';if (err.code === 1) msg += 'Please allow location permission.';else if (err.code === 2) msg += 'Location unavailable.';else if (err.code === 3) msg += 'Timed out. Try again.';d.reject(msg);},{enableHighAccuracy: true, timeout: 15000, maximumAge: 0});return d.promise();}// SUBMIT$('#btnSubmit').click(function () {var btn = $(this);btn.prop('disabled', true).html('<span class="spinner"></span> Submitting...');$.ajax({url: contextPath + '/lead-geo/submit',method: 'POST',data: {leadId: $('#leadId').val(),mobileNumber: $('#mobileNumber').val().trim(),latitude: $('#latitude').val(),longitude: $('#longitude').val(),imageDocumentId: $('#imageDocumentId').val()},success: function (resp) {// Replace body with thank you (SVG check icon — no emoji to avoid charset issues)$('.geo-body').html('<div class="text-center py-4">' +'<svg xmlns="http://www.w3.org/2000/svg" width="72" height="72" viewBox="0 0 24 24"' +' fill="none" stroke="#22c55e" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"' +' style="display:block; margin: 0 auto;">' +'<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline>' +'</svg>' +'<h5 class="mt-3">Thank You!</h5>' +'<p class="text-muted">Your location has been submitted successfully.<br>Our team will review it shortly.</p>' +'</div>');$('.step-indicator').remove();stopCamera();},error: function () {btn.prop('disabled', false).text('Submit Location');alert('Submission failed. Please try again.');}});});// Allow Enter key on mobile input$('#mobileNumber').keypress(function (e) {if (e.which === 13) $('#btnVerifyMobile').click();});</script></body></html>