Allow email update from the verify email modal (#2911)

* Initial plan

* Add update email address option to verify email modal

Co-authored-by: stnguyen90 <1477010+stnguyen90@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: stnguyen90 <1477010+stnguyen90@users.noreply.github.com>
This commit is contained in:
Copilot
2026-03-12 13:13:02 -07:00
committed by GitHub
co-authored by stnguyen90
parent 96eeac0ab2
commit 943e2240bf
2 changed files with 120 additions and 42 deletions
@@ -1,7 +1,8 @@
<script lang="ts">
import { invalidate, goto } from '$app/navigation';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Modal } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { Button, InputEmail, InputPassword } from '$lib/elements/forms';
import { sdk } from '$lib/stores/sdk';
import { user } from '$lib/stores/user';
import { get } from 'svelte/store';
@@ -28,6 +29,17 @@
let resendTimer = $state(0);
let timerInterval: ReturnType<typeof setInterval> | null = null;
let showUpdateEmail = $state(false);
let newEmail = $state('');
let newPassword = $state('');
let updating = $state(false);
$effect(() => {
if (showUpdateEmail) {
newEmail = email || get(user)?.email || '';
}
});
async function logout() {
error = null;
try {
@@ -114,6 +126,32 @@
}
}
async function updateEmail() {
error = null;
updating = true;
try {
await sdk.forConsole.account.updateEmail({
email: newEmail,
password: newPassword
});
await invalidate(Dependencies.ACCOUNT);
trackEvent(Submit.AccountUpdateEmail);
resetUpdateEmailForm();
} catch (err) {
error = err.message;
trackError(err, Submit.AccountUpdateEmail);
} finally {
updating = false;
}
}
function resetUpdateEmailForm() {
showUpdateEmail = false;
newEmail = '';
newPassword = '';
error = null;
}
onMount(restoreTimerState);
onDestroy(() => {
@@ -129,48 +167,87 @@
</script>
<div class="email-verification-scrim">
<Modal
bind:show
bind:error
title="Verify your email address"
{onSubmit}
dismissible={false}
autoClose={false}
backdrop={false}>
<Card.Base variant="secondary" padding="s">
<Layout.Stack gap="xxs">
<Typography.Text gap="m">
To continue using Appwrite Cloud, please verify your email address. An email
will be sent to <Typography.Text
variant="m-600"
color="neutral-secondary"
style="display: inline;">{email || get(user)?.email}</Typography.Text>
</Typography.Text>
<Link variant="default" on:click={() => logout()}>Switch account</Link>
{#if emailSent && resendTimer > 0}
<div transition:slide={{ duration: 150 }}>
<Typography.Text
{#if !showUpdateEmail}
<Modal
bind:show
bind:error
title="Verify your email address"
{onSubmit}
dismissible={false}
autoClose={false}
backdrop={false}>
<Card.Base variant="secondary" padding="s">
<Layout.Stack gap="xxs">
<Typography.Text gap="m">
To continue using Appwrite Cloud, please verify your email address. An email
will be sent to <Typography.Text
variant="m-600"
color="neutral-secondary"
style="margin-block-start: var(--gap-L, 16px);">
Didn't get the email? Try again in {resendTimer}s
</Typography.Text>
</div>
{/if}
</Layout.Stack>
</Card.Base>
style="display: inline;">{email || get(user)?.email}</Typography.Text>
</Typography.Text>
<svelte:fragment slot="footer">
<Button
submit
submissionLoader
forceShowLoader={creating}
disabled={creating || resendTimer > 0}>
{emailSent ? 'Resend email' : 'Send email'}
</Button>
</svelte:fragment>
</Modal>
<Typography.Text>
Wrong email? <Link
variant="default"
on:click={() => {
showUpdateEmail = true;
error = null;
}}>Update email address</Link> or <Link
variant="default"
on:click={() => logout()}>Switch account</Link>
</Typography.Text>
{#if emailSent && resendTimer > 0}
<div transition:slide={{ duration: 150 }}>
<Typography.Text
color="neutral-secondary"
style="margin-block-start: var(--gap-L, 16px);">
Didn't get the email? Try again in {resendTimer}s
</Typography.Text>
</div>
{/if}
</Layout.Stack>
</Card.Base>
<svelte:fragment slot="footer">
<Button
submit
submissionLoader
forceShowLoader={creating}
disabled={creating || resendTimer > 0}>
{emailSent ? 'Resend email' : 'Send email'}
</Button>
</svelte:fragment>
</Modal>
{:else}
<Modal
bind:show={showUpdateEmail}
bind:error
title="Update email address"
onSubmit={updateEmail}
autoClose={false}
backdrop={false}>
<InputEmail
id="new-email"
label="Email"
placeholder="Enter email"
bind:value={newEmail}
required
helper="You'll need access to this email to verify your account" />
<InputPassword
id="update-password"
label="Password"
placeholder="Enter password"
required
bind:value={newPassword} />
<svelte:fragment slot="footer">
<Button text on:click={resetUpdateEmailForm}>Cancel</Button>
<Button submit submissionLoader forceShowLoader={updating} disabled={updating}
>Update</Button>
</svelte:fragment>
</Modal>
{/if}
</div>
<style>
+2 -1
View File
@@ -13,6 +13,7 @@
export let readonly = false;
export let autofocus = false;
export let autocomplete = false;
export let helper: string = undefined;
export let leadingIcon: ComponentType | undefined = undefined;
let error: string;
@@ -49,7 +50,7 @@
on:input
on:invalid={handleInvalid}
type="email"
helper={error}
helper={error || helper}
state={error ? 'error' : 'default'}
autofocus={autofocus || undefined}
autocomplete={autocomplete ? 'on' : 'off'}