Changed 2FA page to input 6 characters and auto submit.
This commit is contained in:
parent
18a14bbd01
commit
44d7253367
@ -11,29 +11,120 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="edit-panel">
|
||||
<form asp-action="TwoFactor" method="post" class="asset-create-form">
|
||||
<section class="edit-panel two-factor-card">
|
||||
<form asp-action="TwoFactor" method="post" class="asset-create-form two-factor-body" data-two-factor-form>
|
||||
<input asp-for="ReturnUrl" type="hidden" />
|
||||
<input asp-for="Code" type="hidden" data-two-factor-code />
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-warning"></div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" asp-for="Code">Authenticator code</label>
|
||||
<input class="form-control" asp-for="Code" autocomplete="one-time-code" />
|
||||
<span class="text-danger" asp-validation-for="Code"></span>
|
||||
<div class="two-factor-code-group">
|
||||
<label class="form-label two-factor-label" for="twoFactorDigit1">Authenticator code</label>
|
||||
<div class="two-factor-inputs" role="group" aria-label="Authenticator code">
|
||||
@for (var i = 1; i <= 6; i++)
|
||||
{
|
||||
<input class="form-control two-factor-digit"
|
||||
id="twoFactorDigit@(i)"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
pattern="[0-9]*"
|
||||
maxlength="1"
|
||||
autocomplete="one-time-code"
|
||||
aria-label="Digit @i of 6"
|
||||
data-two-factor-digit />
|
||||
}
|
||||
</div>
|
||||
<span class="text-danger" asp-validation-for="Code"></span>
|
||||
</div>
|
||||
|
||||
<div class="button-row mt-3">
|
||||
<div class="two-factor-actions">
|
||||
<button class="btn btn-primary" type="submit">Continue</button>
|
||||
<a class="btn btn-outline-secondary" asp-action="Login">Back to Login</a>
|
||||
</div>
|
||||
<p class="mt-3 mb-0">
|
||||
<a asp-action="TwoFactorRecovery" asp-route-returnUrl="@Model.ReturnUrl">Can't access your authenticator app? Use a recovery code.</a>
|
||||
<p class="two-factor-help">
|
||||
<span>Can't access your authenticator app?</span>
|
||||
<a asp-action="TwoFactorRecovery" asp-route-returnUrl="@Model.ReturnUrl">Use a recovery code.</a>
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@section Scripts {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
<script>
|
||||
(() => {
|
||||
const form = document.querySelector("[data-two-factor-form]");
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hiddenCode = form.querySelector("[data-two-factor-code]");
|
||||
const digits = Array.from(form.querySelectorAll("[data-two-factor-digit]"));
|
||||
let hasSubmitted = false;
|
||||
|
||||
const codeValue = () => digits.map(input => input.value).join("");
|
||||
const syncCode = () => {
|
||||
hiddenCode.value = codeValue();
|
||||
};
|
||||
const isComplete = () => digits.every(input => input.value.length === 1);
|
||||
const submitIfComplete = () => {
|
||||
syncCode();
|
||||
if (isComplete() && !hasSubmitted) {
|
||||
hasSubmitted = true;
|
||||
form.requestSubmit();
|
||||
}
|
||||
};
|
||||
const fillFrom = (startIndex, value) => {
|
||||
const numbers = value.replace(/\D/g, "").slice(0, digits.length - startIndex);
|
||||
numbers.split("").forEach((number, offset) => {
|
||||
digits[startIndex + offset].value = number;
|
||||
});
|
||||
|
||||
const nextIndex = Math.min(startIndex + numbers.length, digits.length - 1);
|
||||
digits[nextIndex].focus();
|
||||
submitIfComplete();
|
||||
};
|
||||
|
||||
digits.forEach((input, index) => {
|
||||
input.addEventListener("input", () => {
|
||||
if (input.value.length > 1) {
|
||||
fillFrom(index, input.value);
|
||||
return;
|
||||
}
|
||||
|
||||
input.value = input.value.replace(/\D/g, "");
|
||||
syncCode();
|
||||
|
||||
if (input.value && index < digits.length - 1) {
|
||||
digits[index + 1].focus();
|
||||
}
|
||||
|
||||
submitIfComplete();
|
||||
});
|
||||
|
||||
input.addEventListener("keydown", event => {
|
||||
if (event.key === "Backspace" && !input.value && index > 0) {
|
||||
digits[index - 1].focus();
|
||||
digits[index - 1].value = "";
|
||||
syncCode();
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener("paste", event => {
|
||||
event.preventDefault();
|
||||
fillFrom(index, event.clipboardData.getData("text"));
|
||||
});
|
||||
|
||||
input.addEventListener("focus", () => input.select());
|
||||
});
|
||||
|
||||
form.addEventListener("submit", () => {
|
||||
syncCode();
|
||||
});
|
||||
|
||||
digits.forEach(input => {
|
||||
input.value = "";
|
||||
});
|
||||
syncCode();
|
||||
digits[0].focus();
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
|
||||
@ -205,7 +205,7 @@
|
||||
<div class="marketing-container">
|
||||
<div class="section-heading">
|
||||
<p class="marketing-eyebrow">Loved by story-obsessed writers</p>
|
||||
<h2>Authors use PlotDirector to feel oriented again.</h2>
|
||||
<h2>Authors use PlotDirector to feel in control again.</h2>
|
||||
</div>
|
||||
<div class="testimonial-grid">
|
||||
<figure>
|
||||
|
||||
@ -896,6 +896,121 @@ a:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.two-factor-card {
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
padding: clamp(1.75rem, 4vw, 2.5rem);
|
||||
}
|
||||
|
||||
.two-factor-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 1.5rem;
|
||||
border-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.two-factor-body .alert {
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.two-factor-code-group {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.two-factor-label {
|
||||
margin-bottom: 0;
|
||||
font-weight: 700;
|
||||
color: var(--plotline-text);
|
||||
}
|
||||
|
||||
.two-factor-inputs {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.two-factor-digit {
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 760;
|
||||
line-height: 1;
|
||||
border-radius: 0.75rem;
|
||||
border-color: rgba(122, 77, 44, 0.28);
|
||||
background: var(--plotline-surface-elevated);
|
||||
color: var(--plotline-text);
|
||||
}
|
||||
|
||||
.two-factor-digit:focus {
|
||||
border-color: var(--plotline-accent);
|
||||
box-shadow:
|
||||
0 0 0 0.22rem rgba(122, 77, 44, 0.2),
|
||||
0 10px 22px rgba(62, 45, 28, 0.1);
|
||||
}
|
||||
|
||||
.two-factor-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.two-factor-actions .btn {
|
||||
min-width: 8.75rem;
|
||||
}
|
||||
|
||||
.two-factor-help {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: 0.2rem;
|
||||
margin: 0;
|
||||
font-size: 0.95rem;
|
||||
color: var(--plotline-text-muted);
|
||||
}
|
||||
|
||||
.two-factor-help a {
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.two-factor-code-group .text-danger {
|
||||
min-height: 1.4rem;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.two-factor-card {
|
||||
padding: 1.35rem;
|
||||
}
|
||||
|
||||
.two-factor-inputs {
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.two-factor-digit {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
font-size: 1.45rem;
|
||||
}
|
||||
|
||||
.two-factor-actions {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.two-factor-actions .btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.plotline-confirm-modal {
|
||||
background: #fffaf2;
|
||||
border: 1px solid rgba(84, 68, 48, 0.16);
|
||||
|
||||
2
PlotLine/wwwroot/css/plotline-theme.min.css
vendored
2
PlotLine/wwwroot/css/plotline-theme.min.css
vendored
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user