Skip to content

Commit 122b3d0

Browse files
feat(#10676): require acknowledgement checkbox before continuing without location
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 26bf6e2 commit 122b3d0

8 files changed

Lines changed: 119 additions & 9 deletions

File tree

api/resources/translations/messages-en.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,8 @@ geolocation.permission.denied = Location access is turned off. Check your device
728728
geolocation.progress = Progress
729729
geolocation.result.label = Result
730730
geolocation.retry = Retry here to capture GPS location
731-
geolocation.skip = Continue without location tracking
731+
geolocation.skip.acknowledge = I understand this record will be saved without a location.
732+
geolocation.skip.button = Continue without location
732733
geolocation.skipped = Continuing without location capture
733734
geolocation.success = Success
734735
geolocation.unavailable = GPS is not available on this device.

api/resources/translations/messages-es.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,8 @@ geolocation.permission.denied = El acceso a la ubicación está desactivado. Ver
728728
geolocation.progress = Progreso
729729
geolocation.result.label = Resultado
730730
geolocation.retry = Reintentar
731-
geolocation.skip = Continuar sin seguimiento de ubicación
731+
geolocation.skip.acknowledge = Entiendo que este registro se guardará sin una ubicación.
732+
geolocation.skip.button = Continuar sin ubicación
732733
geolocation.skipped = Continuando sin capturar la ubicación
733734
geolocation.success = Exitoso
734735
geolocation.unavailable = El GPS no está disponible en este dispositivo.

api/resources/translations/messages-fr.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,8 @@ geolocation.permission.denied = L'accès à la localisation est désactivé. Vé
728728
geolocation.progress = Progression
729729
geolocation.result.label = Résultat
730730
geolocation.retry = Réessayer
731-
geolocation.skip = Continuer sans suivi de localisation
731+
geolocation.skip.acknowledge = Je comprends que cet enregistrement sera sauvegardé sans localisation.
732+
geolocation.skip.button = Continuer sans localisation
732733
geolocation.skipped = Poursuite sans capture de localisation
733734
geolocation.success = Succès
734735
geolocation.unavailable = Le GPS n'est pas disponible sur cet appareil.

api/resources/translations/messages-ne.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,8 @@ geolocation.permission.denied = स्थान पहुँच बन्द छ
728728
geolocation.progress = प्रगति
729729
geolocation.result.label = परिणाम
730730
geolocation.retry = पुनः प्रयास गर्नुहोस्
731-
geolocation.skip = स्थान ट्र्याकिङ बिना जारी राख्नुहोस्
731+
geolocation.skip.acknowledge = म बुझ्छु कि यो रेकर्ड स्थान बिना सुरक्षित गरिनेछ।
732+
geolocation.skip.button = स्थान बिना जारी राख्नुहोस्
732733
geolocation.skipped = स्थान कैप्चर बिना जारी राख्दै
733734
geolocation.success = सफल
734735
geolocation.unavailable = यस उपकरणमा GPS उपलब्ध छैन।

api/resources/translations/messages-sw.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,8 @@ geolocation.permission.denied = Ufikiaji wa eneo umezimwa. Angalia mipangilio ya
728728
geolocation.progress = Maendeleo
729729
geolocation.result.label = Matokeo
730730
geolocation.retry = Jaribu tena
731-
geolocation.skip = Endelea bila ufuatiliaji wa eneo
731+
geolocation.skip.acknowledge = Ninaelewa kwamba rekodi hii itahifadhiwa bila eneo.
732+
geolocation.skip.button = Endelea bila eneo
732733
geolocation.skipped = Inaendelea bila kupata eneo
733734
geolocation.success = Imefaulu
734735
geolocation.unavailable = GPS haipatikani kwenye kifaa hiki.

webapp/src/css/enketo/medic.less

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,20 @@
424424
background-color: @gray-light;
425425
}
426426

427+
.geolocation-acknowledge-label {
428+
display: flex;
429+
align-items: flex-start;
430+
gap: 8px;
431+
margin: 8px 0;
432+
cursor: pointer;
433+
font-weight: normal;
434+
}
435+
436+
.geolocation-acknowledge-checkbox {
437+
margin-top: 3px;
438+
flex-shrink: 0;
439+
}
440+
427441
.geolocation-skipped-msg {
428442
margin: 4px 0;
429443
font-style: italic;

webapp/src/js/enketo/widgets/geolocation-widget.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,21 @@ class GeolocationWidget extends Widget {
9898
this._startCapture();
9999
});
100100

101+
const $acknowledgeCheckbox = $('<input type="checkbox" class="geolocation-acknowledge-checkbox ignore">');
102+
const $acknowledgeSpan = $('<span class="geolocation-acknowledge-text">');
103+
const $acknowledgeLabel = $('<label class="geolocation-acknowledge-label">').append($acknowledgeCheckbox, $acknowledgeSpan);
104+
101105
const $skipBtn = $('<button type="button" class="btn btn-default geolocation-skip-btn">');
106+
$skipBtn.prop('disabled', true);
107+
108+
$acknowledgeCheckbox.on('change', event => {
109+
event.stopPropagation();
110+
$skipBtn.prop('disabled', !$acknowledgeCheckbox.prop('checked'));
111+
});
112+
102113
$skipBtn.on('click', () => {
103114
$retryBtn.remove();
115+
$acknowledgeLabel.remove();
104116
$skipBtn.remove();
105117

106118
const $skippedMsg = $('<p class="geolocation-skipped-msg">');
@@ -109,13 +121,14 @@ class GeolocationWidget extends Widget {
109121

110122
$(this.element).val('skipped').trigger('change');
111123
});
112-
$status.append($retryBtn, $skipBtn);
124+
$status.append($retryBtn, $acknowledgeLabel, $skipBtn);
113125

114126
globalThis.CHTCore.Translate.get('geolocation.result.label').then(text => $resultLabel.text(text));
115127
globalThis.CHTCore.Translate.get('geolocation.failure').then(text => $resultText.text(text));
116128
globalThis.CHTCore.Translate.get('geolocation.retry')
117129
.then(text => $retryBtn.append($('<span class="geolocation-btn-label">').text(text)));
118-
globalThis.CHTCore.Translate.get('geolocation.skip').then(text => $skipBtn.text(text));
130+
globalThis.CHTCore.Translate.get('geolocation.skip.button').then(text => $skipBtn.text(text));
131+
globalThis.CHTCore.Translate.get('geolocation.skip.acknowledge').then(text => $acknowledgeSpan.text(text));
119132
} else {
120133
$bar.addClass('geolocation-progress-success');
121134

webapp/tests/karma/js/enketo/widgets/geolocation-widget.spec.ts

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ describe('Enketo: Geolocation Widget', () => {
287287
expect(changeHandler.callCount).to.equal(1);
288288
});
289289

290-
it('should show retry and skip buttons when GPS acquisition fails', async () => {
290+
it('should show retry button, acknowledgement checkbox, and skip button when GPS acquisition fails', async () => {
291291
const promise = Promise.resolve({ code: -2, message: 'Geolocation timeout exceeded' });
292292
window.CHTCore.Geolocation = { currentPromise: promise };
293293
buildHtml();
@@ -302,9 +302,78 @@ describe('Enketo: Geolocation Widget', () => {
302302
await promise;
303303

304304
expect(container.querySelector('.geolocation-retry-btn')).to.not.be.null;
305+
expect(container.querySelector('.geolocation-acknowledge-checkbox')).to.not.be.null;
305306
expect(container.querySelector('.geolocation-skip-btn')).to.not.be.null;
306307
});
307308

309+
it('should disable the "Continue without location" button immediately after GPS failure', async () => {
310+
const promise = Promise.resolve({ code: -2, message: 'Geolocation timeout exceeded' });
311+
window.CHTCore.Geolocation = { currentPromise: promise };
312+
buildHtml();
313+
const widget = createWidget();
314+
widget._isGeolocationAvailable = () => true;
315+
widget._init();
316+
selectHomeContext();
317+
318+
const container = document.querySelector('#geolocation-widget-test .or-appearance-geolocation-capture')!;
319+
(container.querySelector('.geolocation-capture-btn') as HTMLElement).click();
320+
321+
await promise;
322+
323+
const skipBtn = container.querySelector('.geolocation-skip-btn') as HTMLButtonElement;
324+
expect(skipBtn).to.not.be.null;
325+
expect(skipBtn.disabled).to.be.true;
326+
});
327+
328+
it('should enable the "Continue without location" button when the acknowledgement checkbox is checked', async () => {
329+
const promise = Promise.resolve({ code: -2, message: 'Geolocation timeout exceeded' });
330+
window.CHTCore.Geolocation = { currentPromise: promise };
331+
buildHtml();
332+
const widget = createWidget();
333+
widget._isGeolocationAvailable = () => true;
334+
widget._init();
335+
selectHomeContext();
336+
337+
const container = document.querySelector('#geolocation-widget-test .or-appearance-geolocation-capture')!;
338+
(container.querySelector('.geolocation-capture-btn') as HTMLElement).click();
339+
340+
await promise;
341+
342+
const checkbox = container.querySelector('.geolocation-acknowledge-checkbox') as HTMLInputElement;
343+
const skipBtn = container.querySelector('.geolocation-skip-btn') as HTMLButtonElement;
344+
345+
checkbox.checked = true;
346+
$(checkbox).trigger('change');
347+
348+
expect(skipBtn.disabled).to.be.false;
349+
});
350+
351+
it('should re-disable the "Continue without location" button when the acknowledgement checkbox is unchecked', async () => {
352+
const promise = Promise.resolve({ code: -2, message: 'Geolocation timeout exceeded' });
353+
window.CHTCore.Geolocation = { currentPromise: promise };
354+
buildHtml();
355+
const widget = createWidget();
356+
widget._isGeolocationAvailable = () => true;
357+
widget._init();
358+
selectHomeContext();
359+
360+
const container = document.querySelector('#geolocation-widget-test .or-appearance-geolocation-capture')!;
361+
(container.querySelector('.geolocation-capture-btn') as HTMLElement).click();
362+
363+
await promise;
364+
365+
const checkbox = container.querySelector('.geolocation-acknowledge-checkbox') as HTMLInputElement;
366+
const skipBtn = container.querySelector('.geolocation-skip-btn') as HTMLButtonElement;
367+
368+
checkbox.checked = true;
369+
$(checkbox).trigger('change');
370+
expect(skipBtn.disabled).to.be.false;
371+
372+
checkbox.checked = false;
373+
$(checkbox).trigger('change');
374+
expect(skipBtn.disabled).to.be.true;
375+
});
376+
308377
it('should include a map icon in the retry button', async () => {
309378
const promise = Promise.resolve({ code: -2, message: 'Geolocation timeout exceeded' });
310379
window.CHTCore.Geolocation = { currentPromise: promise };
@@ -359,13 +428,17 @@ describe('Enketo: Geolocation Widget', () => {
359428
const changeHandler = sinon.stub();
360429
$(widget.element).on('change', changeHandler);
361430

431+
const checkbox = container.querySelector('.geolocation-acknowledge-checkbox') as HTMLInputElement;
432+
checkbox.checked = true;
433+
$(checkbox).trigger('change');
434+
362435
(container.querySelector('.geolocation-skip-btn') as HTMLElement).click();
363436

364437
expect(widget.element.value).to.equal('skipped');
365438
expect(changeHandler.callCount).to.equal(1);
366439
});
367440

368-
it('should remove retry and skip buttons and show a confirmation message when skip is clicked', async () => {
441+
it('should remove retry, acknowledge, and skip elements and show confirmation when skip is clicked', async () => {
369442
const promise = Promise.resolve({ code: -2, message: 'Geolocation timeout exceeded' });
370443
window.CHTCore.Geolocation = { currentPromise: promise };
371444
buildHtml();
@@ -379,9 +452,14 @@ describe('Enketo: Geolocation Widget', () => {
379452

380453
await promise;
381454

455+
const checkbox = container.querySelector('.geolocation-acknowledge-checkbox') as HTMLInputElement;
456+
checkbox.checked = true;
457+
$(checkbox).trigger('change');
458+
382459
(container.querySelector('.geolocation-skip-btn') as HTMLElement).click();
383460

384461
expect(container.querySelector('.geolocation-retry-btn')).to.be.null;
462+
expect(container.querySelector('.geolocation-acknowledge-label')).to.be.null;
385463
expect(container.querySelector('.geolocation-skip-btn')).to.be.null;
386464
expect(container.querySelector('.geolocation-skipped-msg')).to.not.be.null;
387465
});

0 commit comments

Comments
 (0)