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

+
+
+ + 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}

} + +
+
+
+ ) +} 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()