Skip to content

Commit 5280004

Browse files
authored
chore: otp ux (#481)
* chore: otp ux prevent form resubmission and improve backspace function * chore: update test update test assertion for disabling
1 parent d26ccda commit 5280004

File tree

4 files changed

+43
-23
lines changed

4 files changed

+43
-23
lines changed

docker/otp-provider/e2e/otp.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,12 @@ test('OTP Attempts Limit', async ({ page }, testInfo) => {
9191
await expect(page.locator('#otp-error')).toMatchAriaSnapshot(
9292
`- text: You've tried too many times. Please send a new code.`,
9393
);
94-
await expect(page.getByRole('button', { name: 'Continue' })).toBeDisabled();
94+
// Assert all inputs are disabled
95+
const inputs = await page.$$('input');
96+
for (const input of inputs) {
97+
const isDisabled = await input.isDisabled();
98+
expect(isDisabled).toBe(true);
99+
}
95100
});
96101

97102
test('OTP Success', async ({ page }, testInfo) => {

docker/otp-provider/src/client/otp.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ document.addEventListener('DOMContentLoaded', () => {
77
const waitTimeElement = document.getElementById('wait-time-text') as HTMLElement | null;
88
const codeContainer = document.getElementById('new-code-text');
99
const loginButton = document.getElementById('login-button') as HTMLButtonElement;
10+
let submitting = false;
11+
if (loginButton) loginButton.remove();
12+
1013
if (!form || !errorEl) return;
1114

1215
form.setAttribute('novalidate', '');
@@ -20,15 +23,19 @@ document.addEventListener('DOMContentLoaded', () => {
2023

2124
form.addEventListener('submit', (e) => {
2225
e.preventDefault();
26+
if (submitting) {
27+
return;
28+
}
29+
submitting = true;
2330
const codes = digitInputs.map((input) => input.value).filter((val) => val !== '');
2431
const [, error] = otpValidator(codes);
2532
if (error) {
26-
setFormError(errorEl, loginButton, error as string);
33+
setFormError(errorEl, error as string);
34+
submitting = false;
2735
const firstEmptyInput = digitInputs.find((input) => input?.value === '');
2836
if (firstEmptyInput) firstEmptyInput.focus();
2937
else digitInputs[0].focus();
3038
} else {
31-
loginButton.disabled = true;
3239
form.submit();
3340
}
3441
});
@@ -41,9 +48,9 @@ document.addEventListener('DOMContentLoaded', () => {
4148
const value = e.data;
4249
if (!value) return;
4350

44-
clearFormError(errorEl, loginButton);
51+
clearFormError(errorEl);
4552
if (!otpValidDigits.includes(value)) {
46-
setFormError(errorEl, loginButton, errors.OTP_TYPES);
53+
setFormError(errorEl, errors.OTP_TYPES);
4754
(e.target as HTMLInputElement)?.focus();
4855
return;
4956
}
@@ -54,7 +61,7 @@ document.addEventListener('DOMContentLoaded', () => {
5461
else {
5562
const codes = digitInputs.map((input) => input.value);
5663
const [, error] = otpValidator(codes);
57-
if (error) setFormError(errorEl, loginButton, error as string);
64+
if (error) setFormError(errorEl, error as string);
5865
else form.requestSubmit();
5966
}
6067
});
@@ -77,7 +84,7 @@ document.addEventListener('DOMContentLoaded', () => {
7784

7885
const [, error] = otpValidator(fullCode);
7986
if (error) {
80-
setFormError(errorEl, loginButton, error as string);
87+
setFormError(errorEl, error as string);
8188
return;
8289
}
8390

@@ -90,12 +97,15 @@ document.addEventListener('DOMContentLoaded', () => {
9097
e.preventDefault();
9198
form.requestSubmit();
9299
}
93-
if (e.key === 'Backspace' && input.value === '') {
100+
if (e.key === 'Backspace') {
94101
e.preventDefault();
95-
clearFormError(errorEl, loginButton);
96-
if (i > 0) {
102+
clearFormError(errorEl);
103+
// When passed the first input and hitting backspace on an empty field, want to clear and focus the previous input
104+
if (input.value === '' && i > 0) {
97105
digitInputs[i - 1].value = '';
98106
digitInputs[i - 1].focus();
107+
} else {
108+
input.value = '';
99109
}
100110
}
101111
});

docker/otp-provider/src/client/shared.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,28 +55,33 @@ export const getUID = () => location.pathname.split('/')[location.pathname.split
5555
/**
5656
* Set the form error message.
5757
* @param errorEl The HTML Element in which to render the message.
58-
* @param submitButton The HTML Button Element to submit the form.
5958
* @param errorText The error message to display.
59+
* @param submitButton The HTML Button Element to submit the form.
6060
*/
61-
export const setFormError = (errorEl: HTMLElement, submitButton: HTMLButtonElement, errorText: string) => {
61+
export const setFormError = (errorEl: HTMLElement, errorText: string, submitButton?: HTMLButtonElement,) => {
6262
clearFormError(errorEl, submitButton);
63-
submitButton.disabled = true;
64-
const img = document.createElement('img');
65-
img.src = '/img/caution.svg';
66-
img.alt = 'Caution';
67-
img.className = 'w-16 h-16';
68-
submitButton.appendChild(img);
6963
errorEl.textContent = errorText;
64+
65+
if (submitButton) {
66+
submitButton.disabled = true;
67+
const img = document.createElement('img');
68+
img.src = '/img/caution.svg';
69+
img.alt = 'Caution';
70+
img.className = 'w-16 h-16';
71+
submitButton.appendChild(img);
72+
}
7073
};
7174

7275
/**
7376
* Clear form errors.
7477
* @param errorEl The HTML Element in which to render the message.
7578
* @param submitButton The HTML Button Element to submit the form.
7679
*/
77-
export const clearFormError = (errorEl: HTMLElement, submitButton: HTMLButtonElement) => {
80+
export const clearFormError = (errorEl: HTMLElement, submitButton?: HTMLButtonElement) => {
7881
errorEl.textContent = '';
79-
submitButton.disabled = false;
80-
const cautionImg = submitButton.querySelector('img');
81-
if (cautionImg) cautionImg.remove();
82+
if (submitButton) {
83+
submitButton.disabled = false;
84+
const cautionImg = submitButton.querySelector('img');
85+
if (cautionImg) cautionImg.remove();
86+
}
8287
};

docker/otp-provider/src/client/signin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ document.addEventListener('DOMContentLoaded', () => {
2121
const [emailValid, error] = emailValidator(emailContent);
2222

2323
if (!emailValid) {
24-
setFormError(errorField, submitButton, error as string);
24+
setFormError(errorField, error as string, submitButton);
2525
return;
2626
}
2727
form.submit();

0 commit comments

Comments
 (0)