diff --git a/src/lib/components/account/sendVerificationEmailModal.svelte b/src/lib/components/account/sendVerificationEmailModal.svelte index 3fe8c8f6b..bb9d20bcb 100644 --- a/src/lib/components/account/sendVerificationEmailModal.svelte +++ b/src/lib/components/account/sendVerificationEmailModal.svelte @@ -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 | 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 @@
- + To continue using Appwrite Cloud, please verify your email address. An email will be sent to {email || get(user)?.email} - - - logout()}>Switch account - - + + logout()}>Switch account + {#if emailSent && resendTimer > 0} - - Didn't get the email? Try again in {resendTimer}s - +
+ + Didn't get the email? Try again in {resendTimer}s + +
{/if}
- @@ -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; + } diff --git a/src/lib/components/alerts/emailVerificationBanner.svelte b/src/lib/components/alerts/emailVerificationBanner.svelte deleted file mode 100644 index f3803ce79..000000000 --- a/src/lib/components/alerts/emailVerificationBanner.svelte +++ /dev/null @@ -1,41 +0,0 @@ - - -{#if shouldShowEmailBanner} - - - To avoid losing access to your projects, make sure {$user.email} is valid and up to date. Email - verification will be required soon. - - - - - - -{/if} diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts index 47afdf41a..b9d6e57ed 100644 --- a/src/lib/components/index.ts +++ b/src/lib/components/index.ts @@ -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'; diff --git a/src/routes/(console)/verify-email/+page.svelte b/src/routes/(console)/verify-email/+page.svelte index 8e0f5b490..c70b0e2d3 100644 --- a/src/routes/(console)/verify-email/+page.svelte +++ b/src/routes/(console)/verify-email/+page.svelte @@ -1,85 +1,33 @@ - Verify Email - Appwrite Console + Verify Email - Appwrite
@@ -92,7 +40,7 @@ bind:sideBarIsOpen={$sideBarIsOpen} bind:showAccountMenu={$showAccountMenu} {project} - {avatar} + avatar={navbarProps.avatar} {progressCard} state="open" /> diff --git a/src/routes/(console)/verify-email/+page.ts b/src/routes/(console)/verify-email/+page.ts new file mode 100644 index 000000000..15ad453bc --- /dev/null +++ b/src/routes/(console)/verify-email/+page.ts @@ -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('/')); + } +}; diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 2133f99ed..5ee94b650 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -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')); } }