munch-ease-frontend/frontend-spec.md
2025-11-01 13:31:56 +11:00

12 KiB
Raw Blame History

Frontend Specification: Household Multi-Tenancy (v2)

1. Objective

Transition the frontend from a single-tenant application to a multi-tenant one centered around the concept of "Households". This involves refactoring the existing authentication system, user onboarding, and data presentation to ensure all information (recipes, meals, shopping lists) is scoped to the active household.

This plan is adapted to the existing codebase, focusing on refactoring rather than starting from scratch.

2. Core Concepts

  • Household: A private group of users. All data is isolated and only visible to members of that household.
  • URL-based Tenancy: The active household is determined by the URL, e.g., /the-smiths/recipes. This makes household context explicit and shareable.
  • Household Switching: Users belonging to multiple households can switch between them.
  • Authentication: Evolve the existing system from a simple username login to a robust one supporting email/password and Google OAuth, using JWTs.

3. User Flows & UI Requirements

3.1. Authentication & Onboarding

  • Login Page (/login):
    • Refactor src/components/LoginPage.vue.
    • Replace the current username input with:
      • Email & Password login fields.
      • A "Sign in with Google" button.
    • Add a link to the "Create Account" page (/create-account).
  • Create Account Page (/create-account):
    • Create a new view src/views/CreateAccount.vue.
    • UI components for:
      • Email, Display Name, and Password input.
      • "Sign up with Google" button.
      • A link back to the "Login" page.
  • Post-Login Household Selection (/welcome):
    • Create a new view src/views/Welcome.vue.
    • After a new user logs in for the first time, they are directed here.
    • The page should present two choices:
      1. Create a new household: A simple form with "Household Name".
      2. Join an existing household: This section should clearly state: "To join a household, ask an existing member to send an invitation to your email address."
  • Invitation Flow:
    • A user receives an email with a link like https://<app-domain>/invitations/accept?token=....
    • Visiting this link while logged in should add them to the household and redirect them to that household's dashboard.

3.2. In-App Experience

  • Household-Scoped URLs:
    • All application routes must be nested under a household slug: /:householdSlug/recipes, /:householdSlug/shopping, etc.
    • The router must be updated to handle this dynamic parameter. The householdSlug will be used in all API calls to fetch household-specific data.
  • Household Switcher:
    • Create a new component src/components/HouseholdSwitcher.vue.
    • Place it in a prominent location (e.g., inside App.vue's navigation bar).
    • It should list all households the current user is a member of.
    • Clicking a household name should navigate the user to the dashboard of that household, e.g., /<new-household-slug>/dashboard.
  • Invite Members UI:
    • Create a new view src/views/HouseholdSettings.vue.
    • It should be accessible at /:householdSlug/settings/members.
    • It should contain a simple form to enter an email address and a button to "Send Invitation".
    • It should also list current household members.

4. Actionable Implementation Steps

  1. [ ] Refactor Authentication State & API:

    • Modify src/api/auth.ts:
      • Replace login(username: string) with login(email, password).
      • Add createAccount(email, displayName, password), handleGoogleLogin(token), and logout().
      • The API client should handle storing and clearing the auth token (e.g., from localStorage).
    • Modify src/composables/useAuth.ts:
      • Update the login function to accept email/password.
      • Add a register function.
      • The user ref should now hold the global User profile, and you should add a new state for households and activeHousehold.
      • The logout function should clear the JWT and all user state.
  2. [ ] Update Router for Multi-Tenancy:

    • Modify src/router/index.ts:
      • Add new public routes: /create-account, /welcome, and /invitations/accept.
      • Refactor the beforeEach guard:
        • It should allow access to public routes.
        • After login, it must fetch the user's households.
        • If the user has no households, redirect to /welcome.
        • If the user has households but is at the root (/), redirect to the first household's dashboard (e.g., /${householdSlug}/dashboard).
      • Nest existing routes: All current data-related routes (/meals, /shopping, etc.) must be moved as children of a new dynamic /:householdSlug route.
  3. [ ] Implement Onboarding and Invitation Flows:

    • Build the Welcome.vue view for creating the first household.
    • Build the CreateAccount.vue view.
    • Build the "Accept Invitation" page (/invitations/accept?token=...). It should take the token from the URL, call the API, and redirect on success.
  4. [ ] Integrate Household Context into the App:

    • Create src/composables/useHousehold.ts: This composable should extract the householdSlug from the current route's params. It will provide the activeHouseholdSlug to any component or service that needs it.
    • Implement HouseholdSwitcher.vue: This component will use useAuth to get the list of the user's households and render navigation links.
    • Update API Services: All data-fetching calls (e.g., for recipes, meals) must be updated to use the activeHouseholdSlug from useHousehold. The API client wrapper should be modified to prepend this slug to the request URL.
      • Example: api.get('/recipes') becomes api.get(\/${activeHouseholdSlug.value}/recipes`)`.
  5. [ ] Implement Invitation UI:

    • Build the HouseholdSettings.vue view for inviting members and listing current members.
  6. [ ] Final Review & Cleanup:

    • Remove the old persons concept from the frontend code. The user from useAuth is now the primary identity.
    • Ensure all data displays are correctly filtered by the active household by verifying the householdSlug is passed in all API calls.
    • Test all user flows: new user signup, login, creating a household, joining via invitation, and switching between households.

0. Current State (Nov 2025) and Gap Analysis

Current implementation is single-tenant with username-based login and no household context.

What exists today

  • Auth
    • src/api/auth.ts: username login (login(username: string)), currentUser() via cookie user_id.
    • src/composables/useAuth.ts: stores Person | null, exposes login(username) and loadUser().
    • src/components/LoginPage.vue: lists persons and logs in by selected person name.
  • Routing
    • src/router/index.ts: flat routes (/recipes, /shopping, /mealplan); no /:householdSlug nesting or redirects.
    • Guard checks requiresAuth only; no household awareness.
  • SDK/API
    • src/api/sdk.ts: calls /api/v1/... without household context.
    • Generated OpenAPI types do not include household slug in paths; shapes are single-tenant.
  • Domain & UI
    • Identity revolves around Person; no household model, switcher, or settings views.

Gaps vs requirements

  • Authentication & onboarding
    • Missing email/password login, Google OAuth, account creation, logout, JWT storage/refresh.
    • Missing CreateAccount.vue, Welcome.vue, invitation acceptance flow.
  • Routing & URL-based tenancy
    • Missing /:householdSlug/... route nesting and post-login redirect logic.
    • No useHousehold.ts; no HouseholdSwitcher.vue.
  • API boundary
    • No household scoping passed to backend. Need a typed strategy (prefer header parameter) without breaking OpenAPI typing.
  • Cleanup
    • persons used across login and tests; must be deprecated in favor of authenticated user and their households.

Assumptions and constraints

  • Preserve strict TS (no any/unknown in app code) and keep OpenAPI as source of truth.
  • Do not rewrite typed API paths in code; prefer header or OpenAPI param for household scoping.

Amendment: API Services and Typing Strategy

To align with OpenAPI typing and README axioms, do not rewrite request paths to include the household slug.

  • Preferred: Backend exposes householdSlug as a path or header parameter in OpenAPI. Regenerate types and thread via params for each call.
  • Interim: Agree a header (e.g., X-Household-Slug) and inject it in src/api/client.ts for all requests based on the current routes slug. Keep existing typed paths untouched.

Action

  • Implement header injection in api/client.ts with a pluggable getter for the active slug (decoupled from Vue imports). Update this spec once backend finalizes the parameter shape.

Detailed Tasks by File/Module

New files

  • src/views/CreateAccount.vue: Email, Display Name, Password; Google signup; link to login.
  • src/views/Welcome.vue: Create first household or instructions to join via invite.
  • src/views/HouseholdSettings.vue: Invite members by email; list members.
  • src/components/HouseholdSwitcher.vue: Lists user households and navigates to selected household dashboard.
  • src/composables/useHousehold.ts: Exposes activeHouseholdSlug from route and small helpers.

Auth

  • src/api/auth.ts:
    • Replace login(username: string)login(email: string, password: string).
    • Add createAccount, handleGoogleLogin, logout; update currentUser to refresh JWT/session.
    • Store token per backend guidance; clear on logout.
  • src/composables/useAuth.ts:
    • Manage user, households, activeHousehold state; expose login, register, handleGoogleLogin, logout, loadUser, setActiveHousehold.

Router

  • src/router/index.ts:
    • Add public routes: /create-account, /welcome, /invitations/accept.
    • Nest feature routes under /:householdSlug.
    • Guard: after auth, fetch households; redirect root / to first household dashboard; route /welcome if none.
    • Update imperative navigations to include { householdSlug } via named routes.

API client

  • src/api/client.ts:
    • Add header injection (X-Household-Slug) from a configurable getter to scope requests.
    • Keep paths typing intact; no path string mutations.

SDK

  • src/api/sdk.ts:
    • No path changes; ensure all calls work with new auth and household header.

UI

  • Replace persons-based LoginPage.vue with email/password + Google; link to signup.
  • Add HouseholdSwitcher.vue to app chrome and wire with router.
  • Update components that navigate using string paths to use named routes with slug.

Tests

  • Update MSW handlers/tests to assume JWT auth and household header.
  • Add tests for router guards, invitation acceptance, and household switching.

OpenAPI & Typing Considerations

  • Avoid any/unknown in app code; keep all API calls typed via openapi-fetch.
  • If backend adds header parameter to OpenAPI, regenerate and remove any client-specific header wiring.

Migration Plan & Feature Flag

  • Optional MULTITENANT_ENABLED flag for staged rollout of routes and UI.
  • Keep legacy login until backend endpoints are ready; hide persons UI once households exist for a user.

Acceptance Criteria (Summary)

  • Users can create accounts, login (email/password, Google), logout, and refresh sessions.
  • All routes operate under /:householdSlug with correct redirects and deep link support.
  • Active household is selectable and visible; API calls are correctly scoped.
  • Invitation token acceptance adds membership and navigates to the household dashboard.
  • Legacy persons login removed from UI; tests updated and passing.

Open Questions

  • Backend: path vs header vs cookie for householdSlug? Confirm to finalize client strategy.
  • Exact OpenAPI shapes for User, Household, Invitation endpoints.
  • Google OAuth flow pattern (token exchange vs redirect).
  • JWT storage medium per security guidance (cookie vs localStorage).