refactor: share email code confirmation screen
This commit is contained in:
@@ -143,34 +143,17 @@
|
|||||||
0 0 26px color-mix(in srgb, var(--accent) 42%, transparent);
|
0 0 26px color-mix(in srgb, var(--accent) 42%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-email-dialog-card {
|
.email-code-fullscreen {
|
||||||
display: grid;
|
|
||||||
grid-template-rows: auto minmax(0, 1fr);
|
|
||||||
height: min(100%, 560px);
|
|
||||||
max-height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-email-dialog-card .dialog-head {
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-email-dialog-card .payment-dialog-body {
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.password-code-fullscreen {
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 220;
|
z-index: 220;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
animation: password-code-fullscreen-enter 0.2s ease-out both;
|
animation: email-code-fullscreen-enter 0.2s ease-out both;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes password-code-fullscreen-enter {
|
@keyframes email-code-fullscreen-enter {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2732,8 +2732,7 @@ a {
|
|||||||
width: min(100%, 560px);
|
width: min(100%, 560px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.payment-dialog-card,
|
.payment-dialog-card {
|
||||||
.link-email-dialog-card {
|
|
||||||
width: min(100%, 640px);
|
width: min(100%, 640px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,13 @@
|
|||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
CircleX,
|
CircleX,
|
||||||
LockKeyhole,
|
LockKeyhole,
|
||||||
RefreshCw,
|
|
||||||
TriangleAlert,
|
TriangleAlert,
|
||||||
} from "$components/ui/icons.js";
|
} from "$components/ui/icons.js";
|
||||||
import { Tooltip } from "$components/ui/primitives.js";
|
import { Tooltip } from "$components/ui/primitives.js";
|
||||||
|
|
||||||
import Button from "$components/ui/button.svelte";
|
import Button from "$components/ui/button.svelte";
|
||||||
import Dialog from "$components/ui/dialog.svelte";
|
import Dialog from "$components/ui/dialog.svelte";
|
||||||
|
import EmailCodeScreen from "./auth/EmailCodeScreen.svelte";
|
||||||
import Input from "$components/ui/input.svelte";
|
import Input from "$components/ui/input.svelte";
|
||||||
import {
|
import {
|
||||||
EmptyCard,
|
EmptyCard,
|
||||||
@@ -391,139 +391,77 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{#if setPasswordOpen && setPasswordPending}
|
{#if setPasswordOpen && setPasswordPending}
|
||||||
<div class="password-code-fullscreen" role="dialog" aria-modal="true">
|
<div class="email-code-fullscreen" role="dialog" aria-modal="true">
|
||||||
<div class="phone-screen auth-screen">
|
<EmailCodeScreen
|
||||||
<header class="screen-head center-title">
|
bind:code={setPasswordCode}
|
||||||
<Button
|
email={setPasswordEmail || ""}
|
||||||
variant="icon"
|
busy={setPasswordBusy}
|
||||||
size="icon"
|
resendCooldown={setPasswordResendCooldown}
|
||||||
onclick={closeSetPasswordDialog}
|
status={setPasswordStatus}
|
||||||
aria-label={t("wa_back")}
|
isError={setPasswordIsError}
|
||||||
>
|
{t}
|
||||||
<ArrowLeft size={19} />
|
onBack={closeSetPasswordDialog}
|
||||||
</Button>
|
onConfirm={confirmSetPassword}
|
||||||
<div>
|
onResend={requestSetPasswordCode}
|
||||||
<h1>{t("wa_email_verification_title")}</h1>
|
/>
|
||||||
<p>{t("wa_email_sent_to", { email: setPasswordEmail || "" })}</p>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
<span></span>
|
|
||||||
</header>
|
{#if linkEmailOpen && linkEmailPending}
|
||||||
<div class="otp-wrap">
|
<div class="email-code-fullscreen" role="dialog" aria-modal="true">
|
||||||
<label class="otp-input-wrap">
|
<EmailCodeScreen
|
||||||
<input
|
bind:code={linkEmailCode}
|
||||||
bind:value={setPasswordCode}
|
email={linkEmailPending}
|
||||||
inputmode="numeric"
|
busy={linkEmailBusy}
|
||||||
autocomplete="one-time-code"
|
resendCooldown={linkEmailResendCooldown}
|
||||||
maxlength="6"
|
status={linkEmailStatus}
|
||||||
aria-label={t("wa_email_code_aria")}
|
isError={linkEmailIsError}
|
||||||
/>
|
{t}
|
||||||
<span class="otp-slots" aria-hidden="true">
|
onBack={closeLinkEmailDialog}
|
||||||
{#each Array.from({ length: 6 }) as _, index}
|
onConfirm={verifyLinkEmailCode}
|
||||||
<span class:filled={setPasswordCode[index]}>{setPasswordCode[index] || ""}</span>
|
onResend={requestLinkEmailCode}
|
||||||
{/each}
|
/>
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<Button class="wide" onclick={confirmSetPassword} disabled={setPasswordBusy}>
|
|
||||||
{t("wa_confirm")}
|
|
||||||
</Button>
|
|
||||||
{#if setPasswordStatus}
|
|
||||||
<StatusMessage error={setPasswordIsError}>{setPasswordStatus}</StatusMessage>
|
|
||||||
{/if}
|
|
||||||
<button
|
|
||||||
class="link-button"
|
|
||||||
type="button"
|
|
||||||
onclick={requestSetPasswordCode}
|
|
||||||
disabled={setPasswordBusy || setPasswordResendCooldown > 0}
|
|
||||||
>
|
|
||||||
<RefreshCw size={15} />
|
|
||||||
{setPasswordResendCooldown > 0
|
|
||||||
? t("wa_auth_resend_wait", { seconds: setPasswordResendCooldown })
|
|
||||||
: t("wa_resend_code")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
open={linkEmailOpen}
|
open={linkEmailOpen && !linkEmailPending}
|
||||||
title={t("wa_link_email_modal_title")}
|
title={t("wa_link_email_modal_title")}
|
||||||
description={linkEmailPending
|
description={t("wa_link_email_modal_desc")}
|
||||||
? t("wa_email_sent_to", { email: linkEmailPending })
|
|
||||||
: t("wa_link_email_modal_desc")}
|
|
||||||
closeLabel={t("wa_close")}
|
closeLabel={t("wa_close")}
|
||||||
onclose={closeLinkEmailDialog}
|
onclose={closeLinkEmailDialog}
|
||||||
class={`payment-dialog-card${linkEmailPending ? " link-email-dialog-card" : ""}`}
|
class="payment-dialog-card"
|
||||||
>
|
>
|
||||||
<div class="payment-dialog-body">
|
<div class="payment-dialog-body">
|
||||||
{#if !linkEmailPending}
|
<div class="field-error-wrap">
|
||||||
<div class="field-error-wrap">
|
<Tooltip.Root open={Boolean(linkEmailFieldError)}>
|
||||||
<Tooltip.Root open={Boolean(linkEmailFieldError)}>
|
<Input
|
||||||
<Input
|
bind:value={linkEmailValue}
|
||||||
bind:value={linkEmailValue}
|
type="email"
|
||||||
type="email"
|
placeholder={t("wa_email_placeholder")}
|
||||||
placeholder={t("wa_email_placeholder")}
|
autocomplete="email"
|
||||||
autocomplete="email"
|
class={linkEmailFieldError ? "input-error" : ""}
|
||||||
class={linkEmailFieldError ? "input-error" : ""}
|
on:input={() => (linkEmailFieldError = "")}
|
||||||
on:input={() => (linkEmailFieldError = "")}
|
/>
|
||||||
/>
|
{#if linkEmailFieldError}
|
||||||
{#if linkEmailFieldError}
|
<Tooltip.Trigger class="field-error-trigger" aria-label={linkEmailFieldError}>
|
||||||
<Tooltip.Trigger class="field-error-trigger" aria-label={linkEmailFieldError}>
|
<span class="field-error-icon" aria-hidden="true"><TriangleAlert size={18} /></span>
|
||||||
<span class="field-error-icon" aria-hidden="true"><TriangleAlert size={18} /></span>
|
</Tooltip.Trigger>
|
||||||
</Tooltip.Trigger>
|
{/if}
|
||||||
{/if}
|
{#if linkEmailFieldError}
|
||||||
{#if linkEmailFieldError}
|
<Tooltip.Portal>
|
||||||
<Tooltip.Portal>
|
<Tooltip.Content class="field-error-tooltip">{linkEmailFieldError}</Tooltip.Content>
|
||||||
<Tooltip.Content class="field-error-tooltip">{linkEmailFieldError}</Tooltip.Content>
|
</Tooltip.Portal>
|
||||||
</Tooltip.Portal>
|
{/if}
|
||||||
{/if}
|
</Tooltip.Root>
|
||||||
</Tooltip.Root>
|
</div>
|
||||||
</div>
|
<Button
|
||||||
<Button
|
class="wide bottom-action payment-submit-button"
|
||||||
class="wide bottom-action payment-submit-button"
|
onclick={requestLinkEmailCode}
|
||||||
onclick={requestLinkEmailCode}
|
disabled={linkEmailBusy}
|
||||||
disabled={linkEmailBusy}
|
>
|
||||||
>
|
{t("wa_send_code_email")}
|
||||||
{t("wa_send_code_email")}
|
</Button>
|
||||||
</Button>
|
|
||||||
{:else}
|
|
||||||
<div class="link-email-code-layout">
|
|
||||||
<div class="otp-wrap link-email-code-center">
|
|
||||||
<label class="otp-input-wrap">
|
|
||||||
<input
|
|
||||||
bind:value={linkEmailCode}
|
|
||||||
inputmode="numeric"
|
|
||||||
autocomplete="one-time-code"
|
|
||||||
maxlength="6"
|
|
||||||
aria-label={t("wa_email_code_aria")}
|
|
||||||
/>
|
|
||||||
<span class="otp-slots" aria-hidden="true">
|
|
||||||
{#each Array.from({ length: 6 }) as _, index}
|
|
||||||
<span class:filled={linkEmailCode[index]}>{linkEmailCode[index] || ""}</span>
|
|
||||||
{/each}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<Button
|
|
||||||
class="wide bottom-action payment-submit-button"
|
|
||||||
onclick={verifyLinkEmailCode}
|
|
||||||
disabled={linkEmailBusy}
|
|
||||||
>
|
|
||||||
{t("wa_confirm")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="link-button link-email-resend"
|
|
||||||
type="button"
|
|
||||||
onclick={requestLinkEmailCode}
|
|
||||||
disabled={linkEmailBusy || linkEmailResendCooldown > 0}
|
|
||||||
>
|
|
||||||
<RefreshCw size={15} />
|
|
||||||
{linkEmailResendCooldown > 0
|
|
||||||
? t("wa_auth_resend_wait", { seconds: linkEmailResendCooldown })
|
|
||||||
: t("wa_resend_code")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if linkEmailStatus}
|
{#if linkEmailStatus}
|
||||||
<StatusMessage error={linkEmailIsError}>{linkEmailStatus}</StatusMessage>
|
<StatusMessage error={linkEmailIsError}>{linkEmailStatus}</StatusMessage>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import {
|
import { LockKeyhole, Mail, Send, TriangleAlert } from "$components/ui/icons.js";
|
||||||
ArrowLeft,
|
|
||||||
LockKeyhole,
|
|
||||||
Mail,
|
|
||||||
RefreshCw,
|
|
||||||
Send,
|
|
||||||
TriangleAlert,
|
|
||||||
} from "$components/ui/icons.js";
|
|
||||||
import { Tooltip } from "$components/ui/primitives.js";
|
import { Tooltip } from "$components/ui/primitives.js";
|
||||||
|
|
||||||
import Button from "$components/ui/button.svelte";
|
import Button from "$components/ui/button.svelte";
|
||||||
import BrandMark from "$lib/webapp/BrandMark.svelte";
|
import BrandMark from "$lib/webapp/BrandMark.svelte";
|
||||||
|
import EmailCodeScreen from "./EmailCodeScreen.svelte";
|
||||||
import Input from "$components/ui/input.svelte";
|
import Input from "$components/ui/input.svelte";
|
||||||
import Spinner from "$components/ui/spinner.svelte";
|
import Spinner from "$components/ui/spinner.svelte";
|
||||||
import { StatusMessage } from "$components/patterns/webapp/index.js";
|
import { StatusMessage } from "$components/patterns/webapp/index.js";
|
||||||
@@ -55,52 +49,21 @@
|
|||||||
$: authCardHeight = authPanelHeight ? `${authPanelHeight}px` : undefined;
|
$: authCardHeight = authPanelHeight ? `${authPanelHeight}px` : undefined;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="phone-screen auth-screen">
|
{#if screen === "code"}
|
||||||
{#if screen === "code"}
|
<EmailCodeScreen
|
||||||
<header class="screen-head center-title">
|
bind:code={emailCode}
|
||||||
<Button variant="icon" size="icon" onclick={onBackToLogin} aria-label={t("wa_back")}>
|
email={pendingEmail}
|
||||||
<ArrowLeft size={19} />
|
busy={authBusy}
|
||||||
</Button>
|
resendCooldown={authResendCooldown}
|
||||||
<div>
|
status={authStatus}
|
||||||
<h1>{t("wa_email_verification_title")}</h1>
|
isError={authIsError}
|
||||||
<p>{t("wa_email_sent_to", { email: pendingEmail })}</p>
|
{t}
|
||||||
</div>
|
onBack={onBackToLogin}
|
||||||
<span></span>
|
onConfirm={verifyEmailCode}
|
||||||
</header>
|
onResend={requestEmailCode}
|
||||||
<div class="otp-wrap">
|
/>
|
||||||
<label class="otp-input-wrap">
|
{:else}
|
||||||
<input
|
<div class="phone-screen auth-screen">
|
||||||
bind:value={emailCode}
|
|
||||||
inputmode="numeric"
|
|
||||||
autocomplete="one-time-code"
|
|
||||||
maxlength="6"
|
|
||||||
aria-label={t("wa_email_code_aria")}
|
|
||||||
/>
|
|
||||||
<span class="otp-slots" aria-hidden="true">
|
|
||||||
{#each Array.from({ length: 6 }) as _, index}
|
|
||||||
<span class:filled={emailCode[index]}>{emailCode[index] || ""}</span>
|
|
||||||
{/each}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<Button class="wide" onclick={verifyEmailCode} disabled={authBusy}>
|
|
||||||
{t("wa_confirm")}
|
|
||||||
</Button>
|
|
||||||
{#if authStatus}
|
|
||||||
<StatusMessage error={authIsError}>{authStatus}</StatusMessage>
|
|
||||||
{/if}
|
|
||||||
<button
|
|
||||||
class="link-button"
|
|
||||||
type="button"
|
|
||||||
onclick={requestEmailCode}
|
|
||||||
disabled={authBusy || authResendCooldown > 0}
|
|
||||||
>
|
|
||||||
<RefreshCw size={15} />
|
|
||||||
{authResendCooldown > 0
|
|
||||||
? t("wa_auth_resend_wait", { seconds: authResendCooldown })
|
|
||||||
: t("wa_resend_code")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="auth-card-wrap">
|
<div class="auth-card-wrap">
|
||||||
<div class="login-brand login-brand-auth">
|
<div class="login-brand login-brand-auth">
|
||||||
<BrandMark {brand} size="xl" />
|
<BrandMark {brand} size="xl" />
|
||||||
@@ -305,5 +268,5 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<script>
|
||||||
|
import { ArrowLeft, RefreshCw } from "$components/ui/icons.js";
|
||||||
|
|
||||||
|
import Button from "$components/ui/button.svelte";
|
||||||
|
import { StatusMessage } from "$components/patterns/webapp/index.js";
|
||||||
|
|
||||||
|
export let code = "";
|
||||||
|
export let email = "";
|
||||||
|
export let busy = false;
|
||||||
|
export let resendCooldown = 0;
|
||||||
|
export let status = "";
|
||||||
|
export let isError = false;
|
||||||
|
export let t = (key) => key;
|
||||||
|
export let onBack = () => {};
|
||||||
|
export let onConfirm = () => {};
|
||||||
|
export let onResend = () => {};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="phone-screen auth-screen">
|
||||||
|
<header class="screen-head center-title">
|
||||||
|
<Button variant="icon" size="icon" onclick={onBack} aria-label={t("wa_back")}>
|
||||||
|
<ArrowLeft size={19} />
|
||||||
|
</Button>
|
||||||
|
<div>
|
||||||
|
<h1>{t("wa_email_verification_title")}</h1>
|
||||||
|
<p>{t("wa_email_sent_to", { email })}</p>
|
||||||
|
</div>
|
||||||
|
<span></span>
|
||||||
|
</header>
|
||||||
|
<div class="otp-wrap">
|
||||||
|
<label class="otp-input-wrap">
|
||||||
|
<input
|
||||||
|
bind:value={code}
|
||||||
|
inputmode="numeric"
|
||||||
|
autocomplete="one-time-code"
|
||||||
|
maxlength="6"
|
||||||
|
aria-label={t("wa_email_code_aria")}
|
||||||
|
/>
|
||||||
|
<span class="otp-slots" aria-hidden="true">
|
||||||
|
{#each Array.from({ length: 6 }) as _, index}
|
||||||
|
<span class:filled={code[index]}>{code[index] || ""}</span>
|
||||||
|
{/each}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<Button class="wide" onclick={onConfirm} disabled={busy}>
|
||||||
|
{t("wa_confirm")}
|
||||||
|
</Button>
|
||||||
|
{#if status}
|
||||||
|
<StatusMessage error={isError}>{status}</StatusMessage>
|
||||||
|
{/if}
|
||||||
|
<button
|
||||||
|
class="link-button"
|
||||||
|
type="button"
|
||||||
|
onclick={onResend}
|
||||||
|
disabled={busy || resendCooldown > 0}
|
||||||
|
>
|
||||||
|
<RefreshCw size={15} />
|
||||||
|
{resendCooldown > 0
|
||||||
|
? t("wa_auth_resend_wait", { seconds: resendCooldown })
|
||||||
|
: t("wa_resend_code")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user