Files
familienarchiv/frontend/src/routes/login/+page.svelte
Marcel daea748a20
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m37s
CI / OCR Service Tests (push) Successful in 32s
CI / OCR Service Tests (pull_request) Successful in 30s
CI / Backend Unit Tests (push) Failing after 2m47s
CI / Unit & Component Tests (pull_request) Failing after 2m29s
CI / Backend Unit Tests (pull_request) Failing after 2m46s
feat(frontend): invite-based registration UI
- Add /register route with invite code prefill, password show/hide
- Add /login?registered=1 success banner
- Add /admin/invites page: list, create, revoke, copy link
- Add Einladungen nav section to admin sidebar (ADMIN_USER perm)
- Add invite error codes to errors.ts
- Add 48 i18n keys across de/en/es
- Update hooks.server.ts to allow public access to invite/register API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 01:01:19 +02:00

106 lines
3.0 KiB
Svelte

<script lang="ts">
import { m } from '$lib/paraglide/messages.js';
import AuthHeader from '../AuthHeader.svelte';
let {
data,
form
}: { data: { registered: boolean }; form?: { error?: string; success?: boolean } } = $props();
</script>
<svelte:head>
<title>{m.page_title_login()}</title>
</svelte:head>
<div class="flex min-h-screen flex-col bg-canvas">
<AuthHeader />
<div class="flex flex-1 items-center justify-center px-4">
<div class="w-full max-w-sm">
<!-- Logo -->
<div class="mb-10 text-center">
<a href="/" class="inline-flex items-center" aria-label="Familienarchiv">
<span class="font-sans text-2xl font-bold tracking-widest text-ink uppercase"
>Familienarchiv</span
>
</a>
</div>
<!-- Card -->
<div class="rounded-sm border border-line bg-surface p-8 shadow-sm">
{#if data.registered}
<div
role="status"
aria-live="polite"
class="mb-5 rounded-sm border border-green-200 bg-green-50 px-4 py-3 font-sans text-xs font-medium text-green-800"
>
{m.login_registered_success()}
</div>
{/if}
<h1 class="mb-6 font-sans text-sm font-bold tracking-widest text-ink uppercase">
{m.login_heading()}
</h1>
<form method="POST" action="?/login" class="space-y-5">
<div>
<label
for="email"
class="mb-1.5 block font-sans text-xs font-bold tracking-widest text-ink-2 uppercase"
>{m.login_label_email()}</label
>
<input
type="email"
name="email"
id="email"
required
autocomplete="email"
class="block w-full border border-line px-3 py-2.5 font-serif text-sm text-ink placeholder-ink-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
/>
</div>
<div>
<label
for="password"
class="mb-1.5 block font-sans text-xs font-bold tracking-widest text-ink-2 uppercase"
>{m.login_label_password()}</label
>
<input
type="password"
name="password"
id="password"
required
autocomplete="current-password"
class="block w-full border border-line px-3 py-2.5 font-serif text-sm text-ink placeholder-ink-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
/>
</div>
{#if form?.error}
<div class="text-center font-sans text-xs font-medium text-red-600">{form.error}</div>
{/if}
<button
type="submit"
class="mt-2 w-full bg-primary py-2.5 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-colors hover:bg-primary/90"
>
{m.login_btn_submit()}
</button>
<div class="mt-4 text-center">
<a
href="/forgot-password"
class="font-sans text-xs text-ink-3 transition-colors hover:text-ink"
>{m.login_forgot_password()}</a
>
</div>
</form>
</div>
</div>
</div>
<!-- Footer -->
<div class="py-4 text-center">
<p class="font-sans text-xs tracking-widest text-ink-3 uppercase">Familienarchiv</p>
</div>
</div>