diff --git a/amplify.yml b/amplify.yml index 51d8bf22..6a03427b 100644 --- a/amplify.yml +++ b/amplify.yml @@ -15,4 +15,9 @@ applications: - '**/*' cache: paths: - - node_modules/**/* \ No newline at end of file + - node_modules/**/* + redirects: # ← add this whole block + - source: "/<*>" # match any client‑side route + target: "/index.html" # serve SPA shell + status: "200" # rewrite, not redirect + condition: null \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 1bd99ac0..5e3922b5 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,6 +2,7 @@ + Vite + React + TS diff --git a/frontend/src/api.ts b/frontend/src/api.ts index d8b91b49..06878c34 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -1,11 +1,16 @@ +// API INDEX + const BASE = (import.meta.env.VITE_API_URL || '').replace(/\/$/, ''); export async function api( path: string, - init?: RequestInit + init: RequestInit = {} ): Promise { - // Ensure path starts with a single slash const cleanPath = path.startsWith('/') ? path : `/${path}`; const url = `${BASE}${cleanPath}`; - return fetch(url, init); + + return fetch(url, { + credentials: 'include', // ← send & receive the jwt cookie + ...init, + }); } diff --git a/frontend/src/context/auth/authContext.tsx b/frontend/src/context/auth/authContext.tsx index 96b6e45d..19ced61b 100644 --- a/frontend/src/context/auth/authContext.tsx +++ b/frontend/src/context/auth/authContext.tsx @@ -1,4 +1,4 @@ -import { useContext, createContext, ReactNode } from 'react'; +import { useContext, createContext, ReactNode, useEffect } from 'react'; import { getAppStore } from '../../external/bcanSatchel/store'; import { setAuthState, logoutUser } from '../../external/bcanSatchel/actions' import { observer } from 'mobx-react-lite'; @@ -42,13 +42,12 @@ export const AuthProvider = observer(({ children }: { children: ReactNode }) => const data = await response.json(); - // TODO: Need to either completely remove access_token - // or verify it in each action - if (data.access_token) { - setAuthState(true, data.user, data.access_token); - } else { + if (data.user) { + // cookie was set by /auth/login + setAuthState(true, data.user, null); + } else { alert('Login failed. Please check your credentials.'); - } + } }; /** @@ -74,9 +73,19 @@ export const AuthProvider = observer(({ children }: { children: ReactNode }) => /** * Log out the user */ - const logout = () => { - logoutUser(); // Satchel action that clears state - }; + const logout = () => { + api('/auth/logout', { method: 'POST' }); + logoutUser(); + }; + + // Session Level 1.1 + // Restore on page-load / hard-refresh + useEffect(() => { + api('/auth/session') + .then(r => (r.ok ? r.json() : Promise.reject())) + .then(({ user }) => setAuthState(true, user, null)) + .catch(() => logoutUser()); + }, []); return (