feat(onboarding): add A2 household setup page with responsive progress sidebar layout

Desktop: 300px ProgressSidebar (step 1 active) + flex form area.
Mobile: "Schritt 1 von 3" eyebrow + HouseholdSetupForm.
Also stubs /household/staples as redirect target for A3.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-02 19:20:02 +02:00
parent e85a7ca313
commit 6de7f5a9b5
4 changed files with 109 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
<script lang="ts">
import ProgressSidebar from '$lib/components/ProgressSidebar.svelte';
import HouseholdSetupForm from '$lib/onboarding/HouseholdSetupForm.svelte';
type FormResult = {
errors?: Record<string, string>;
name?: string;
} | null;
let { form = null }: { form?: FormResult } = $props();
</script>
<svelte:head>
<title>Haushalt einrichten — Mealplan</title>
</svelte:head>
<div class="flex min-h-screen bg-[var(--color-page)]">
<!-- Desktop progress sidebar — hidden on mobile -->
<aside
class="hidden md:flex w-[300px] flex-shrink-0 flex-col bg-[var(--color-surface)] border-r border-[var(--color-border)] p-[40px_28px]"
aria-hidden="true"
>
<ProgressSidebar currentStep={1} />
</aside>
<!-- Form area -->
<main class="flex flex-1 flex-col justify-center">
<!-- Mobile: step indicator (visible only on mobile) -->
<div class="md:hidden px-[20px] pt-[24px] pb-[0]">
<p class="text-[10px] font-medium tracking-[.08em] uppercase text-[var(--color-text-muted)]">
Schritt 1 von 3
</p>
</div>
<!-- Form -->
<div class="px-[20px] py-[24px] md:px-[56px] md:py-[48px]">
<div class="max-w-[420px]">
<HouseholdSetupForm {form} />
</div>
</div>
</main>
</div>

View File

@@ -0,0 +1,53 @@
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/svelte';
import Page from './+page.svelte';
vi.mock('$app/forms', () => ({
enhance: () => ({ destroy: () => {} })
}));
describe('household setup page', () => {
it('renders the form heading', () => {
render(Page);
expect(screen.getByText('Haushalt benennen')).toBeInTheDocument();
});
it('renders the household name input', () => {
render(Page);
expect(screen.getByLabelText('Haushaltsname')).toBeInTheDocument();
});
it('renders the continue button', () => {
render(Page);
expect(screen.getByRole('button', { name: /weiter/i })).toBeInTheDocument();
});
it('renders the ProgressSidebar with step 1 active', () => {
render(Page);
const step1 = screen.getByTestId('step-1');
expect(step1).toHaveAttribute('aria-current', 'step');
});
it('renders steps 2 and 3 as future steps', () => {
render(Page);
expect(screen.getByTestId('step-2')).not.toHaveAttribute('aria-current');
expect(screen.getByTestId('step-3')).not.toHaveAttribute('aria-current');
});
it('does not render app navigation chrome', () => {
render(Page);
// No nav links like Planer or Rezepte (those are app shell nav items)
expect(screen.queryByText('Planer')).not.toBeInTheDocument();
expect(screen.queryByText('Rezepte')).not.toBeInTheDocument();
});
it('sets the page title', () => {
render(Page);
expect(document.title).toBe('Haushalt einrichten — Mealplan');
});
it('renders the mobile step indicator text', () => {
render(Page);
expect(screen.getByText(/schritt 1 von 3/i)).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,7 @@
<svelte:head>
<title>Vorräte einrichten — Mealplan</title>
</svelte:head>
<div class="flex min-h-screen items-center justify-center bg-[var(--color-page)]">
<p class="text-[var(--color-text-muted)]">A3 — Vorräte einrichten (coming soon)</p>
</div>

View File

@@ -0,0 +1,7 @@
import '@testing-library/jest-dom/vitest';
import { configure } from '@testing-library/dom';
// Exclude elements inside aria-hidden containers from text queries,
// so that visually-hidden sidebars (e.g. ProgressSidebar in onboarding pages)
// don't create duplicate text matches when the same text appears in the main content.
configure({ defaultIgnore: 'script, style, [aria-hidden="true"] *' });