update: refactor.

This commit is contained in:
Darshan
2025-10-05 13:50:06 +05:30
parent 0f9c48d8e4
commit 860cd4078c
6 changed files with 126 additions and 183 deletions
@@ -10,44 +10,51 @@
import { Card, Layout, Typography } from '@appwrite.io/pink-svelte';
import { Dependencies } from '$lib/constants';
import { onMount, onDestroy } from 'svelte';
import { base } from '$app/paths';
import { resolve } from '$app/paths';
import { browser } from '$app/environment';
import { addNotification } from '$lib/stores/notifications';
import { slide } from 'svelte/transition';
let { show = $bindable(false), email }: { show?: boolean; email?: string } = $props();
let {
show = $bindable(false),
email
}: {
show?: boolean;
email?: string;
} = $props();
let error = $state(null);
let creating = $state(false);
let emailSent = $state(false);
let resendTimer = $state(0);
let timerInterval: ReturnType<typeof setInterval> | null = null;
async function logout() {
error = null;
try {
await sdk.forConsole.account.deleteSession({ sessionId: 'current' });
await invalidate(Dependencies.ACCOUNT);
goto(`${base}/login`);
} catch (error) {
addNotification({
type: 'error',
title: 'Logout failed',
message: 'Unable to log out. Please try again or refresh the page.'
});
await goto(resolve('/login'));
} catch (err) {
error = err.message;
}
}
const cleanUrl = $derived(page.url.origin + page.url.pathname);
// Manage resend timer in localStorage
const TIMER_END_KEY = 'email_verification_timer_end';
// manage resend timer in localStorage
const EMAIL_SENT_KEY = 'email_verification_sent';
const TIMER_END_KEY = 'email_verification_timer_end';
function startResendTimer() {
const timerEndTime = Date.now() + 60 * 1000;
resendTimer = 60;
emailSent = true;
const timerEndTime = Date.now() + 60 * 1000;
if (browser) {
localStorage.setItem(TIMER_END_KEY, timerEndTime.toString());
localStorage.setItem(EMAIL_SENT_KEY, 'true');
localStorage.setItem(TIMER_END_KEY, timerEndTime.toString());
}
startTimerCountdown(timerEndTime);
}
@@ -55,18 +62,21 @@
if (!browser) return;
const savedTimerEnd = localStorage.getItem(TIMER_END_KEY);
const savedEmailSent = localStorage.getItem(EMAIL_SENT_KEY);
if (savedTimerEnd && savedEmailSent) {
const timerEndTime = parseInt(savedTimerEnd);
const now = Date.now();
const remainingTime = Math.max(0, Math.ceil((timerEndTime - now) / 1000));
if (remainingTime > 0) {
resendTimer = remainingTime;
emailSent = true;
startTimerCountdown(timerEndTime);
} else {
// Timer has expired, clean up
// timer has expired, clean up
localStorage.removeItem(TIMER_END_KEY);
localStorage.removeItem(EMAIL_SENT_KEY);
resendTimer = 0;
emailSent = false;
}
@@ -91,57 +101,26 @@
async function onSubmit() {
if (creating || resendTimer > 0) return;
error = null;
creating = true;
try {
await sdk.forConsole.account.createVerification({ url: cleanUrl });
emailSent = true;
startResendTimer();
} catch (error) {
addNotification({
type: 'error',
title: 'Failed to send verification email',
message: 'Unable to send verification email. Please try again.'
});
console.error('Failed to send verification email:', error);
} catch (err) {
error = err.message;
} finally {
creating = false;
}
}
async function updateEmailVerification() {
const searchParams = page.url.searchParams;
const userId = searchParams.get('userId');
const secret = searchParams.get('secret');
if (userId && secret) {
try {
await sdk.forConsole.account.updateVerification({ userId, secret });
await Promise.all([
invalidate(Dependencies.ACCOUNT),
invalidate(Dependencies.FACTORS)
]);
goto(`${base}/`);
} catch (error) {
addNotification({
type: 'error',
title: 'Email verification failed',
message: 'Unable to verify your email. Please try again.'
});
console.error('Failed to verify email:', error);
}
}
}
onMount(() => {
updateEmailVerification();
restoreTimerState(); // Check for existing timer
});
onMount(() => restoreTimerState);
onDestroy(() => {
if (timerInterval) {
clearInterval(timerInterval);
}
if (browser) {
localStorage.removeItem(TIMER_END_KEY);
localStorage.removeItem(EMAIL_SENT_KEY);
@@ -152,12 +131,13 @@
<div class="email-verification-scrim">
<Modal
bind:show
bind:error
title="Verify your email address"
{onSubmit}
dismissible={false}
autoClose={false}>
<Card.Base variant="secondary" padding="s">
<Layout.Stack gap="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
@@ -165,21 +145,27 @@
color="neutral-secondary"
style="display: inline;">{email || get(user)?.email}</Typography.Text>
</Typography.Text>
<Layout.Stack class="u-margin-block-start-4 u-margin-block-end-24">
<Layout.Stack direction="row">
<Link variant="default" on:click={() => logout()}>Switch account</Link>
</Layout.Stack>
</Layout.Stack>
<Link variant="default" on:click={() => logout()}>Switch account</Link>
{#if emailSent && resendTimer > 0}
<Typography.Text color="neutral-secondary">
Didn't get the email? Try again in {resendTimer}s
</Typography.Text>
<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 disabled={creating || resendTimer > 0}>
<Button
submit
submissionLoader
forceShowLoader={creating}
disabled={creating || resendTimer > 0}>
{emailSent ? 'Resend email' : 'Send email'}
</Button>
</svelte:fragment>
@@ -199,4 +185,10 @@
align-items: center;
justify-content: center;
}
/* avoids the background scroll and bars */
:global(html:has(.email-verification-scrim)) {
height: 100%;
overflow: hidden !important;
}
</style>
@@ -1,41 +0,0 @@
<script lang="ts">
import { Button } from '$lib/elements/forms';
import { HeaderAlert } from '$lib/layout';
import { Typography } from '@appwrite.io/pink-svelte';
import { user } from '$lib/stores/user';
import SendVerificationEmailModal from '../account/sendVerificationEmailModal.svelte';
import { page } from '$app/state';
import { wizard, isNewWizardStatusOpen } from '$lib/stores/wizard';
import { isCloud, VARS } from '$lib/system';
const hasUser = $derived(!!$user);
const needsEmailVerification = $derived(hasUser && !$user.emailVerification);
const notOnOnboarding = $derived(!page.route.id.includes('/onboarding'));
const notOnWizard = $derived(!$wizard.show && !$isNewWizardStatusOpen);
const shouldShowEmailBanner = $derived(
VARS.EMAIL_VERIFICATION &&
isCloud &&
hasUser &&
needsEmailVerification &&
notOnOnboarding &&
notOnWizard
);
let showSendVerification = $state(false);
</script>
{#if shouldShowEmailBanner}
<HeaderAlert type="warning" title="Your email address needs to be verified">
<svelte:fragment>
To avoid losing access to your projects, make sure <Typography.Text
variant="m-500"
style="display:inline">{$user.email}</Typography.Text> is valid and up to date. Email
verification will be required soon.
</svelte:fragment>
<svelte:fragment slot="buttons">
<Button secondary size="s" on:click={() => (showSendVerification = true)}
>Verify email</Button>
</svelte:fragment>
</HeaderAlert>
<SendVerificationEmailModal bind:show={showSendVerification} email={$user?.email} />
{/if}
+1 -1
View File
@@ -85,5 +85,5 @@ export { default as ViewToggle } from './viewToggle.svelte';
export { default as RegionEndpoint } from './regionEndpoint.svelte';
export { default as ExpirationInput } from './expirationInput.svelte';
export { default as EstimatedCard } from './estimatedCard.svelte';
export { default as EmailVerificationBanner } from './alerts/emailVerificationBanner.svelte';
export { default as SortButton, type SortDirection } from './sortButton.svelte';
export { default as SendVerificationEmailModal } from './account/sendVerificationEmailModal.svelte';
+20 -72
View File
@@ -1,85 +1,33 @@
<script lang="ts">
import Sidebar from '$lib/components/sidebar.svelte';
import Navbar from '$lib/components/navbar.svelte';
import SendVerificationEmailModal from '$lib/components/account/sendVerificationEmailModal.svelte';
import { writable } from 'svelte/store';
import { invalidate } from '$app/navigation';
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
import { base } from '$app/paths';
import { Dependencies } from '$lib/constants';
import { page } from '$app/state';
import { realtime } from '$lib/stores/sdk';
import { writable } from 'svelte/store';
import type { Models } from '@appwrite.io/console';
import { Navbar, SendVerificationEmailModal, Sidebar } from '$lib/components';
let sideBarIsOpen = writable(false);
let showAccountMenu = writable(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let project: any = {
$id: 'verify-email-project',
region: 'us-east-1',
name: 'Verify Email Project'
};
let avatar = '/images/default-avatar.png';
let progressCard = {
title: 'Get started',
percentage: 33
};
let navbarProps = {
logo: {
src: '/images/appwrite-logo-light.svg',
alt: 'Appwrite Logo'
},
avatar: avatar,
organizations: [],
currentProject: project
};
let showVerificationModal = $state(!page.data.account?.emailVerification);
$effect(() => {
if (page.data.account?.emailVerification) {
checkEmailVerification();
}
});
// fake props!
const project = {
region: 'fra',
$id: 'appwrite',
name: 'Appwrite Project'
} as Models.Project;
async function checkEmailVerification() {
if (page.data.account?.emailVerification) {
await goto(`${base}/`);
}
}
onMount(() => {
if (!page.data.account) {
goto(`${base}/login`);
return;
}
// If email is already verified, redirect immediately
if (page.data.account?.emailVerification) {
checkEmailVerification();
return;
}
const unsubscribe = realtime.forProject('', '').subscribe(['account'], async () => {
await invalidate(Dependencies.ACCOUNT);
checkEmailVerification();
});
const interval = setInterval(async () => {
await invalidate(Dependencies.ACCOUNT);
checkEmailVerification();
}, 10000);
return () => {
clearInterval(interval);
unsubscribe();
};
});
const progressCard = { title: 'Get started', percentage: 33 };
const navbarProps = {
logo: {
src: 'https://appwrite.io/images/logos/logo.svg',
alt: 'Logo Appwrite'
},
avatar: undefined,
organizations: []
};
</script>
<svelte:head>
<title>Verify Email - Appwrite Console</title>
<title>Verify Email - Appwrite</title>
</svelte:head>
<div class="verify-email-page">
@@ -92,7 +40,7 @@
bind:sideBarIsOpen={$sideBarIsOpen}
bind:showAccountMenu={$showAccountMenu}
{project}
{avatar}
avatar={navbarProps.avatar}
{progressCard}
state="open" />
@@ -0,0 +1,45 @@
import { resolve } from '$app/paths';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
import { Dependencies } from '$lib/constants';
import { sdk } from '$lib/stores/sdk';
import { addNotification } from '$lib/stores/notifications';
import { VARS } from '$lib/system';
export const load: PageLoad = async ({ parent, depends, url }) => {
if (!VARS.EMAIL_VERIFICATION) {
redirect(303, resolve('/'));
}
const { account } = await parent();
depends(Dependencies.ACCOUNT);
const user = url.searchParams.get('userId') ?? null;
const secret = url.searchParams.get('secret') ?? null;
if (account && account.emailVerification === true) {
redirect(303, resolve('/'));
} else if (user && secret) {
try {
await sdk.forConsole.account.updateVerification({
userId: user,
secret
});
addNotification({
type: 'success',
message: 'Email has been verified',
timeout: 10000 // 10 seconds max, because if succeeded, loads takes some time!
});
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
} finally {
redirect(303, resolve('/'));
}
} else {
redirect(303, resolve('/'));
}
};
+7 -8
View File
@@ -6,10 +6,10 @@ import { redirect } from '@sveltejs/kit';
import { Dependencies } from '$lib/constants';
import type { LayoutLoad } from './$types';
import { redirectTo } from './store';
import { base } from '$app/paths';
import { base, resolve } from '$app/paths';
import type { Account } from '$lib/stores/user';
import type { AppwriteException } from '@appwrite.io/console';
import { isCloud } from '$lib/system';
import { isCloud, VARS } from '$lib/system';
import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect';
export const ssr = false;
@@ -29,13 +29,12 @@ export const load: LayoutLoad = async ({ depends, url, route }) => {
}
if (account) {
if (isCloud && !account.emailVerification) {
const isPublicRoute = route.id?.startsWith('/(public)');
const isAuthRoute = route.id?.startsWith('/(authenticated)');
const isVerifyEmailPage = url.pathname === `${base}/verify-email`;
if (isCloud && !account.emailVerification && VARS.EMAIL_VERIFICATION) {
const isConsoleRoute = route.id?.startsWith('/(console)');
const isVerifyEmailPage = url.pathname === resolve('/verify-email');
if (!isPublicRoute && !isAuthRoute && !isVerifyEmailPage) {
redirect(303, `${base}/verify-email`);
if (isConsoleRoute && !isVerifyEmailPage) {
redirect(303, resolve('/verify-email'));
}
}