feat: email and password login
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
<script>
|
||||
import { ArrowLeft, Mail, RefreshCw, Send, TriangleAlert } from "$components/ui/icons.js";
|
||||
import {
|
||||
ArrowLeft,
|
||||
LockKeyhole,
|
||||
Mail,
|
||||
RefreshCw,
|
||||
Send,
|
||||
TriangleAlert,
|
||||
} from "$components/ui/icons.js";
|
||||
import { Tooltip } from "$components/ui/primitives.js";
|
||||
|
||||
import Button from "$components/ui/button.svelte";
|
||||
import BrandMark from "$lib/webapp/BrandMark.svelte";
|
||||
import Card from "$components/ui/card.svelte";
|
||||
import Input from "$components/ui/input.svelte";
|
||||
import Spinner from "$components/ui/spinner.svelte";
|
||||
import { StatusMessage } from "$components/patterns/webapp/index.js";
|
||||
@@ -14,6 +20,7 @@
|
||||
export let brand = {};
|
||||
export let brandTitle;
|
||||
export let email;
|
||||
export let emailPassword;
|
||||
export let emailCode;
|
||||
export let pendingEmail;
|
||||
export let authStatus;
|
||||
@@ -22,6 +29,8 @@
|
||||
export let authResendCooldown;
|
||||
export let loginEmailFieldError;
|
||||
export let loginEmailTooltipOpen;
|
||||
export let passwordLoginFallback;
|
||||
export let passwordLoginMode;
|
||||
export let telegramLoginBusy;
|
||||
export let telegramLoginUnavailable;
|
||||
export let telegramLoginChecking;
|
||||
@@ -31,12 +40,19 @@
|
||||
export let userAgreementUrl;
|
||||
export let t;
|
||||
export let requestEmailCode;
|
||||
export let loginWithEmailPassword;
|
||||
export let verifyEmailCode;
|
||||
export let openTelegramLogin;
|
||||
export let openExternalLink;
|
||||
export let submitEmailOnEnter;
|
||||
export let onBackToLogin;
|
||||
export let clearLoginEmailError;
|
||||
export let setPasswordLoginMode;
|
||||
|
||||
let authPanelHeight = 0;
|
||||
|
||||
$: passwordModeActive = Boolean(passwordLoginMode && CFG.emailAuthEnabled !== false);
|
||||
$: authCardHeight = authPanelHeight ? `${authPanelHeight}px` : undefined;
|
||||
</script>
|
||||
|
||||
<div class="phone-screen auth-screen">
|
||||
@@ -90,74 +106,168 @@
|
||||
<BrandMark {brand} size="xl" />
|
||||
<h1>{brandTitle}</h1>
|
||||
</div>
|
||||
<Card class="auth-card">
|
||||
{#if CFG.emailAuthEnabled !== false}
|
||||
<div class="auth-pane">
|
||||
<div class="auth-email-stack">
|
||||
<div class="field-error-wrap">
|
||||
<Tooltip.Root open={Boolean(loginEmailFieldError) && loginEmailTooltipOpen}>
|
||||
<section class="card auth-card" style:height={authCardHeight}>
|
||||
{#key passwordModeActive}
|
||||
<div
|
||||
class={`auth-mode-panel${passwordModeActive ? " auth-mode-panel-password" : ""}`}
|
||||
bind:clientHeight={authPanelHeight}
|
||||
>
|
||||
{#if passwordModeActive}
|
||||
<div class="auth-pane">
|
||||
<div class="auth-email-stack">
|
||||
<div class="field-error-wrap">
|
||||
<Tooltip.Root open={Boolean(loginEmailFieldError) && loginEmailTooltipOpen}>
|
||||
<Input
|
||||
bind:value={email}
|
||||
type="email"
|
||||
placeholder={t("wa_email_placeholder")}
|
||||
autocomplete="email"
|
||||
class={loginEmailFieldError ? "input-error" : ""}
|
||||
on:input={clearLoginEmailError}
|
||||
/>
|
||||
{#if loginEmailFieldError}
|
||||
<Tooltip.Trigger
|
||||
class="field-error-trigger"
|
||||
aria-label={loginEmailFieldError}
|
||||
>
|
||||
<span class="field-error-icon" aria-hidden="true"
|
||||
><TriangleAlert size={18} /></span
|
||||
>
|
||||
</Tooltip.Trigger>
|
||||
{/if}
|
||||
{#if loginEmailFieldError}
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content class="field-error-tooltip"
|
||||
>{loginEmailFieldError}</Tooltip.Content
|
||||
>
|
||||
</Tooltip.Portal>
|
||||
{/if}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
<Input
|
||||
bind:value={email}
|
||||
type="email"
|
||||
placeholder={t("wa_email_placeholder")}
|
||||
autocomplete="email"
|
||||
class={loginEmailFieldError ? "input-error" : ""}
|
||||
on:keydown={submitEmailOnEnter}
|
||||
on:input={clearLoginEmailError}
|
||||
bind:value={emailPassword}
|
||||
type="password"
|
||||
placeholder={t("wa_password_placeholder")}
|
||||
autocomplete="current-password"
|
||||
on:keydown={(event) => {
|
||||
if (event.key !== "Enter") return;
|
||||
event.preventDefault();
|
||||
loginWithEmailPassword();
|
||||
}}
|
||||
/>
|
||||
{#if loginEmailFieldError}
|
||||
<Tooltip.Trigger class="field-error-trigger" aria-label={loginEmailFieldError}>
|
||||
<span class="field-error-icon" aria-hidden="true"
|
||||
><TriangleAlert size={18} /></span
|
||||
>
|
||||
</Tooltip.Trigger>
|
||||
<Button class="wide" onclick={loginWithEmailPassword} disabled={authBusy}>
|
||||
<LockKeyhole size={18} />
|
||||
{t("wa_login_password_submit")}
|
||||
</Button>
|
||||
{#if passwordLoginFallback}
|
||||
<button
|
||||
class="link-button auth-code-fallback"
|
||||
type="button"
|
||||
onclick={requestEmailCode}
|
||||
disabled={authBusy}
|
||||
>
|
||||
<Mail size={15} />
|
||||
{t("wa_login_use_email_code")}
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="link-button auth-code-fallback"
|
||||
type="button"
|
||||
onclick={() => setPasswordLoginMode(false)}
|
||||
disabled={authBusy}
|
||||
>
|
||||
{t("wa_login_use_email_code")}
|
||||
</button>
|
||||
{/if}
|
||||
{#if loginEmailFieldError}
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content class="field-error-tooltip"
|
||||
>{loginEmailFieldError}</Tooltip.Content
|
||||
>
|
||||
</Tooltip.Portal>
|
||||
{/if}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</div>
|
||||
<Button class="wide" onclick={requestEmailCode} disabled={authBusy}>
|
||||
<Mail size={18} />
|
||||
{t("wa_send_code_email")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if CFG.emailAuthEnabled !== false}
|
||||
<div class="or-line"><span></span>{t("wa_or")}<span></span></div>
|
||||
{/if}
|
||||
<div class="auth-pane">
|
||||
<Button
|
||||
variant="telegram"
|
||||
class={`wide telegram-login-button${telegramLoginUnavailable ? " unavailable" : ""}${telegramLoginChecking ? " checking" : ""}`}
|
||||
onclick={openTelegramLogin}
|
||||
disabled={authBusy || telegramLoginBusy || telegramLoginUnavailable}
|
||||
aria-label={telegramLoginLabel}
|
||||
>
|
||||
<span class="telegram-login-text">
|
||||
{#if telegramLoginChecking}
|
||||
<Spinner size="sm" />
|
||||
{:else}
|
||||
<Send size={17} />
|
||||
{#if authStatus}
|
||||
<StatusMessage error={authIsError} class="auth-login-status">
|
||||
{authStatus}
|
||||
</StatusMessage>
|
||||
{/if}
|
||||
{telegramLoginLabel}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
{#if !telegramLoginChecking && (authStatus || telegramLoginUnavailableMessage)}
|
||||
<StatusMessage
|
||||
error={authIsError || Boolean(telegramLoginUnavailableMessage)}
|
||||
class="auth-login-status"
|
||||
>
|
||||
{authStatus || telegramLoginUnavailableMessage}
|
||||
</StatusMessage>
|
||||
{/if}
|
||||
</Card>
|
||||
{:else if CFG.emailAuthEnabled !== false}
|
||||
<div class="auth-pane">
|
||||
<div class="auth-email-stack">
|
||||
<div class="field-error-wrap">
|
||||
<Tooltip.Root open={Boolean(loginEmailFieldError) && loginEmailTooltipOpen}>
|
||||
<Input
|
||||
bind:value={email}
|
||||
type="email"
|
||||
placeholder={t("wa_email_placeholder")}
|
||||
autocomplete="email"
|
||||
class={loginEmailFieldError ? "input-error" : ""}
|
||||
on:keydown={submitEmailOnEnter}
|
||||
on:input={clearLoginEmailError}
|
||||
/>
|
||||
{#if loginEmailFieldError}
|
||||
<Tooltip.Trigger
|
||||
class="field-error-trigger"
|
||||
aria-label={loginEmailFieldError}
|
||||
>
|
||||
<span class="field-error-icon" aria-hidden="true"
|
||||
><TriangleAlert size={18} /></span
|
||||
>
|
||||
</Tooltip.Trigger>
|
||||
{/if}
|
||||
{#if loginEmailFieldError}
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content class="field-error-tooltip"
|
||||
>{loginEmailFieldError}</Tooltip.Content
|
||||
>
|
||||
</Tooltip.Portal>
|
||||
{/if}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
<Button class="wide" onclick={requestEmailCode} disabled={authBusy}>
|
||||
<Mail size={18} />
|
||||
{t("wa_send_code_email")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="or-line"><span></span>{t("wa_or")}<span></span></div>
|
||||
<div class="auth-pane">
|
||||
<Button
|
||||
variant="telegram"
|
||||
class={`wide telegram-login-button${telegramLoginUnavailable ? " unavailable" : ""}${telegramLoginChecking ? " checking" : ""}`}
|
||||
onclick={openTelegramLogin}
|
||||
disabled={authBusy || telegramLoginBusy || telegramLoginUnavailable}
|
||||
aria-label={telegramLoginLabel}
|
||||
>
|
||||
<span class="telegram-login-text">
|
||||
{#if telegramLoginChecking}
|
||||
<Spinner size="sm" />
|
||||
{:else}
|
||||
<Send size={17} />
|
||||
{/if}
|
||||
{telegramLoginLabel}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="password-switch-stack">
|
||||
<div class="password-switch-divider" aria-hidden="true"></div>
|
||||
<button
|
||||
class="link-button password-switch-button"
|
||||
type="button"
|
||||
onclick={() => setPasswordLoginMode(true)}
|
||||
disabled={authBusy}
|
||||
>
|
||||
<LockKeyhole size={15} />
|
||||
{t("wa_login_use_password")}
|
||||
</button>
|
||||
</div>
|
||||
{#if !telegramLoginChecking && (authStatus || telegramLoginUnavailableMessage)}
|
||||
<StatusMessage
|
||||
error={authIsError || Boolean(telegramLoginUnavailableMessage)}
|
||||
class="auth-login-status"
|
||||
>
|
||||
{authStatus || telegramLoginUnavailableMessage}
|
||||
</StatusMessage>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
</section>
|
||||
{#if userAgreementUrl || privacyPolicyUrl}
|
||||
<div class="auth-legal">
|
||||
<span class="auth-legal-intro">{t("wa_auth_legal_intro")}</span>
|
||||
|
||||
Reference in New Issue
Block a user