From abe0a21966adf389897edc161c195ebfce551c17 Mon Sep 17 00:00:00 2001 From: Till JS Date: Wed, 8 Apr 2026 12:41:19 +0200 Subject: [PATCH] refactor(auth-ui): tighten LoginPage UX, a11y, and dead code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LoginPage cleanup: - Drop dev pre-fill credentials and the secret logo-as-button trick - Remove duplicate in-component theme toggle; accept isDark as a prop and let the (auth) layout's global theme toggle drive it - Move passkey CTA below the password form so the primary flow stays primary - Remove the dead "Angemeldet bleiben" checkbox (was bound but never forwarded to onSignIn) - Fix the skip-to-form link to use sr-only/focus:not-sr-only so it only appears on keyboard focus - Fix the "oder" divider to render its before/after hairlines by setting an explicit color on the parent - Wire focus-visible outlines on all interactive controls - Bump 0.6 → 0.75 opacity on subtitle text for AA contrast - Drop opacity-60 from the headerControls wrapper Robustness: - Track all setTimeout IDs in a Set and clear them in an effect cleanup so navigation away doesn't fire stale callbacks (success redirects, error shake, focus restore) - Replace (result as any) casts with the new typed AuthResult fields - New resolveErrorCode() helper prefers result.errorCode and falls back to legacy string matching, so rate-limit / account-lock detection survives i18n - WebAuthn Conditional UI: on mount, if PublicKeyCredential.isConditionalMediationAvailable(), call onSignInWithPasskey({ conditional: true }) so passkeys appear inline in the email autofill dropdown - Extract the dismissible success-banner markup into a {#snippet successBanner} and reuse it for the verified / verification-sent / magic-link-sent cases (~50 lines of duplicate JSX out) Page wrappers: - login/+page.svelte passes isDark={theme.isDark} so the in-app theme store drives both layouts - register/+page.svelte wraps trackGuestConversion() in queueMicrotask + try/catch so analytics can never block the success redirect - Drop the dead baseSignupCredits={25} prop from register/+page.svelte (RegisterPage never accepted it) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../web/src/routes/(auth)/login/+page.svelte | 4 + .../src/routes/(auth)/register/+page.svelte | 12 +- .../shared-auth-ui/src/pages/LoginPage.svelte | 370 +++++++++--------- 3 files changed, 202 insertions(+), 184 deletions(-) diff --git a/apps/mana/apps/web/src/routes/(auth)/login/+page.svelte b/apps/mana/apps/web/src/routes/(auth)/login/+page.svelte index 0dc09aa66..2031d4863 100644 --- a/apps/mana/apps/web/src/routes/(auth)/login/+page.svelte +++ b/apps/mana/apps/web/src/routes/(auth)/login/+page.svelte @@ -8,8 +8,11 @@ import AppSlider from '$lib/components/AppSlider.svelte'; import LanguageSelector from '$lib/components/LanguageSelector.svelte'; import { authStore } from '$lib/stores/auth.svelte'; + import { theme } from '$lib/stores/theme'; import { APP_VERSION, BUILD_TIME } from '$lib/version'; + const isDark = $derived(theme.isDark); + // Get translations based on current locale const translations = $derived(getLoginTranslations($locale || 'de')); @@ -46,6 +49,7 @@ {translations} {verified} {initialEmail} + {isDark} version={APP_VERSION} buildTime={BUILD_TIME} > diff --git a/apps/mana/apps/web/src/routes/(auth)/register/+page.svelte b/apps/mana/apps/web/src/routes/(auth)/register/+page.svelte index 54720c898..07f46835f 100644 --- a/apps/mana/apps/web/src/routes/(auth)/register/+page.svelte +++ b/apps/mana/apps/web/src/routes/(auth)/register/+page.svelte @@ -14,7 +14,16 @@ async function handleSignUp(email: string, password: string) { const result = await authStore.signUp(email, password); - if (result.success) trackGuestConversion(); + if (result.success) { + // Tracking must never block the success redirect. + queueMicrotask(() => { + try { + trackGuestConversion(); + } catch { + /* swallow tracking errors */ + } + }); + } return result; } @@ -33,7 +42,6 @@ primaryColor="#6366f1" onSignUp={handleSignUp} onResendVerification={handleResendVerification} - baseSignupCredits={25} {goto} successRedirect="/" loginPath="/login" diff --git a/packages/shared-auth-ui/src/pages/LoginPage.svelte b/packages/shared-auth-ui/src/pages/LoginPage.svelte index 58ddb4526..4a89cb408 100644 --- a/packages/shared-auth-ui/src/pages/LoginPage.svelte +++ b/packages/shared-auth-ui/src/pages/LoginPage.svelte @@ -1,7 +1,24 @@ @@ -428,7 +475,7 @@ + +{/snippet} +
- - - {#if headerControls} -
+
{@render headerControls()}
{/if} @@ -471,13 +520,12 @@
- +

{appName}

@@ -508,7 +556,7 @@

{useBackupCode ? t.twoFactorBackupSubtitle : t.twoFactorSubtitle}

@@ -606,23 +654,8 @@ {t.twoFactorBackToLogin} {:else} - {#if showVerifiedBanner} -
- -

{t.emailVerified}

- -
+ {#if showVerifiedBanner && t.emailVerified} + {@render successBanner(t.emailVerified, () => (showVerifiedBanner = false))} {/if}
@@ -634,63 +667,17 @@

{t.subtitle}

- {#if passkeyAvailable && onSignInWithPasskey} - -
- {t.orDivider} -
- {/if} - - {#if verificationEmailSent} -
- -

{t.verificationEmailSent}

- -
+ {#if verificationEmailSent && t.verificationEmailSent} + {@render successBanner( + t.verificationEmailSent, + () => (verificationEmailSent = false) + )} {/if} {#if isLockedOut} @@ -826,19 +813,8 @@
- -
- + +
+ {#if passkeyAvailable && onSignInWithPasskey} +
+ {t.orDivider} +
+ + {/if} + {#if onSendMagicLink} {#if magicLinkSent} -
- -

{t.magicLinkSent?.replace('{email}', email)}

- -
+ {@render successBanner( + t.magicLinkSent?.replace('{email}', email) ?? '', + () => (magicLinkSent = false) + )} {:else}