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
-
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.
-
Sidebar
- Logo (link to
/app/[slug]/ticketsvia 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.
- Logo (link to
-
Top bar
- Page title (dynamic per route).
- Search input placeholder (no search logic until M12).
- Notification bell placeholder (no panel until M08).
-
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
| Criterion | Status |
|---|---|
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
-
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.
- Hook or util: accepts path (e.g.
-
Redirects
/app/[slug](exact) → redirect to/app/[slug]/tickets.- Ensure all settings and app routes are under single layout.
-
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
| Criterion | Status |
|---|---|
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
-
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; persistlast_tenant_slug(cookie or session).
-
Visibility
- Only show when user has more than one tenant membership. Single-tenant users can have no switcher or disabled state.
-
Persistence
- On load and on switch, store last-used slug so post-login redirect (M02) uses it.
Acceptance criteria
| Criterion | Status |
|---|---|
| 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
-
Role in context
- Middleware (M02) already sets role. Ensure role (admin | agent | viewer) is available in layout and tRPC.
-
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).
-
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
| Criterion | Status |
|---|---|
| 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
-
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.
-
Empty states
- Shared component: illustration or icon, headline, optional subline, optional CTA. Use on tickets list ("No tickets yet"), inbox list (later), etc.
-
Loading states
- Skeleton components for list rows and content blocks. Use in layout or Suspense where data is loading (tRPC in M07).
-
Error boundary
- At least one error boundary for app shell or layout so uncaught errors show a friendly message.
-
i18n (Section 3.13)
- All app shell and placeholder strings use
useTranslations(client) orgetTranslations(server) from next-intl; use namespacescommon, 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.
- All app shell and placeholder strings use
Acceptance criteria
| Criterion | Status |
|---|---|
| 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
- 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.
- Logged-in user sees sidebar and top bar; nav links use correct tenant path; theme toggle and sign out from shell work; visiting
Acceptance criteria
| Criterion | Status |
|---|---|
| App shell and nav E2E tests pass (or manual sign-off until automated). | ✅ |
Milestone 3 sign-off
| Criterion | Status |
|---|---|
| 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 |