From 09b8d7b3841f2f7ec8bd4fae92e50074e1dbf9a3 Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Tue, 27 Jan 2026 01:31:55 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(auth-ui):=20show=20email=20ver?= =?UTF-8?q?ified=20banner=20on=20login=20pages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add verified banner and email pre-fill to LoginPage component when users are redirected after email verification. Updates all app login pages to pass verification params from URL query string. Co-Authored-By: Claude Opus 4.5 --- .../web/src/routes/(auth)/login/+page.svelte | 6 ++ .../web/src/routes/(auth)/login/+page.svelte | 6 ++ .../web/src/routes/(auth)/login/+page.svelte | 6 ++ .../web/src/routes/(auth)/login/+page.svelte | 6 ++ .../web/src/routes/(auth)/login/+page.svelte | 7 ++ .../web/src/routes/(auth)/login/+page.svelte | 7 ++ .../web/src/routes/(auth)/login/+page.svelte | 6 ++ .../web/src/routes/auth/login/+page.svelte | 7 ++ .../web/src/routes/(auth)/login/+page.svelte | 35 ++++++++++ .../web/src/routes/(auth)/login/+page.svelte | 6 ++ .../web/src/routes/(auth)/login/+page.svelte | 6 ++ .../shared-auth-ui/src/pages/LoginPage.svelte | 66 ++++++++++++++++++- 12 files changed, 162 insertions(+), 2 deletions(-) diff --git a/apps/calendar/apps/web/src/routes/(auth)/login/+page.svelte b/apps/calendar/apps/web/src/routes/(auth)/login/+page.svelte index f31f16f59..c0322d9fe 100644 --- a/apps/calendar/apps/web/src/routes/(auth)/login/+page.svelte +++ b/apps/calendar/apps/web/src/routes/(auth)/login/+page.svelte @@ -32,6 +32,10 @@ // Get translations based on current locale const translations = $derived(getLoginTranslations($locale || 'de')); + // Read verification status from query params (set after email verification) + const verified = $derived($page.url.searchParams.get('verified') === 'true'); + const initialEmail = $derived($page.url.searchParams.get('email') || ''); + async function handleSignIn(email: string, password: string) { return authStore.signIn(email, password); } @@ -55,6 +59,8 @@ lightBackground="#e0f2fe" darkBackground="#0c1929" {translations} + {verified} + {initialEmail} > {#snippet headerControls()} diff --git a/apps/chat/apps/web/src/routes/(auth)/login/+page.svelte b/apps/chat/apps/web/src/routes/(auth)/login/+page.svelte index 312d281c4..4d2ef712f 100644 --- a/apps/chat/apps/web/src/routes/(auth)/login/+page.svelte +++ b/apps/chat/apps/web/src/routes/(auth)/login/+page.svelte @@ -32,6 +32,10 @@ // Get translations based on current locale const translations = $derived(getLoginTranslations($locale || 'de')); + // Read verification status from query params (set after email verification) + const verified = $derived($page.url.searchParams.get('verified') === 'true'); + const initialEmail = $derived($page.url.searchParams.get('email') || ''); + async function handleSignIn(email: string, password: string) { return authStore.signIn(email, password); } @@ -55,6 +59,8 @@ lightBackground="#e0f2fe" darkBackground="#0c1929" {translations} + {verified} + {initialEmail} > {#snippet headerControls()} diff --git a/apps/clock/apps/web/src/routes/(auth)/login/+page.svelte b/apps/clock/apps/web/src/routes/(auth)/login/+page.svelte index 7a7aa214f..d38e1471f 100644 --- a/apps/clock/apps/web/src/routes/(auth)/login/+page.svelte +++ b/apps/clock/apps/web/src/routes/(auth)/login/+page.svelte @@ -9,6 +9,10 @@ let error = $state(''); let loading = $state(false); + // Read verification status from query params (set after email verification) + const verified = $derived($page.url.searchParams.get('verified') === 'true'); + const initialEmail = $derived($page.url.searchParams.get('email') || ''); + // Get redirect URL from query params or sessionStorage (set by AuthGateModal in guest mode) const redirectTo = $derived.by(() => { const queryRedirect = $page.url.searchParams.get('redirectTo'); @@ -51,4 +55,6 @@ onSubmit={handleLogin} registerHref="/register" forgotPasswordHref="/forgot-password" + {verified} + {initialEmail} /> diff --git a/apps/contacts/apps/web/src/routes/(auth)/login/+page.svelte b/apps/contacts/apps/web/src/routes/(auth)/login/+page.svelte index 12010bf3f..6ec0d9078 100644 --- a/apps/contacts/apps/web/src/routes/(auth)/login/+page.svelte +++ b/apps/contacts/apps/web/src/routes/(auth)/login/+page.svelte @@ -29,6 +29,10 @@ // Get translations based on current locale const translations = $derived(getLoginTranslations($locale || 'de')); + // Read verification status from query params (set after email verification) + const verified = $derived($page.url.searchParams.get('verified') === 'true'); + const initialEmail = $derived($page.url.searchParams.get('email') || ''); + async function handleSignIn(email: string, password: string) { return authStore.signIn(email, password); } @@ -52,6 +56,8 @@ lightBackground="#eff6ff" darkBackground="#1e293b" {translations} + {verified} + {initialEmail} > {#snippet headerControls()} diff --git a/apps/manacore/apps/web/src/routes/(auth)/login/+page.svelte b/apps/manacore/apps/web/src/routes/(auth)/login/+page.svelte index 45a3b0c57..bb1ad1753 100644 --- a/apps/manacore/apps/web/src/routes/(auth)/login/+page.svelte +++ b/apps/manacore/apps/web/src/routes/(auth)/login/+page.svelte @@ -1,5 +1,6 @@ @@ -54,6 +59,8 @@ lightBackground="#f0f9ff" darkBackground="#0c1929" {translations} + {verified} + {initialEmail} > {#snippet headerControls()} diff --git a/apps/planta/apps/web/src/routes/(auth)/login/+page.svelte b/apps/planta/apps/web/src/routes/(auth)/login/+page.svelte index 49cdbf9a6..468afb672 100644 --- a/apps/planta/apps/web/src/routes/(auth)/login/+page.svelte +++ b/apps/planta/apps/web/src/routes/(auth)/login/+page.svelte @@ -1,11 +1,27 @@
+ {#if showVerifiedBanner} +
+ + E-Mail erfolgreich bestätigt! Du kannst dich jetzt anmelden. +
+ {/if} + {#if error}
{error} diff --git a/apps/presi/apps/web/src/routes/(auth)/login/+page.svelte b/apps/presi/apps/web/src/routes/(auth)/login/+page.svelte index 1de53fb3b..7d6ee56af 100644 --- a/apps/presi/apps/web/src/routes/(auth)/login/+page.svelte +++ b/apps/presi/apps/web/src/routes/(auth)/login/+page.svelte @@ -16,6 +16,10 @@ // Get translations based on current locale const translations = $derived(getLoginTranslations($locale || 'de')); + // Read verification status from query params (set after email verification) + const verified = $derived($page.url.searchParams.get('verified') === 'true'); + const initialEmail = $derived($page.url.searchParams.get('email') || ''); + async function handleSignIn(email: string, password: string) { return auth.login(email, password); } @@ -39,6 +43,8 @@ lightBackground="#fff7ed" darkBackground="#1c1210" {translations} + {verified} + {initialEmail} > {#snippet headerControls()} diff --git a/apps/todo/apps/web/src/routes/(auth)/login/+page.svelte b/apps/todo/apps/web/src/routes/(auth)/login/+page.svelte index 80b915def..9b33bf625 100644 --- a/apps/todo/apps/web/src/routes/(auth)/login/+page.svelte +++ b/apps/todo/apps/web/src/routes/(auth)/login/+page.svelte @@ -31,6 +31,10 @@ // Get translations based on current locale const translations = $derived(getLoginTranslations($locale || 'de')); + // Read verification status from query params (set after email verification) + const verified = $derived($page.url.searchParams.get('verified') === 'true'); + const initialEmail = $derived($page.url.searchParams.get('email') || ''); + async function handleSignIn(email: string, password: string) { return authStore.signIn(email, password); } @@ -54,6 +58,8 @@ lightBackground="#f3e8ff" darkBackground="#1e1b4b" {translations} + {verified} + {initialEmail} > {#snippet headerControls()} diff --git a/packages/shared-auth-ui/src/pages/LoginPage.svelte b/packages/shared-auth-ui/src/pages/LoginPage.svelte index 82d1523e4..34adc287d 100644 --- a/packages/shared-auth-ui/src/pages/LoginPage.svelte +++ b/packages/shared-auth-ui/src/pages/LoginPage.svelte @@ -29,6 +29,7 @@ googleSignInFailed: string; signInSuccess: string; googleSignInSuccess: string; + emailVerified?: string; } const defaultTranslations: LoginTranslations = { @@ -54,6 +55,7 @@ googleSignInFailed: 'Google sign in failed', signInSuccess: 'Successfully signed in. Redirecting...', googleSignInSuccess: 'Successfully signed in with Google. Redirecting...', + emailVerified: 'Email successfully verified! Please sign in.', }; interface Props { @@ -74,6 +76,10 @@ appSlider?: Snippet; headerControls?: Snippet; translations?: Partial; + /** Show email verified success banner */ + verified?: boolean; + /** Pre-fill email field (e.g., after email verification) */ + initialEmail?: string; } let { @@ -94,6 +100,8 @@ appSlider, headerControls, translations = {}, + verified = false, + initialEmail = '', }: Props = $props(); const t = $derived({ ...defaultTranslations, ...translations }); @@ -101,7 +109,7 @@ let loading = $state(false); let error = $state(null); let errorField = $state<'email' | 'password' | 'general' | null>(null); - let email = $state(''); + let email = $state(initialEmail); let password = $state(''); let showPassword = $state(false); let rememberMe = $state(false); @@ -110,6 +118,7 @@ let emailInput: HTMLInputElement; let passwordInput: HTMLInputElement; let successAnnouncement = $state(''); + let showVerifiedBanner = $state(verified); // Theme state - can be toggled manually, defaults to system preference let userThemePreference = $state<'light' | 'dark' | null>(null); @@ -145,7 +154,12 @@ } $effect(() => { - if (emailInput) emailInput.focus(); + // Focus password field if email is pre-filled, otherwise focus email + if (initialEmail && passwordInput) { + passwordInput.focus(); + } else if (emailInput) { + emailInput.focus(); + } }); function isValidEmail(email: string): boolean { @@ -296,6 +310,21 @@
+ {#if showVerifiedBanner} +
+ +

{t.emailVerified}

+ +
+ {/if} +

{t.title}

{t.subtitle}

@@ -612,6 +641,39 @@ color: rgba(0, 0, 0, 0.6); } + .verified-banner { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem; + margin-bottom: 1rem; + border-radius: 0.75rem; + background: rgba(34, 197, 94, 0.15); + border: 1px solid rgba(34, 197, 94, 0.3); + color: #22c55e; + font-size: 0.875rem; + position: relative; + } + + .verified-banner-close { + position: absolute; + right: 0.5rem; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: #22c55e; + font-size: 1.25rem; + cursor: pointer; + padding: 0.25rem; + line-height: 1; + opacity: 0.7; + } + + .verified-banner-close:hover { + opacity: 1; + } + .error-message { display: flex; align-items: center;