Subversion Repositories SmartDukaan

Rev

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 stream

    function 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 camera
            audio: 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 retake
        var 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 parallel
            var 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>