diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index dd837dd..2e756b8 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -4,6 +4,8 @@ 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()
@@ -26,6 +28,17 @@ function App() {
Shopping List
+
+
+
@@ -35,6 +48,7 @@ function App() {
} />
} />
} />
+ } />
diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts
index 2735924..78a30f2 100644
--- a/frontend/src/api/index.ts
+++ b/frontend/src/api/index.ts
@@ -10,6 +10,16 @@ const api = axios.create({
withCredentials: true,
})
+api.interceptors.response.use(
+ response => response,
+ error => {
+ if (error.response?.status === 401 && window.location.pathname !== '/login') {
+ window.location.href = '/login'
+ }
+ return Promise.reject(error)
+ }
+)
+
export const mealPlannerApi = {
auth: {
login: (password: string) => api.post('/auth/login', { password }),
diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx
new file mode 100644
index 0000000..bce4d66
--- /dev/null
+++ b/frontend/src/pages/Login.tsx
@@ -0,0 +1,54 @@
+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
+
+
+
+ )
+}
diff --git a/scripts/seed_family.py b/scripts/seed_family.py
new file mode 100644
index 0000000..f7249ce
--- /dev/null
+++ b/scripts/seed_family.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+"""
+One-time seed: creates the family profile + members.
+
+Edit the MEMBERS list below to set real names and emails, then 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
+
+Safe to re-run — exits early if a profile already exists.
+"""
+import os
+import sys
+
+# ── Edit before running ───────────────────────────────────────────────────────
+FAMILY_NAME = "Woolery"
+FAMILY_CALORIE_TARGET = 500 # per serving per meal
+HOUSEHOLD_SIZE = 4
+ADULT_COUNT = 2
+CHILD_COUNT = 2
+
+MEMBERS = [
+ {"name": "Peter", "email": "peter@research.bike", "role": "adult", "likes_mushrooms": True},
+ {"name": "Wife", "email": "wife@example.com", "role": "adult", "likes_mushrooms": False},
+ {"name": "Kid 1", "email": "", "role": "child", "likes_mushrooms": False},
+ {"name": "Kid 2", "email": "", "role": "child", "likes_mushrooms": False},
+]
+# ─────────────────────────────────────────────────────────────────────────────
+
+sys.path.insert(0, "/app")
+
+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(
+ name=FAMILY_NAME,
+ household_size=HOUSEHOLD_SIZE,
+ adult_count=ADULT_COUNT,
+ child_count=CHILD_COUNT,
+ calorie_target=FAMILY_CALORIE_TARGET,
+ )
+ db.add(profile)
+ db.flush()
+ print(f"Created family profile '{FAMILY_NAME}' id={profile.id}")
+
+ for m in MEMBERS:
+ if not m["email"]:
+ print(f" Skipping {m['name']} — no email set (edit MEMBERS to add one)")
+ 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: {m['name']} <{m['email']}> role={m['role']}")
+
+ db.commit()
+ print("Done.")
+finally:
+ db.close()