From 28adf880f8497fa74bc8a84efee07c24a152c075 Mon Sep 17 00:00:00 2001 From: Jaren Adams Date: Wed, 16 Apr 2025 16:12:48 -0400 Subject: [PATCH 1/8] amplify build yml --- amplify.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 amplify.yml diff --git a/amplify.yml b/amplify.yml new file mode 100644 index 00000000..51d8bf22 --- /dev/null +++ b/amplify.yml @@ -0,0 +1,18 @@ +version: 1 +applications: + - appRoot: frontend # ← path to App + frontend: + phases: + preBuild: + commands: + - npm ci + build: + commands: + - npm run build # runs "tsc -b && vite build" + artifacts: + baseDirectory: dist + files: + - '**/*' + cache: + paths: + - node_modules/**/* \ No newline at end of file From 7c174e365f804e3b24b9bf09601c053bccd32e3d Mon Sep 17 00:00:00 2001 From: Jaren Adams Date: Wed, 16 Apr 2025 16:29:22 -0400 Subject: [PATCH 2/8] amplify-yml-force --- frontend/src/Bell.tsx | 5 +++-- frontend/src/GrantSearch.tsx | 3 ++- frontend/src/Profile.tsx | 3 ++- frontend/src/Register.tsx | 5 +++-- frontend/src/api.ts | 6 ++++++ frontend/src/context/auth/authContext.tsx | 5 +++-- frontend/src/grant-info/components/GrantItem.tsx | 3 ++- .../src/grant-info/components/GrantList/processGrantData.ts | 3 ++- frontend/src/grant-info/components/NewGrantModal.tsx | 5 +++-- 9 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 frontend/src/api.ts diff --git a/frontend/src/Bell.tsx b/frontend/src/Bell.tsx index 8b40e0c4..2e7cf1ff 100644 --- a/frontend/src/Bell.tsx +++ b/frontend/src/Bell.tsx @@ -1,6 +1,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faBell } from "@fortawesome/free-solid-svg-icons"; import { useEffect, useState } from "react"; +import { api } from "./api"; // get current user id // const currUserID = sessionStorage.getItem('userId'); @@ -20,8 +21,8 @@ const BellButton = () => { // function that handles when button is clicked and fetches notifications const handleClick = async () => { - const response = await fetch( - `http://localhost:3001/notifications/user/${currUserID}`, + const response = await api( + `/notifications/user/${currUserID}`, { method: "GET", } diff --git a/frontend/src/GrantSearch.tsx b/frontend/src/GrantSearch.tsx index 7990f2c9..164c6329 100644 --- a/frontend/src/GrantSearch.tsx +++ b/frontend/src/GrantSearch.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from "react"; import Fuse from "fuse.js"; import "./styles/GrantSearch.css"; import { Grant } from "../../middle-layer/types/Grant"; +import { api } from "./api"; function GrantSearch({ onGrantSelect }: any) { const [userInput, setUserInput] = useState(""); @@ -21,7 +22,7 @@ function GrantSearch({ onGrantSelect }: any) { const fetchGrants = async () => { try { - const response = await fetch(`http://localhost:3001/grant`, { method: "GET" }); + const response = await api(`/grant`, { method: "GET" }); const data: Grant[] = await response.json(); const formattedData: Grant[] = data.map((grant: any) => ({ ...grant, diff --git a/frontend/src/Profile.tsx b/frontend/src/Profile.tsx index 500dc83f..b38001dc 100644 --- a/frontend/src/Profile.tsx +++ b/frontend/src/Profile.tsx @@ -4,6 +4,7 @@ import { useAuthContext } from "./context/auth/authContext"; import { updateUserProfile } from "./external/bcanSatchel/actions"; import { toJS } from 'mobx'; import { Link } from "react-router-dom"; +import { api } from "./api"; /** * Current logged in user's profile @@ -20,7 +21,7 @@ const Profile = observer(() => { e.preventDefault(); try { - const response = await fetch("http://localhost:3001/auth/update-profile", { + const response = await api("/auth/update-profile", { method: "POST", headers: { "Content-Type": "application/json", diff --git a/frontend/src/Register.tsx b/frontend/src/Register.tsx index 79cbc6fd..402a6ccd 100644 --- a/frontend/src/Register.tsx +++ b/frontend/src/Register.tsx @@ -3,6 +3,7 @@ import { setAuthState } from "./external/bcanSatchel/actions"; import { observer } from "mobx-react-lite"; import { useNavigate } from "react-router-dom"; import logo from "./images/bcan_logo.svg"; +import { api } from "./api"; /** * Register a new BCAN user @@ -17,7 +18,7 @@ const Register = observer(() => { e.preventDefault(); try { - const response = await fetch("http://localhost:3001/auth/register", { + const response = await api("/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password, email }), @@ -31,7 +32,7 @@ const Register = observer(() => { } // If registration succeeded, automatically log in the user - const loginResponse = await fetch("http://localhost:3001/auth/login", { + const loginResponse = await api("/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }), diff --git a/frontend/src/api.ts b/frontend/src/api.ts new file mode 100644 index 00000000..d01a9708 --- /dev/null +++ b/frontend/src/api.ts @@ -0,0 +1,6 @@ +// src/api.ts +const API = import.meta.env.VITE_API_URL; + +export function api(path: string, init?: RequestInit) { + return fetch(`${API}${path.startsWith('/') ? '' : '/'}${path}`, init); +} \ No newline at end of file diff --git a/frontend/src/context/auth/authContext.tsx b/frontend/src/context/auth/authContext.tsx index 4ef4bde4..eae474de 100644 --- a/frontend/src/context/auth/authContext.tsx +++ b/frontend/src/context/auth/authContext.tsx @@ -3,6 +3,7 @@ import { getAppStore } from '../../external/bcanSatchel/store'; import { setAuthState, logoutUser } from '../../external/bcanSatchel/actions' import { observer } from 'mobx-react-lite'; import { User } from '../../../../middle-layer/types/User' +import { api } from '@/api'; /** * Available authenticated user options @@ -33,7 +34,7 @@ export const AuthProvider = observer(({ children }: { children: ReactNode }) => * Attempt to log in the user */ const login = async (username: string, password: string) => { - const response = await fetch('http://localhost:3001/auth/login', { + const response = await api('/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), @@ -54,7 +55,7 @@ export const AuthProvider = observer(({ children }: { children: ReactNode }) => * Register a new user and automatically log them in */ const register = async (username: string, password: string, email: string) => { - const response = await fetch('http://localhost:3001/auth/register', { + const response = await fetch('/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password, email }), diff --git a/frontend/src/grant-info/components/GrantItem.tsx b/frontend/src/grant-info/components/GrantItem.tsx index c04b956c..451bcf25 100644 --- a/frontend/src/grant-info/components/GrantItem.tsx +++ b/frontend/src/grant-info/components/GrantItem.tsx @@ -8,6 +8,7 @@ import { Grant } from "../../../../middle-layer/types/Grant"; import { DoesBcanQualifyText } from "../../translations/general"; import RingButton, { ButtonColorOption } from "../../custom/RingButton"; import { Status } from "../../../../middle-layer/types/Status"; +import { api } from "../../api"; interface GrantItemProps { grant: Grant; @@ -40,7 +41,7 @@ const GrantItem: React.FC = ({ grant, defaultExpanded = false }) if (isEditing) { // Save changes when exiting edit mode. try { - const response = await fetch("http://localhost:3001/grant/save", { + const response = await api("/grant/save", { method: "PUT", headers: { "Content-Type": "application/json", diff --git a/frontend/src/grant-info/components/GrantList/processGrantData.ts b/frontend/src/grant-info/components/GrantList/processGrantData.ts index 76d5aa30..4ac6662d 100644 --- a/frontend/src/grant-info/components/GrantList/processGrantData.ts +++ b/frontend/src/grant-info/components/GrantList/processGrantData.ts @@ -4,11 +4,12 @@ import { fetchAllGrants } from "../../../external/bcanSatchel/actions"; import { Grant } from "../../../../../middle-layer/types/Grant"; import {dateRangeFilter, filterGrants, statusFilter} from "./grantFilters"; import { sortGrants } from "./grantSorter.ts"; +import { api } from "../../../api.ts"; // GET request for all grants const fetchGrants = async () => { try { - const response = await fetch("http://localhost:3001/grant"); + const response = await api("/grant"); if (!response.ok) { throw new Error(`HTTP Error, Status: ${response.status}`); } diff --git a/frontend/src/grant-info/components/NewGrantModal.tsx b/frontend/src/grant-info/components/NewGrantModal.tsx index 287041b9..30a26d20 100644 --- a/frontend/src/grant-info/components/NewGrantModal.tsx +++ b/frontend/src/grant-info/components/NewGrantModal.tsx @@ -6,6 +6,7 @@ import POCEntry from "./POCEntry"; import { Grant } from "../../../../middle-layer/types/Grant"; import { TDateISO } from "../../../../backend/src/utils/date"; import { Status } from "../../../../middle-layer/types/Status"; +import { api } from "../../api"; /** Attachment type from your middle layer */ enum AttachmentType { @@ -162,7 +163,7 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { }; console.log(newGrant); try { - const response = await fetch("http://localhost:3001/grant/new-grant", { + const response = await api("/grant/new-grant", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(newGrant), @@ -175,7 +176,7 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { } // Re-fetch the full list of grants - const grantsResponse = await fetch("http://localhost:3001/grant"); + const grantsResponse = await api("/grant"); if (!grantsResponse.ok) { throw new Error("Failed to re-fetch grants."); } From cf3dbdc01ae58eb08d9038671aa73b2e9ce36f2c Mon Sep 17 00:00:00 2001 From: Jaren Adams Date: Wed, 16 Apr 2025 16:36:18 -0400 Subject: [PATCH 3/8] import fix for api calls in auth frontend service --- frontend/src/context/auth/authContext.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/context/auth/authContext.tsx b/frontend/src/context/auth/authContext.tsx index eae474de..2a5a5184 100644 --- a/frontend/src/context/auth/authContext.tsx +++ b/frontend/src/context/auth/authContext.tsx @@ -3,7 +3,7 @@ import { getAppStore } from '../../external/bcanSatchel/store'; import { setAuthState, logoutUser } from '../../external/bcanSatchel/actions' import { observer } from 'mobx-react-lite'; import { User } from '../../../../middle-layer/types/User' -import { api } from '@/api'; +import { api } from '../../api'; /** * Available authenticated user options @@ -55,7 +55,7 @@ export const AuthProvider = observer(({ children }: { children: ReactNode }) => * Register a new user and automatically log them in */ const register = async (username: string, password: string, email: string) => { - const response = await fetch('/auth/register', { + const response = await api('/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password, email }), From 1ed882e556f11c3e2adb066c3a6abe4fe1c6cdac Mon Sep 17 00:00:00 2001 From: Jaren Adams Date: Wed, 16 Apr 2025 16:41:30 -0400 Subject: [PATCH 4/8] no changes --- frontend/src/context/auth/authContext.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/context/auth/authContext.tsx b/frontend/src/context/auth/authContext.tsx index 9d10c0ca..96b6e45d 100644 --- a/frontend/src/context/auth/authContext.tsx +++ b/frontend/src/context/auth/authContext.tsx @@ -55,7 +55,8 @@ export const AuthProvider = observer(({ children }: { children: ReactNode }) => * Register a new user and automatically log them in */ const register = async (username: string, password: string, email: string) => { - const response = await fetch('http://localhost:3001/auth/register', { + + const response = await api('/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password, email }), From 0cdfdf401c55cbb442bf3c3c586f6321c42a751a Mon Sep 17 00:00:00 2001 From: Jaren Adams Date: Wed, 16 Apr 2025 17:00:12 -0400 Subject: [PATCH 5/8] fix(api): prepend VITE_API_URL automatically --- frontend/src/api.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index d01a9708..d8b91b49 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -1,6 +1,11 @@ -// src/api.ts -const API = import.meta.env.VITE_API_URL; +const BASE = (import.meta.env.VITE_API_URL || '').replace(/\/$/, ''); -export function api(path: string, init?: RequestInit) { - return fetch(`${API}${path.startsWith('/') ? '' : '/'}${path}`, init); -} \ No newline at end of file +export async function api( + path: string, + init?: RequestInit +): Promise { + // Ensure path starts with a single slash + const cleanPath = path.startsWith('/') ? path : `/${path}`; + const url = `${BASE}${cleanPath}`; + return fetch(url, init); +} From 294f5bcebfae364b134102f852bfb3690805a404 Mon Sep 17 00:00:00 2001 From: Jaren Adams Date: Wed, 16 Apr 2025 17:08:53 -0400 Subject: [PATCH 6/8] index html change for base paths --- frontend/index.html | 1 + 1 file changed, 1 insertion(+) 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 From dada49b40ab292aefa7cbc29f3dd1fa3eb99b254 Mon Sep 17 00:00:00 2001 From: Jaren Adams Date: Tue, 29 Jul 2025 13:57:56 -0700 Subject: [PATCH 7/8] frontend cookies changes part 1 --- frontend/src/api.ts | 11 ++++++--- frontend/src/context/auth/authContext.tsx | 29 +++++++++++++++-------- 2 files changed, 27 insertions(+), 13 deletions(-) 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 ( Date: Tue, 29 Jul 2025 14:14:10 -0700 Subject: [PATCH 8/8] amplify yml update --- amplify.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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