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