| 36642 |
ranu |
1 |
<html>
|
|
|
2 |
<head>
|
|
|
3 |
<meta charset="UTF-8">
|
|
|
4 |
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
|
5 |
<title>Location Verification - SmartDukaan</title>
|
|
|
6 |
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css">
|
|
|
7 |
<style>
|
|
|
8 |
body {
|
|
|
9 |
background: #f4f6f9;
|
| 36686 |
ranu |
10 |
font-family: 'Segoe UI', Roboto, sans-serif;
|
| 36642 |
ranu |
11 |
}
|
|
|
12 |
|
|
|
13 |
.geo-card {
|
|
|
14 |
max-width: 480px;
|
|
|
15 |
margin: 30px auto;
|
|
|
16 |
background: #fff;
|
| 36686 |
ranu |
17 |
border-radius: 14px;
|
|
|
18 |
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
| 36642 |
ranu |
19 |
overflow: hidden;
|
|
|
20 |
}
|
|
|
21 |
|
|
|
22 |
.geo-header {
|
| 36686 |
ranu |
23 |
background: linear-gradient(135deg, #f7931e 0%, #ed1c24 100%);
|
| 36642 |
ranu |
24 |
color: #fff;
|
| 36686 |
ranu |
25 |
padding: 22px 24px;
|
| 36642 |
ranu |
26 |
text-align: center;
|
|
|
27 |
}
|
|
|
28 |
|
| 36686 |
ranu |
29 |
.geo-header .brand-row {
|
|
|
30 |
display: flex;
|
|
|
31 |
align-items: center;
|
|
|
32 |
justify-content: center;
|
|
|
33 |
gap: 10px;
|
|
|
34 |
margin-bottom: 6px;
|
|
|
35 |
}
|
|
|
36 |
|
|
|
37 |
.geo-header .brand-mark {
|
|
|
38 |
width: 36px;
|
|
|
39 |
height: 36px;
|
|
|
40 |
background: #fff;
|
|
|
41 |
color: #ed1c24;
|
|
|
42 |
border-radius: 8px;
|
|
|
43 |
display: inline-flex;
|
|
|
44 |
align-items: center;
|
|
|
45 |
justify-content: center;
|
|
|
46 |
font-weight: 800;
|
|
|
47 |
font-size: 20px;
|
|
|
48 |
letter-spacing: -1px;
|
|
|
49 |
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
|
|
50 |
}
|
|
|
51 |
|
| 36642 |
ranu |
52 |
.geo-header h4 {
|
|
|
53 |
margin: 0;
|
| 36686 |
ranu |
54 |
font-weight: 700;
|
|
|
55 |
letter-spacing: 0.3px;
|
| 36642 |
ranu |
56 |
}
|
|
|
57 |
|
|
|
58 |
.geo-header p {
|
|
|
59 |
margin: 8px 0 0;
|
| 36686 |
ranu |
60 |
opacity: 0.95;
|
|
|
61 |
font-size: 13px;
|
| 36642 |
ranu |
62 |
}
|
|
|
63 |
|
|
|
64 |
.geo-body {
|
|
|
65 |
padding: 24px;
|
|
|
66 |
}
|
|
|
67 |
|
|
|
68 |
.step {
|
|
|
69 |
display: none;
|
|
|
70 |
}
|
|
|
71 |
|
|
|
72 |
.step.active {
|
|
|
73 |
display: block;
|
|
|
74 |
}
|
|
|
75 |
|
|
|
76 |
.step-indicator {
|
|
|
77 |
display: flex;
|
|
|
78 |
justify-content: center;
|
|
|
79 |
gap: 8px;
|
|
|
80 |
margin-bottom: 20px;
|
|
|
81 |
}
|
|
|
82 |
|
|
|
83 |
.step-dot {
|
|
|
84 |
width: 10px;
|
|
|
85 |
height: 10px;
|
|
|
86 |
border-radius: 50%;
|
|
|
87 |
background: #ddd;
|
|
|
88 |
transition: background 0.3s;
|
|
|
89 |
}
|
|
|
90 |
|
|
|
91 |
.step-dot.done {
|
|
|
92 |
background: #4caf50;
|
|
|
93 |
}
|
|
|
94 |
|
|
|
95 |
.step-dot.current {
|
| 36686 |
ranu |
96 |
background: #ed1c24;
|
| 36642 |
ranu |
97 |
}
|
|
|
98 |
|
|
|
99 |
.btn-geo {
|
| 36686 |
ranu |
100 |
background: linear-gradient(135deg, #f7931e 0%, #ed1c24 100%);
|
| 36642 |
ranu |
101 |
color: #fff;
|
|
|
102 |
border: none;
|
|
|
103 |
border-radius: 8px;
|
|
|
104 |
padding: 12px 24px;
|
|
|
105 |
font-size: 16px;
|
|
|
106 |
width: 100%;
|
|
|
107 |
cursor: pointer;
|
| 36686 |
ranu |
108 |
font-weight: 600;
|
|
|
109 |
box-shadow: 0 2px 8px rgba(237, 28, 36, 0.25);
|
| 36642 |
ranu |
110 |
}
|
|
|
111 |
|
|
|
112 |
.btn-geo:hover {
|
| 36686 |
ranu |
113 |
filter: brightness(0.95);
|
| 36642 |
ranu |
114 |
color: #fff;
|
|
|
115 |
}
|
|
|
116 |
|
|
|
117 |
.btn-geo:disabled {
|
|
|
118 |
background: #b0bec5;
|
|
|
119 |
cursor: not-allowed;
|
|
|
120 |
}
|
|
|
121 |
|
|
|
122 |
.loc-info {
|
|
|
123 |
background: #e8f5e9;
|
|
|
124 |
border-radius: 8px;
|
|
|
125 |
padding: 12px;
|
|
|
126 |
margin: 12px 0;
|
|
|
127 |
font-size: 13px;
|
|
|
128 |
color: #2e7d32;
|
|
|
129 |
}
|
|
|
130 |
|
|
|
131 |
.loc-info.error {
|
|
|
132 |
background: #fce4ec;
|
|
|
133 |
color: #c62828;
|
|
|
134 |
}
|
|
|
135 |
|
|
|
136 |
.upload-area {
|
|
|
137 |
border: 2px dashed #b0bec5;
|
|
|
138 |
border-radius: 8px;
|
|
|
139 |
padding: 24px;
|
|
|
140 |
text-align: center;
|
|
|
141 |
cursor: pointer;
|
|
|
142 |
transition: border-color 0.3s;
|
|
|
143 |
}
|
|
|
144 |
|
|
|
145 |
.upload-area:hover {
|
| 36686 |
ranu |
146 |
border-color: #ed1c24;
|
| 36642 |
ranu |
147 |
}
|
|
|
148 |
|
|
|
149 |
.upload-area.uploaded {
|
|
|
150 |
border-color: #4caf50;
|
|
|
151 |
background: #e8f5e9;
|
|
|
152 |
}
|
|
|
153 |
|
|
|
154 |
.preview-img {
|
|
|
155 |
max-width: 100%;
|
|
|
156 |
max-height: 200px;
|
|
|
157 |
border-radius: 8px;
|
|
|
158 |
margin-top: 12px;
|
|
|
159 |
}
|
|
|
160 |
|
|
|
161 |
.spinner {
|
|
|
162 |
display: inline-block;
|
|
|
163 |
width: 20px;
|
|
|
164 |
height: 20px;
|
|
|
165 |
border: 3px solid #fff;
|
|
|
166 |
border-top-color: transparent;
|
|
|
167 |
border-radius: 50%;
|
|
|
168 |
animation: spin 0.8s linear infinite;
|
|
|
169 |
}
|
|
|
170 |
|
|
|
171 |
@keyframes spin {
|
|
|
172 |
to {
|
|
|
173 |
transform: rotate(360deg);
|
|
|
174 |
}
|
|
|
175 |
}
|
|
|
176 |
|
|
|
177 |
.form-control:focus {
|
| 36686 |
ranu |
178 |
border-color: #ed1c24;
|
|
|
179 |
box-shadow: 0 0 0 0.2rem rgba(237, 28, 36, 0.20);
|
| 36642 |
ranu |
180 |
}
|
|
|
181 |
</style>
|
|
|
182 |
</head>
|
|
|
183 |
<body>
|
|
|
184 |
|
|
|
185 |
<div class="geo-card">
|
|
|
186 |
<div class="geo-header">
|
| 36686 |
ranu |
187 |
<div class="brand-row">
|
|
|
188 |
<span class="brand-mark">S</span>
|
|
|
189 |
<h4>SmartDukaan</h4>
|
|
|
190 |
</div>
|
| 36642 |
ranu |
191 |
<p>Location Verification</p>
|
|
|
192 |
</div>
|
|
|
193 |
<div class="geo-body">
|
|
|
194 |
<div class="step-indicator">
|
|
|
195 |
<div class="step-dot current" id="dot1"></div>
|
|
|
196 |
<div class="step-dot" id="dot2"></div>
|
|
|
197 |
<div class="step-dot" id="dot3"></div>
|
|
|
198 |
</div>
|
|
|
199 |
|
|
|
200 |
<p class="text-center text-muted mb-3" style="font-size:14px;">
|
|
|
201 |
Hello <strong>$!leadName</strong>, please verify your location.
|
|
|
202 |
</p>
|
|
|
203 |
|
|
|
204 |
<!-- STEP 1: Mobile Verification -->
|
|
|
205 |
<div class="step active" id="step1">
|
|
|
206 |
<h6 class="mb-3">Step 1: Verify Mobile Number</h6>
|
|
|
207 |
<div class="form-group">
|
|
|
208 |
<input type="tel" class="form-control" id="mobileNumber" maxlength="10"
|
|
|
209 |
placeholder="Enter your registered mobile number" autocomplete="off">
|
|
|
210 |
</div>
|
|
|
211 |
<div id="mobileError" class="loc-info error" style="display:none;"></div>
|
|
|
212 |
<button class="btn btn-geo" id="btnVerifyMobile">
|
|
|
213 |
Verify Mobile
|
|
|
214 |
</button>
|
|
|
215 |
</div>
|
|
|
216 |
|
|
|
217 |
<!-- STEP 2: Capture Location -->
|
|
|
218 |
<div class="step" id="step2">
|
|
|
219 |
<h6 class="mb-3">Step 2: Capture Your Live Location</h6>
|
|
|
220 |
<p class="text-muted" style="font-size:13px;">
|
|
|
221 |
Please allow location access when prompted. Stand at your store/shop location.
|
|
|
222 |
</p>
|
|
|
223 |
<button class="btn btn-geo" id="btnCaptureLocation">
|
|
|
224 |
Capture My Location
|
|
|
225 |
</button>
|
|
|
226 |
<div id="locationInfo" class="loc-info" style="display:none;"></div>
|
|
|
227 |
<input type="hidden" id="latitude" value="">
|
|
|
228 |
<input type="hidden" id="longitude" value="">
|
|
|
229 |
<button class="btn btn-geo mt-3" id="btnNextToUpload" style="display:none;">
|
|
|
230 |
Next: Upload Photo
|
|
|
231 |
</button>
|
|
|
232 |
</div>
|
|
|
233 |
|
|
|
234 |
<!-- STEP 3: Upload Photo & Submit -->
|
|
|
235 |
<div class="step" id="step3">
|
|
|
236 |
<h6 class="mb-3">Step 3: Upload Store Photo</h6>
|
|
|
237 |
<div class="upload-area" id="uploadArea" onclick="document.getElementById('photoInput').click();">
|
|
|
238 |
<div id="uploadPlaceholder">
|
| 36686 |
ranu |
239 |
<svg xmlns="http://www.w3.org/2000/svg" width="42" height="42" viewBox="0 0 24 24" fill="none"
|
|
|
240 |
stroke="#ed1c24" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"
|
|
|
241 |
style="display:block; margin: 0 auto 8px;">
|
|
|
242 |
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path>
|
|
|
243 |
<circle cx="12" cy="13" r="4"></circle>
|
|
|
244 |
</svg>
|
| 36642 |
ranu |
245 |
<p class="text-muted mb-0">Tap to capture or upload store photo</p>
|
|
|
246 |
</div>
|
|
|
247 |
<img id="photoPreview" class="preview-img" style="display:none;">
|
|
|
248 |
<input type="file" id="photoInput" accept="image/*" capture="environment" style="display:none;">
|
|
|
249 |
</div>
|
|
|
250 |
<div id="uploadStatus" class="loc-info" style="display:none;"></div>
|
|
|
251 |
<input type="hidden" id="imageDocumentId" value="0">
|
|
|
252 |
<button class="btn btn-geo mt-3" id="btnSubmit" disabled>
|
|
|
253 |
Submit Location
|
|
|
254 |
</button>
|
|
|
255 |
</div>
|
|
|
256 |
</div>
|
|
|
257 |
</div>
|
|
|
258 |
|
|
|
259 |
<input type="hidden" id="leadId" value="$leadId">
|
|
|
260 |
|
|
|
261 |
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
|
|
|
262 |
<script>
|
|
|
263 |
var contextPath = "$rc.contextPath";
|
|
|
264 |
|
|
|
265 |
function showStep(num) {
|
|
|
266 |
$('.step').removeClass('active');
|
|
|
267 |
$('#step' + num).addClass('active');
|
|
|
268 |
for (var i = 1; i <= 3; i++) {
|
|
|
269 |
var dot = $('#dot' + i);
|
|
|
270 |
dot.removeClass('current done');
|
|
|
271 |
if (i < num) dot.addClass('done');
|
|
|
272 |
else if (i === num) dot.addClass('current');
|
|
|
273 |
}
|
|
|
274 |
}
|
|
|
275 |
|
|
|
276 |
// STEP 1: Verify Mobile
|
|
|
277 |
$('#btnVerifyMobile').click(function () {
|
|
|
278 |
var mobile = $('#mobileNumber').val().trim();
|
|
|
279 |
if (!mobile || mobile.length < 10) {
|
|
|
280 |
$('#mobileError').text('Please enter a valid 10-digit mobile number').show();
|
|
|
281 |
return;
|
|
|
282 |
}
|
|
|
283 |
var btn = $(this);
|
|
|
284 |
btn.prop('disabled', true).html('<span class="spinner"></span> Verifying...');
|
|
|
285 |
$('#mobileError').hide();
|
|
|
286 |
|
|
|
287 |
$.ajax({
|
|
|
288 |
url: contextPath + '/lead-geo/verify-mobile',
|
|
|
289 |
method: 'POST',
|
|
|
290 |
data: {leadId: $('#leadId').val(), mobileNumber: mobile},
|
|
|
291 |
success: function (resp) {
|
|
|
292 |
if (resp.response && resp.response.verified) {
|
|
|
293 |
showStep(2);
|
|
|
294 |
} else {
|
|
|
295 |
$('#mobileError').text('Verification failed. Please try again.').show();
|
|
|
296 |
}
|
|
|
297 |
},
|
|
|
298 |
error: function (xhr) {
|
|
|
299 |
var msg = 'Verification failed';
|
|
|
300 |
try {
|
|
|
301 |
msg = JSON.parse(xhr.responseText).message || msg;
|
|
|
302 |
} catch (e) {
|
|
|
303 |
}
|
|
|
304 |
$('#mobileError').text(msg).show();
|
|
|
305 |
},
|
|
|
306 |
complete: function () {
|
|
|
307 |
btn.prop('disabled', false).text('Verify Mobile');
|
|
|
308 |
}
|
|
|
309 |
});
|
|
|
310 |
});
|
|
|
311 |
|
|
|
312 |
// STEP 2: Capture Location
|
|
|
313 |
$('#btnCaptureLocation').click(function () {
|
|
|
314 |
var btn = $(this);
|
|
|
315 |
btn.prop('disabled', true).html('<span class="spinner"></span> Capturing...');
|
|
|
316 |
$('#locationInfo').hide();
|
|
|
317 |
|
|
|
318 |
if (!navigator.geolocation) {
|
|
|
319 |
$('#locationInfo').addClass('error').text('Geolocation is not supported by your browser').show();
|
|
|
320 |
btn.prop('disabled', false).text('Capture My Location');
|
|
|
321 |
return;
|
|
|
322 |
}
|
|
|
323 |
|
|
|
324 |
navigator.geolocation.getCurrentPosition(
|
|
|
325 |
function (position) {
|
|
|
326 |
var lat = position.coords.latitude;
|
|
|
327 |
var lng = position.coords.longitude;
|
|
|
328 |
var acc = position.coords.accuracy;
|
|
|
329 |
|
|
|
330 |
$('#latitude').val(lat);
|
|
|
331 |
$('#longitude').val(lng);
|
|
|
332 |
$('#locationInfo').removeClass('error')
|
|
|
333 |
.html('Location captured successfully!<br>Accuracy: ' + acc.toFixed(0) + ' meters')
|
|
|
334 |
.show();
|
|
|
335 |
btn.prop('disabled', false).text('Recapture Location');
|
|
|
336 |
$('#btnNextToUpload').show();
|
|
|
337 |
},
|
|
|
338 |
function (error) {
|
|
|
339 |
var msg = 'Unable to get location. ';
|
|
|
340 |
switch (error.code) {
|
|
|
341 |
case 1:
|
|
|
342 |
msg += 'Please allow location permission.';
|
|
|
343 |
break;
|
|
|
344 |
case 2:
|
|
|
345 |
msg += 'Location unavailable.';
|
|
|
346 |
break;
|
|
|
347 |
case 3:
|
|
|
348 |
msg += 'Request timed out. Try again.';
|
|
|
349 |
break;
|
|
|
350 |
}
|
|
|
351 |
$('#locationInfo').addClass('error').text(msg).show();
|
|
|
352 |
btn.prop('disabled', false).text('Capture My Location');
|
|
|
353 |
},
|
|
|
354 |
{enableHighAccuracy: true, timeout: 15000, maximumAge: 0}
|
|
|
355 |
);
|
|
|
356 |
});
|
|
|
357 |
|
|
|
358 |
$('#btnNextToUpload').click(function () {
|
|
|
359 |
showStep(3);
|
|
|
360 |
});
|
|
|
361 |
|
|
|
362 |
// STEP 3: Upload Photo
|
|
|
363 |
$('#photoInput').change(function () {
|
|
|
364 |
var file = this.files[0];
|
|
|
365 |
if (!file) return;
|
|
|
366 |
|
|
|
367 |
// Preview
|
|
|
368 |
var reader = new FileReader();
|
|
|
369 |
reader.onload = function (e) {
|
|
|
370 |
$('#photoPreview').attr('src', e.target.result).show();
|
|
|
371 |
$('#uploadPlaceholder').hide();
|
|
|
372 |
$('#uploadArea').addClass('uploaded');
|
|
|
373 |
};
|
|
|
374 |
reader.readAsDataURL(file);
|
|
|
375 |
|
|
|
376 |
// Upload
|
|
|
377 |
$('#uploadStatus').removeClass('error').text('Uploading...').show();
|
|
|
378 |
var formData = new FormData();
|
|
|
379 |
formData.append('file', file);
|
|
|
380 |
|
|
|
381 |
$.ajax({
|
|
|
382 |
url: contextPath + '/lead-geo/upload-image',
|
|
|
383 |
method: 'POST',
|
|
|
384 |
data: formData,
|
|
|
385 |
processData: false,
|
|
|
386 |
contentType: false,
|
|
|
387 |
success: function (resp) {
|
|
|
388 |
if (resp.response && resp.response.document_id) {
|
|
|
389 |
$('#imageDocumentId').val(resp.response.document_id);
|
|
|
390 |
$('#uploadStatus').removeClass('error').text('Photo uploaded successfully!').show();
|
|
|
391 |
$('#btnSubmit').prop('disabled', false);
|
|
|
392 |
}
|
|
|
393 |
},
|
|
|
394 |
error: function () {
|
|
|
395 |
$('#uploadStatus').addClass('error').text('Upload failed. Please try again.').show();
|
|
|
396 |
}
|
|
|
397 |
});
|
|
|
398 |
});
|
|
|
399 |
|
|
|
400 |
// SUBMIT
|
|
|
401 |
$('#btnSubmit').click(function () {
|
|
|
402 |
var btn = $(this);
|
|
|
403 |
btn.prop('disabled', true).html('<span class="spinner"></span> Submitting...');
|
|
|
404 |
|
|
|
405 |
$.ajax({
|
|
|
406 |
url: contextPath + '/lead-geo/submit',
|
|
|
407 |
method: 'POST',
|
|
|
408 |
data: {
|
|
|
409 |
leadId: $('#leadId').val(),
|
|
|
410 |
mobileNumber: $('#mobileNumber').val().trim(),
|
|
|
411 |
latitude: $('#latitude').val(),
|
|
|
412 |
longitude: $('#longitude').val(),
|
|
|
413 |
imageDocumentId: $('#imageDocumentId').val()
|
|
|
414 |
},
|
|
|
415 |
success: function (resp) {
|
| 36686 |
ranu |
416 |
// Replace body with thank you (SVG check icon — no emoji to avoid charset issues)
|
| 36642 |
ranu |
417 |
$('.geo-body').html(
|
|
|
418 |
'<div class="text-center py-4">' +
|
| 36686 |
ranu |
419 |
'<svg xmlns="http://www.w3.org/2000/svg" width="72" height="72" viewBox="0 0 24 24"' +
|
|
|
420 |
' fill="none" stroke="#22c55e" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"' +
|
|
|
421 |
' style="display:block; margin: 0 auto;">' +
|
|
|
422 |
'<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>' +
|
|
|
423 |
'</svg>' +
|
| 36642 |
ranu |
424 |
'<h5 class="mt-3">Thank You!</h5>' +
|
|
|
425 |
'<p class="text-muted">Your location has been submitted successfully.<br>Our team will review it shortly.</p>' +
|
|
|
426 |
'</div>'
|
|
|
427 |
);
|
|
|
428 |
$('.step-indicator').remove();
|
|
|
429 |
},
|
|
|
430 |
error: function () {
|
|
|
431 |
btn.prop('disabled', false).text('Submit Location');
|
|
|
432 |
alert('Submission failed. Please try again.');
|
|
|
433 |
}
|
|
|
434 |
});
|
|
|
435 |
});
|
|
|
436 |
|
|
|
437 |
// Allow Enter key on mobile input
|
|
|
438 |
$('#mobileNumber').keypress(function (e) {
|
|
|
439 |
if (e.which === 13) $('#btnVerifyMobile').click();
|
|
|
440 |
});
|
|
|
441 |
</script>
|
|
|
442 |
</body>
|
|
|
443 |
</html>
|