From fe64f0ead48d1c65970b0ff9b9b0c8d8e293258a Mon Sep 17 00:00:00 2001 From: Peter Woolery Date: Sat, 9 May 2026 12:36:40 -0700 Subject: [PATCH] feat: login page, 401 interceptor, nav sign-out --- frontend/src/App.tsx | 14 ++++++++++ frontend/src/api/index.ts | 10 +++++++ frontend/src/pages/Login.tsx | 54 ++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 frontend/src/pages/Login.tsx 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}

} + +
+
+
+ ) +}