# 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 (
    <div className="min-h-screen bg-gray-50 flex items-center justify-center">
      <div className="bg-white rounded-lg shadow p-8 w-full max-w-sm">
        <h1 className="text-2xl font-bold text-gray-900 mb-2 text-center">MealPlanner</h1>
        <p className="text-sm text-gray-500 text-center mb-6">Family login</p>
        <form onSubmit={handleSubmit} className="space-y-4">
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-1">
              Password
            </label>
            <input
              type="password"
              value={password}
              onChange={e => 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
            />
          </div>
          {error && <p className="text-red-600 text-sm">{error}</p>}
          <button
            type="submit"
            disabled={loading}
            className="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 disabled:opacity-50 font-medium"
          >
            {loading ? 'Signing in…' : 'Sign in'}
          </button>
        </form>
      </div>
    </div>
  )
}
```

### 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
<div className="flex items-center">
  <button
    onClick={async () => {
      await mealPlannerApi.auth.logout().catch(() => {})
      window.location.href = '/login'
    }}
    className="text-sm text-gray-500 hover:text-gray-900"
  >
    Sign out
  </button>
</div>
```

Add the Login route inside `<Routes>`:

```tsx
<Route path="/login" element={<Login />} />
```

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 (
    <QueryClientProvider client={queryClient}>
      <BrowserRouter>
        <div className="min-h-screen bg-gray-50">
          <nav className="bg-white shadow-sm">
            <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
              <div className="flex justify-between h-16">
                <div className="flex space-x-8">
                  <Link to="/" className="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-900">
                    MealPlanner
                  </Link>
                  <Link to="/pantry" className="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-500 hover:text-gray-900">
                    Pantry
                  </Link>
                  <Link to="/shopping-list" className="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-500 hover:text-gray-900">
                    Shopping List
                  </Link>
                </div>
                <div className="flex items-center">
                  <button
                    onClick={async () => {
                      await mealPlannerApi.auth.logout().catch(() => {})
                      window.location.href = '/login'
                    }}
                    className="text-sm text-gray-500 hover:text-gray-900"
                  >
                    Sign out
                  </button>
                </div>
              </div>
            </div>
          </nav>
          <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
            <Routes>
              <Route path="/" element={<Dashboard />} />
              <Route path="/meals/:id" element={<MealDetail />} />
              <Route path="/pantry" element={<Pantry />} />
              <Route path="/shopping-list" element={<ShoppingList />} />
              <Route path="/login" element={<Login />} />
            </Routes>
          </main>
        </div>
      </BrowserRouter>
    </QueryClientProvider>
  )
}

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."
