Skip to main content

Milestone 3: App shell and navigation

Purpose: Authenticated users see a consistent app shell (sidebar, nav, user menu), can switch tenants, and have theme and empty/loading patterns in place.

Exit state: App layout for /app/[slug]/**; useTenantPath; tenant switcher; role-based nav; dark/light theme; empty and loading states.

Spec reference: §2.7 App shell and navigation, §3.13 Internationalisation (i18n), §6.1 Navigation, §6.10 Layout shells, Component strategy, Global UI patterns.

Prerequisites: M02 (Auth and tenant identity).


3.1 App layout and shell

Tasks

  1. App layout (app/app/[slug]/layout.tsx)

    • Resolve tenant and user from middleware (headers or context); pass to children.
    • Render shell: fixed left sidebar (e.g. 240px), top bar, main content area. Main is scrollable.
  2. Sidebar

    • Logo (link to /app/[slug]/tickets via useTenantPath).
    • Primary nav: Inboxes, Tickets, Settings (links use useTenantPath).
    • Placeholder for inbox list (e.g. "Inboxes" or empty list until M05).
    • User menu at bottom: profile link, theme toggle, sign out.
  3. Top bar

    • Page title (dynamic per route).
    • Search input placeholder (no search logic until M12).
    • Notification bell placeholder (no panel until M08).
  4. Responsive

    • Sidebar collapses to icon-only on tablet (breakpoint); drawer on mobile. Document breakpoints (Section 6.10: sm 640, md 768, lg 1024, xl 1280).

Acceptance criteria

CriterionStatus
Every page under /app/[slug]/** shows the same sidebar and top bar.
Nav links point to correct tenant path (e.g. /app/acme/tickets when slug is acme).
User menu: profile, theme, sign out work (sign out implemented in M02).
Layout is responsive; sidebar collapses on smaller viewports.

3.2 useTenantPath and routing

Tasks

  1. useTenantPath helper

    • Hook or util: accepts path (e.g. /tickets, /settings/general) and returns /app/[slug]/<path> using current slug from context/params.
    • Use in all in-app links; no hardcoded /app/xyz/ in components.
  2. Redirects

    • /app/[slug] (exact) → redirect to /app/[slug]/tickets.
    • Ensure all settings and app routes are under single layout.
  3. Placeholder pages

    • /app/[slug]/tickets: empty state "No tickets yet" or table placeholder.
    • /app/[slug]/inboxes/[id]: placeholder.
    • /app/[slug]/settings/*: placeholder or minimal (e.g. "Settings" with nav).
    • /app/[slug]/reports: placeholder.
    • /app/[slug]/profile: placeholder.
    • /app/[slug]/locked: minimal "Account locked" card (logic in M10).

Acceptance criteria

CriterionStatus
No component uses raw /app/ + slug; all use useTenantPath or equivalent.
Visiting /app/acme redirects to /app/acme/tickets.
Placeholder pages render without error; nav highlights current section.

3.3 Tenant (workspace) switcher

Tasks

  1. Switcher UI

    • In sidebar or user menu: list of tenants for current user (query tenant_memberships by userId).
    • Show current tenant name/slug; dropdown or list to switch.
    • On select: navigate to /app/[new_slug]/tickets; persist last_tenant_slug (cookie or session).
  2. Visibility

    • Only show when user has more than one tenant membership. Single-tenant users can have no switcher or disabled state.
  3. Persistence

    • On load and on switch, store last-used slug so post-login redirect (M02) uses it.

Acceptance criteria

CriterionStatus
User with multiple tenants sees switcher; selecting another tenant navigates to that slug and updates context.
After switch, nav and data reflect new tenant (tenantId in context).
Last-used tenant is used on next login (M02 behaviour).

3.4 Role-based navigation

Tasks

  1. Role in context

    • Middleware (M02) already sets role. Ensure role (admin | agent | viewer) is available in layout and tRPC.
  2. Nav visibility

    • Settings: only show to Admin (and optionally Agent for limited items per spec). Viewer: hide or disable Settings.
    • Tickets, Inboxes: all roles; Viewers read-only (enforce in API in M07).
  3. Enforcement

    • API/tRPC must enforce role per procedure (e.g. settings mutations require admin/agent); UI hides nav and actions for Viewer. Full procedure-level enforcement is implemented in later milestones (e.g. M07 for tickets, M09 for team).

Acceptance criteria

CriterionStatus
Admin sees Settings in nav; Viewer does not (or sees disabled).
Role is available in app context for conditional rendering.

3.5 Theming and shared UI

Tasks

  1. Theme (Section 2.7, 6.10)

    • next-themes: provider in root layout; theme toggle in user menu (light / dark / system).
    • Persist preference (cookie or localStorage); apply class or data-theme on <html>; avoid FOUC (script in head or class on root).
    • All placeholder pages and components use Tailwind dark: variants so theme applies.
  2. Empty states

    • Shared component: illustration or icon, headline, optional subline, optional CTA. Use on tickets list ("No tickets yet"), inbox list (later), etc.
  3. Loading states

    • Skeleton components for list rows and content blocks. Use in layout or Suspense where data is loading (tRPC in M07).
  4. Error boundary

    • At least one error boundary for app shell or layout so uncaught errors show a friendly message.
  5. i18n (Section 3.13)

    • All app shell and placeholder strings use useTranslations (client) or getTranslations (server) from next-intl; use namespaces common, and add keys to relevant namespaces (e.g. nav labels in common or a dedicated shell namespace). No hardcoded English in components.
    • Dates and relative time (e.g. in placeholders) use useFormatter (formatRelativeTime, formatDateTime). Section 3.13.

Acceptance criteria

CriterionStatus
User can switch light/dark/system; preference persists across reloads; no flash of wrong theme.
Empty state component used on ticket list placeholder.
Loading skeleton used where async data will be (e.g. ticket list).
Error boundary catches and displays error in app area.
App shell and placeholder copy use next-intl (useTranslations/getTranslations); no hardcoded user-visible strings.

3.6 E2E tests

See INDEX — Testing strategy (E2E and unit).

Tasks

  1. App shell E2E
    • Logged-in user sees sidebar and top bar; nav links use correct tenant path; theme toggle and sign out from shell work; visiting /app/[slug] redirects to /app/[slug]/tickets; placeholder pages (tickets, settings, profile) render without error.

Acceptance criteria

CriterionStatus
App shell and nav E2E tests pass (or manual sign-off until automated).

Milestone 3 sign-off

CriterionStatus
All tasks in 3.1–3.6 complete.✅ Done
All acceptance criteria met.✅ Done
Authenticated user sees full app shell; can switch tenant and theme; placeholders and nav consistent.✅ Done
Ready for M04 (Onboarding and tenant settings).✅ Done