# MVP Login + Family Seed Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. **Goal:** Make the UI actually usable — login page, auth gating, family profile seed, and vote-link base URL fix. **Architecture:** Three changes: (1) React Login page + 401 interceptor in the frontend so unauthenticated users are redirected; (2) Python seed script to insert the first `family_profile` + `family_member` rows (no API endpoint exists to create them); (3) `APP_BASE_URL` added to `.env.test` so vote links in emails point to the right host. **Tech Stack:** React 18 + TypeScript + Vite + Tailwind, axios, FastAPI, SQLAlchemy 2.0, Postgres 15. --- ## File map | File | Action | |---|---| | `frontend/src/pages/Login.tsx` | **Create** — password form, calls `auth.login()`, redirects to `/` | | `frontend/src/App.tsx` | **Modify** — add `/login` route, logout button in nav | | `frontend/src/api/index.ts` | **Modify** — add 401 response interceptor → redirect to `/login` | | `scripts/seed_family.py` | **Create** — inserts `family_profile` + `family_member` rows via SQLAlchemy | --- ## Task 1: Login page + 401 interceptor + nav logout **Worktree:** `/home/peter/Projects/MealPlanner/.worktrees/feature-mvp-login` **Files:** - Create: `frontend/src/pages/Login.tsx` - Modify: `frontend/src/App.tsx` - Modify: `frontend/src/api/index.ts` ### Step 1: Create `frontend/src/pages/Login.tsx` ```tsx import { useState } from 'react' import { mealPlannerApi } from '../api' export default function Login() { const [password, setPassword] = useState('') const [error, setError] = useState('') const [loading, setLoading] = useState(false) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setError('') setLoading(true) try { await mealPlannerApi.auth.login(password) window.location.href = '/' } catch { setError('Incorrect password. Try again.') } finally { setLoading(false) } } return (

MealPlanner

Family login

setPassword(e.target.value)} className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" autoFocus required />
{error &&

{error}

}
) } ``` ### Step 2: Modify `frontend/src/App.tsx` Add `import Login from './pages/Login'` to the imports block. Add a logout button to the nav (right side of the flex): ```tsx
``` Add the Login route inside ``: ```tsx } /> ``` The full updated `App.tsx`: ```tsx import { BrowserRouter, Routes, Route, Link } from 'react-router-dom' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import Dashboard from './pages/Dashboard' import MealDetail from './pages/MealDetail' import Pantry from './pages/Pantry' import ShoppingList from './pages/ShoppingList' import Login from './pages/Login' import { mealPlannerApi } from './api' const queryClient = new QueryClient() function App() { return (
} /> } /> } /> } /> } />
) } export default App ``` ### Step 3: Add 401 interceptor to `frontend/src/api/index.ts` After the `api` axios instance is created (after the `const api = axios.create(...)` block), add: ```typescript api.interceptors.response.use( response => response, error => { if (error.response?.status === 401 && window.location.pathname !== '/login') { window.location.href = '/login' } return Promise.reject(error) } ) ``` ### Step 4: Build and verify ```bash cd /home/peter/Projects/MealPlanner/.worktrees/feature-mvp-login npm --prefix frontend ci npm --prefix frontend run build # Expected: no TypeScript errors, build succeeds ``` ### Step 5: Commit ```bash cd /home/peter/Projects/MealPlanner/.worktrees/feature-mvp-login git add frontend/src/pages/Login.tsx \ frontend/src/App.tsx \ frontend/src/api/index.ts git commit -m "feat: login page, 401 interceptor, nav sign-out" ``` --- ## Task 2: Family profile seed script **File:** Create `scripts/seed_family.py` This script inserts a `family_profile` row and `family_member` rows directly via SQLAlchemy. Run once after the containers are up. ```python #!/usr/bin/env python3 """ One-time seed: creates the Woolery family profile + members. Run inside the backend container: docker compose --env-file .env.test exec backend python /app/seed_family.py Or copy and run: docker cp scripts/seed_family.py mealplanner-backend-1:/app/seed_family.py docker compose --env-file .env.test exec backend python /app/seed_family.py """ import os import sys # ── Edit these before running ──────────────────────────────────────────────── FAMILY_CALORIE_TARGET = 500 # per serving, per meal MEMBERS = [ {"name": "Peter", "email": "peter@research.bike", "role": "admin", "likes_mushrooms": True}, {"name": "Wife", "email": "", "role": "member", "likes_mushrooms": False}, {"name": "Kid 1", "email": "", "role": "member", "likes_mushrooms": False}, {"name": "Kid 2", "email": "", "role": "member", "likes_mushrooms": False}, ] # ───────────────────────────────────────────────────────────────────────────── sys.path.insert(0, "/app") os.environ.setdefault("DATABASE_URL", os.environ["DATABASE_URL"]) from app.database import SessionLocal from app.models import FamilyProfile, FamilyMember, FamilyMemberRole db = SessionLocal() try: existing = db.query(FamilyProfile).first() if existing: print(f"Family profile already exists (id={existing.id}). Nothing to do.") sys.exit(0) profile = FamilyProfile(calorie_target=FAMILY_CALORIE_TARGET) db.add(profile) db.flush() for m in MEMBERS: if not m["email"]: print(f" Skipping {m['name']} — no email set") continue member = FamilyMember( family_profile_id=profile.id, name=m["name"], email=m["email"], role=FamilyMemberRole[m["role"].upper()], likes_mushrooms=m["likes_mushrooms"], ) db.add(member) print(f" Added member: {m['name']} <{m['email']}>") db.commit() print(f"Done. Family profile id={profile.id}") finally: db.close() ``` ### Step: Commit ```bash cd /home/peter/Projects/MealPlanner/.worktrees/feature-mvp-login git add scripts/seed_family.py git commit -m "feat: one-time family profile seed script" ``` --- ## Post-merge steps (run by operator after merge) ### 1. Add APP_BASE_URL to `.env.test` Edit `.env.test` on the host and add: ``` APP_BASE_URL=http://100.108.224.12:8081 ``` This makes vote links in Friday emails point to the right host. ### 2. Rebuild frontend container and restart ```bash docker compose --env-file .env.test build frontend docker compose --env-file .env.test up -d ``` ### 3. Run the seed script ```bash # Edit scripts/seed_family.py to fill in real names/emails first, then: docker cp scripts/seed_family.py mealplanner-backend-1:/app/seed_family.py docker compose --env-file .env.test exec backend python /app/seed_family.py ``` ### 4. Verify login works Open `http://100.108.224.12:8081` — should redirect to `/login`. Enter `SESSION_PASSWORD`. Should land on Dashboard showing "No meal plan generated yet."