feat(auth): add missing auth pages for zitare and planta

- Add zitare login page with standard pattern
- Add zitare forgot-password page
- Add planta forgot-password page
- Refactor planta register to use shared RegisterPage component

All apps now have consistent login, register, and forgot-password pages
using the shared auth-ui components and i18n translations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-02-01 13:09:42 +01:00
parent 45152ee954
commit df2c518a5c
4 changed files with 179 additions and 92 deletions

View file

@ -0,0 +1,32 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { locale } from 'svelte-i18n';
import { ForgotPasswordPage } from '@manacore/shared-auth-ui';
import { getForgotPasswordTranslations } from '@manacore/shared-i18n';
import { PlantaLogo } from '@manacore/shared-branding';
import { authStore } from '$lib/stores/auth.svelte';
import '$lib/i18n';
// Get translations based on current locale
const translations = $derived(getForgotPasswordTranslations($locale || 'de'));
async function handleForgotPassword(email: string) {
return authStore.resetPassword(email);
}
</script>
<svelte:head>
<title>{translations.titleForm} | Planta</title>
</svelte:head>
<ForgotPasswordPage
appName="Planta"
logo={PlantaLogo}
primaryColor="#22c55e"
onForgotPassword={handleForgotPassword}
{goto}
loginPath="/login"
lightBackground="#dcfce7"
darkBackground="#052e16"
{translations}
/>

View file

@ -1,103 +1,51 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import { locale } from 'svelte-i18n';
import { RegisterPage } from '@manacore/shared-auth-ui';
import { getRegisterTranslations } from '@manacore/shared-i18n';
import { PlantaLogo } from '@manacore/shared-branding';
import { authStore } from '$lib/stores/auth.svelte';
import '$lib/i18n';
let email = $state('');
let password = $state('');
let passwordConfirm = $state('');
let error = $state('');
let loading = $state(false);
async function handleSubmit(e: Event) {
e.preventDefault();
error = '';
if (password !== passwordConfirm) {
error = 'Passwörter stimmen nicht überein';
return;
}
if (password.length < 8) {
error = 'Passwort muss mindestens 8 Zeichen lang sein';
return;
}
loading = true;
const result = await authStore.signUp(email, password);
if (result.success) {
if (result.needsVerification) {
error = 'Bitte bestätige deine E-Mail-Adresse';
} else {
goto('/dashboard');
// Get redirect URL from sessionStorage
const redirectTo = $derived.by(() => {
if (browser) {
const sessionRedirect = sessionStorage.getItem('auth-return-url');
if (sessionRedirect) {
sessionStorage.removeItem('auth-return-url');
return sessionRedirect;
}
} else {
error = result.error || 'Registrierung fehlgeschlagen';
}
return '/dashboard';
});
loading = false;
// Get translations based on current locale
const translations = $derived(getRegisterTranslations($locale || 'de'));
async function handleSignUp(email: string, password: string) {
return authStore.signUp(email, password);
}
async function handleResendVerification(email: string) {
return authStore.resendVerificationEmail(email);
}
</script>
<form onsubmit={handleSubmit} class="space-y-4">
{#if error}
<div class="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
{error}
</div>
{/if}
<svelte:head>
<title>{translations.title} | Planta</title>
</svelte:head>
<div>
<label for="email" class="block text-sm font-medium text-foreground">E-Mail</label>
<input
id="email"
type="email"
bind:value={email}
required
class="input mt-1 w-full"
placeholder="deine@email.de"
/>
</div>
<div>
<label for="password" class="block text-sm font-medium text-foreground">Passwort</label>
<input
id="password"
type="password"
bind:value={password}
required
minlength="8"
class="input mt-1 w-full"
placeholder="Mindestens 8 Zeichen"
/>
</div>
<div>
<label for="passwordConfirm" class="block text-sm font-medium text-foreground">
Passwort bestätigen
</label>
<input
id="passwordConfirm"
type="password"
bind:value={passwordConfirm}
required
class="input mt-1 w-full"
placeholder="Passwort wiederholen"
/>
</div>
<button type="submit" class="btn btn-primary w-full" disabled={loading}>
{#if loading}
<span
class="inline-block h-4 w-4 animate-spin rounded-full border-2 border-white border-r-transparent"
></span>
{:else}
Registrieren
{/if}
</button>
<p class="text-center text-sm text-muted-foreground">
Bereits ein Konto?
<a href="/login" class="text-primary hover:underline">Anmelden</a>
</p>
</form>
<RegisterPage
appName="Planta"
logo={PlantaLogo}
primaryColor="#22c55e"
onSignUp={handleSignUp}
onResendVerification={handleResendVerification}
{goto}
successRedirect={redirectTo}
loginPath="/login"
lightBackground="#dcfce7"
darkBackground="#052e16"
{translations}
/>

View file

@ -0,0 +1,37 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { locale } from 'svelte-i18n';
import { ForgotPasswordPage } from '@manacore/shared-auth-ui';
import { getForgotPasswordTranslations } from '@manacore/shared-i18n';
import { ZitareLogo } from '@manacore/shared-branding';
import { authStore } from '$lib/stores/auth.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
import '$lib/i18n';
// Get translations based on current locale
const translations = $derived(getForgotPasswordTranslations($locale || 'de'));
async function handleForgotPassword(email: string) {
return authStore.resetPassword(email);
}
</script>
<svelte:head>
<title>{translations.titleForm} - Zitare</title>
</svelte:head>
<ForgotPasswordPage
appName="Zitare"
logo={ZitareLogo}
primaryColor="#f59e0b"
onForgotPassword={handleForgotPassword}
{goto}
loginPath="/login"
lightBackground="#fffbeb"
darkBackground="#1c1917"
{translations}
>
{#snippet headerControls()}
<LanguageSelector />
{/snippet}
</ForgotPasswordPage>

View file

@ -0,0 +1,70 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { browser } from '$app/environment';
import { locale } from 'svelte-i18n';
import { LoginPage } from '@manacore/shared-auth-ui';
import { getLoginTranslations } from '@manacore/shared-i18n';
import { ZitareLogo } from '@manacore/shared-branding';
import { authStore } from '$lib/stores/auth.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
import '$lib/i18n';
// Get redirect URL from query params or sessionStorage
const redirectTo = $derived.by(() => {
const queryRedirect = $page.url.searchParams.get('redirectTo');
if (queryRedirect) return queryRedirect;
if (browser) {
const sessionRedirect = sessionStorage.getItem('auth-return-url');
if (sessionRedirect) {
sessionStorage.removeItem('auth-return-url');
return sessionRedirect;
}
}
return '/';
});
// 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);
}
async function handleResendVerification(email: string) {
return authStore.resendVerificationEmail(email);
}
</script>
<svelte:head>
<title>{translations.title} - Zitare</title>
</svelte:head>
<LoginPage
appName="Zitare"
logo={ZitareLogo}
primaryColor="#f59e0b"
onSignIn={handleSignIn}
onResendVerification={handleResendVerification}
{goto}
enableGoogle={false}
enableApple={false}
successRedirect={redirectTo}
registerPath="/register"
forgotPasswordPath="/forgot-password"
lightBackground="#fffbeb"
darkBackground="#1c1917"
{translations}
{verified}
{initialEmail}
>
{#snippet headerControls()}
<LanguageSelector />
{/snippet}
</LoginPage>