All core features are migrated to multi-tenancy with path-scoped endpoints and token-based auth. The codebase no longer uses the `X-Household-Slug` header. Tests and type checks are fully green.
All core features are migrated to multi-tenancy with path-scoped endpoints and token-based auth. The codebase no longer uses the `X-Household-Slug` header. Tests and type checks are fully green.
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`**.
- 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".
- **History behavior**: Uses hash history in real browsers and memory history in tests/SSR (detected via `globalThis.location`). Router tests assert slug-only mode.
- Status: Welcome page implements create-household flow using `POST /api/v1/households` and redirects to `/:slug/mealplan`. Create Account UI implemented. Invitation Accept implemented: reads `token` from query, calls typed `POST /api/v1/invitations/accept`, and redirects to the accepted household.
- **Update API Services**: No household header injection; Authorization header provided by token provider. Cookies are only sent for the refresh endpoint; all other requests avoid credentials. SDK/services use typed path params.
- Verified by `tests/household.header.test.ts`, auth tests, and router guard tests.
- Status: Invite form implemented (sends email via POST `/api/v1/invitations`). Members listing implemented using typed endpoint `GET /api/v1/households/{householdSlug}/members`.
All core features are migrated to multi-tenancy with path-scoped endpoints and token-based auth. The codebase no longer uses the `X-Household-Slug` header. Tests and type checks are fully green.
-`src/api/sdk.ts`: recipes, meals, and shopping are migrated to `/api/v1/households/{householdSlug}/...` typed endpoints. Person endpoints removed; parse uses a temporary raw fetch endpoint until typed coverage is available.
## Amendment: API Services and Typing Strategy (Updated Nov 1, 2025)
The backend OpenAPI has been updated and now exposes `householdSlug` as a path parameter for scoped endpoints (recipes, meals, shopping, invitations, whoami). Auth endpoints are fully typed (login/register/refresh/logout). Invitation acceptance remains a global endpoint (`/api/v1/invitations/accept`) with a typed operation.
- Implemented `useHousehold.ts`, Authorization provider in API client, minimal `HouseholdSwitcher.vue`, and mounted it. Replaced header injection test with path-scoped assertion.
- Implemented JWT login/register in `auth.ts` and wired token to client provider. `useAuth` updated with households fetching. `currentUser` refactored to token-only refresh plus households load.
- Completed migration to typed path parameters; header injection removed; only small raw fetch helpers remain for endpoints not yet in OpenAPI (parse only).
-`src/views/HouseholdSettings.vue`: Invite members form wired to `sendInvitation(email)`. Members list rendered from `listMembers()` using the typed endpoint. (Completed)
- Household scoping: use typed path params (`{ params: { path: { householdSlug } } }`); do not mutate path strings.
- Auth refresh: returns `{ accessToken, tokenType }`. After refresh, call user/household endpoints to populate app state. Use `whoami` to validate the active route’s slug when needed.
2) Replace temporary raw fetch calls with generated typed endpoints where available: invitations and members listing are now typed; migrate usages. Persons and parse remain raw for now.
3) Integrate the new `POST /shopping/current/ingredients` endpoint into the SDK (`requestIngredient(ingredientId: number)`) and expose via `useShopping`; refactor `MyShoppingPage.vue` accordingly and remove legacy stubs. (SDK + composable done; UI refactor next)
- The v2 OpenAPI spec is being updated to include endpoints for creating, listing, updating, and deleting ad-hoc requested items on the current shopping list (independent of meals), scoped under `/api/v1/households/{householdSlug}/shopping/current/items`.
-`listRequestedItems()` (if provided separately; otherwise rely on `getCurrentShoppingList()`)
- Refactor `src/components/shopping/MyShoppingPage.vue` to use the above methods and remove legacy stubs `getMyShoppingList/saveMyShoppingList` from the SDK and `useShopping` composable.
- Update tests to drive TDD:
- MSW handlers for the new endpoints with path-scoped URLs and Authorization.
- Component tests to add, edit, and delete an ad-hoc requested item and verify it appears under outstanding items in `getCurrentShoppingList()`.
- Acceptance criteria:
- Users can add an item without associating it to a meal.
- Users can edit and delete such items.
- All calls use typed path-scoped endpoints; no header injection; Authorization still via provider.
- Note: No temporary measures required; proceed directly once backend ships endpoints.
- Current status: Legacy stubs removed from composable; `MyShoppingPage.vue` simplified around `requestIngredient`; shape test added and passing. Full ad-hoc CRUD UI awaits backend endpoints.
- Remove the legacy username login shim (`login(username: string)`) and any fallback UI; standardize on email/password (and Google) only.
- Rollout flag: default `VUE_APP_MULTITENANT_ENABLED` to true across environments and plan removal of legacy flat routes and related tests once stable.