Skip to content

Foundation — Pages

Scope note: Foundation owns only two system pages — dashboard placeholder and forbidden. The interesting content of this doc is the patterns these pages establish (composition of AppShell, semantic buttons, display primitives, error UX) — every later feature page reuses them.

Login was deleted. The pre-existing src/routes/login.tsx was vibe-coded against no real auth contract and gets removed in the M0 cleanup ticket. Login and account screens land back with identity's first ticket.

Page: Dashboard placeholder

Field Value
Route /
File src/routes/_auth/index.tsx (exists)
Layer Inside AppShell (_auth group)
Owner ticket Touched by M2 (AppShell ticket) — currently empty; gets a placeholder card "Welcome — modules load here as they ship".
Permissions None today (no auth wiring). Once identity ships, this page renders for any authenticated user.

Surface. Inside AppShell. Single welcome card with the logged-in user's name (placeholder string until identity ships) and a one-line "your modules will appear in the sidebar as they go live" message. Intentionally bare — this page will be redesigned per a future requirement once 3+ modules exist (KPI cards, recent activity, etc.).

Components used. - AppShell (M2) - PageContainer variant=detail (M2) - Card, PageHeader (M1)

Empty / error states. Renders the same regardless of role. No data fetched.

Prototype. ux-tests/prototypes/action-flow-prototypes/01-home.html — referenced for chrome only (the dashboard content itself is intentionally minimal).


Page: Forbidden

Field Value
Route /forbidden
File src/routes/_auth/forbidden.tsx (new — M2 ticket)
Layer Inside AppShell — user is authenticated but lacks the permission for what they tried to access.
Owner ticket M2 — AppShell ticket. Triggered by the error parser when it receives 403 FORBIDDEN_INSUFFICIENT_PERMISSION.
Permissions Any authenticated user (this is the page they land on when they fail a permission check).

Surface. Centered card with shield icon, large title t('common:errors.forbiddenTitle') ("Brak dostępu"), one-line body t('common:errors.forbiddenBody') ("Skontaktuj się z administratorem jeśli uważasz, że powinieneś mieć dostęp do tej strony."), one [Wróć do panelu] button linking to routes.dashboard.path.

Components used. - Card, Button (variant=default) from components/ui/.

Empty / error states. This is an error state. No fetch.

Prototype. None.


Composition patterns established here

These patterns are referenced by every future feature page plan:

  1. AppShell wraps every route under _auth/. Public routes (login, etc.) live outside _auth/ when they're added by identity.
  2. PageContainer with list or detail variant owns the page body's max-width, padding, and background.
  3. PageHeader at the top of every authenticated page — owns the page title (from the route registry's i18nKey), optional breadcrumbs, and the right-aligned primary actions slot.
  4. Forms use components/forms/ field wrappers (M4), never raw <input> or shadcn <Input> directly. The wrappers handle label + error + i18n.
  5. Errors surface in three places per ../../standards.md § Error Envelope:
  6. Field errors → inline under the field via applyFieldErrorsToForm.
  7. Non-field errors with a known code → useApiErrorToast() (helper added in M3 errors ticket).
  8. 403 → navigate to /forbidden. 409 → refresh-and-retry inline. 5xx → "Spróbuj ponownie później" toast.
  9. No permission gates run today — the requiredPermission props on buttons are wired but always pass. Identity ticket flips the switch.