define OtpDialog
This commit is contained in:
29
website/public/css/otp.css
Normal file
29
website/public/css/otp.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.otp-inputs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--spacing-inline-sm);
|
||||
justify-content: center;
|
||||
|
||||
input {
|
||||
border: none;
|
||||
border-block-end: 2px solid var(--colour-grey-fg);
|
||||
font-size: var(--font-size-lg);
|
||||
text-align: center;
|
||||
width: var(--font-size-lg);
|
||||
}
|
||||
}
|
||||
|
||||
.otp-form {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-block-sm);
|
||||
|
||||
> * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
max-width: max-content;
|
||||
}
|
||||
}
|
||||
31
website/src/components/OtpDialog.astro
Normal file
31
website/src/components/OtpDialog.astro
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
---
|
||||
|
||||
<script src="../scripts/otp-form-wc.ts"></script>
|
||||
<link rel="stylesheet" href="/css/otp.css" />
|
||||
|
||||
<dialog class="otp-dialog">
|
||||
<otp-form>
|
||||
<form class="otp-form" class="otp-form">
|
||||
<p>
|
||||
I've sent a six-digit code to <span class="otp-recipient">your email address</span>.
|
||||
Let me know what it is, so I can confirm this really is your email address. The
|
||||
code will be valid <span class="otp-valid-until">for five minutes</span>.
|
||||
</p>
|
||||
|
||||
<p hidden class="error"/>
|
||||
|
||||
<div class="otp-inputs">
|
||||
<input maxlength="1" name="1" required autocapitalize="characters" autocomplete="one-time-code" aria-label="First digit of one-time passcode" autofocus>
|
||||
<input maxlength="1" name="2" required autocapitalize="characters" autocomplete="one-time-code" aria-label="Second digit of one-time passcode">
|
||||
<input maxlength="1" name="3" required autocapitalize="characters" autocomplete="one-time-code" aria-label="Third digit of one-time passcode">
|
||||
<input maxlength="1" name="4" required autocapitalize="characters" autocomplete="one-time-code" aria-label="Fourth digit of one-time passcode">
|
||||
<input maxlength="1" name="5" required autocapitalize="characters" autocomplete="one-time-code" aria-label="Fifth digit of one-time passcode">
|
||||
<input maxlength="1" name="6" required autocapitalize="characters" autocomplete="one-time-code" aria-label="Sixth digit of one-time passcode">
|
||||
</div>
|
||||
|
||||
<input type="submit" value="Verify">
|
||||
<button disabled class="resend-button">Resend (60s)</button>
|
||||
</form>
|
||||
</otp-form>
|
||||
</dialog>
|
||||
68
website/src/scripts/otp-form-wc.ts
Normal file
68
website/src/scripts/otp-form-wc.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
class OtpForm extends HTMLElement {
|
||||
observer?: MutationObserver;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.observer = new MutationObserver(() => {
|
||||
this.clearInputs();
|
||||
this.configureInputs();
|
||||
});
|
||||
this.observer.observe(this, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.observer?.disconnect();
|
||||
}
|
||||
|
||||
clearInputs() {
|
||||
console.log("clearing all inputs");
|
||||
const inputs = this.querySelectorAll(
|
||||
'input:not([type="submit"])',
|
||||
) as NodeListOf<HTMLInputElement>;
|
||||
for (const input of inputs) {
|
||||
input.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
configureInputs() {
|
||||
this.observer?.disconnect();
|
||||
const inputs = this.querySelectorAll(
|
||||
'input:not([type="submit"])',
|
||||
) as NodeListOf<HTMLInputElement>;
|
||||
const form = this.querySelector("form") as HTMLFormElement;
|
||||
|
||||
for (const input of inputs) {
|
||||
input.addEventListener("focus", () => {
|
||||
input.select();
|
||||
});
|
||||
|
||||
input.addEventListener("input", () => {
|
||||
if (input.value.length > 0) {
|
||||
input.value = input.value.slice(0, 1).toLocaleUpperCase();
|
||||
const nextInput = input.nextElementSibling as HTMLInputElement;
|
||||
if (nextInput) {
|
||||
nextInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const submitButton = form.querySelector(
|
||||
'input[type="submit"]',
|
||||
) as HTMLInputElement;
|
||||
submitButton.focus();
|
||||
let areAllInputsEntered = true;
|
||||
inputs.forEach((input) => {
|
||||
areAllInputsEntered = areAllInputsEntered && input.value.length > 0;
|
||||
});
|
||||
if (areAllInputsEntered) {
|
||||
form.requestSubmit(submitButton);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("otp-form", OtpForm);
|
||||
Reference in New Issue
Block a user