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