fixed server2 database and public file access

This commit is contained in:
Josh 2025-07-17 19:26:54 +00:00
parent a59ecd2b7a
commit c2cc63e2f7
22 changed files with 135 additions and 145 deletions

1
.env
View File

@ -3,3 +3,4 @@ SERVER1_PORT=5000
SERVER2_PORT=5001 SERVER2_PORT=5001
SERVER3_PORT=5002 SERVER3_PORT=5002
SALARY_DB=/salary_info.db SALARY_DB=/salary_info.db
NODE_ENV=production

View File

@ -4,8 +4,8 @@ ONET_PASSWORD=2296ahq
# ─── Publicfacing React build ─────────── # ─── Publicfacing React build ───────────
NODE_ENV=development NODE_ENV=development
REACT_APP_ENV=production REACT_APP_ENV=development
APTIVA_API_BASE=https://dev1.aptivaai.com/api APTIVA_API_BASE=https://dev1.aptivaai.com
REACT_APP_API_URL=${APTIVA_API_BASE} REACT_APP_API_URL=${APTIVA_API_BASE}
REACT_APP_GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20 REACT_APP_GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
REACT_APP_BLS_API_KEY=80d7a65a809a43f3a306a41ec874d231 REACT_APP_BLS_API_KEY=80d7a65a809a43f3a306a41ec874d231
@ -15,7 +15,7 @@ REACT_APP_OPENAI_API_KEY=sk-proj-IyBOKc2T9RyViN_WBZwnjNCwUiRDBekmrghpHTKyf6OsqWx
OPENAI_API_KEY=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 OPENAI_API_KEY=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
GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20 GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
COLLEGE_SCORECARD_KEY=BlZ0tIdmXVGI4G8NxJ9e6dXEiGUfAfnQJyw8bumj COLLEGE_SCORECARD_KEY=BlZ0tIdmXVGI4G8NxJ9e6dXEiGUfAfnQJyw8bumj
SALARY_DB=/home/jcoakley/aptiva-dev1-app/salary_info.db SALARY_DB=/salary_info.db
# ─── Database (premium server) ─────────── # ─── Database (premium server) ───────────
DB_HOST=34.67.180.54 DB_HOST=34.67.180.54
@ -31,10 +31,10 @@ TWILIO_AUTH_TOKEN=fb8979ccb172032a249014c9c30eba80
TWILIO_MESSAGING_SERVICE_SID=MGMGaa07992a9231c841b1bfb879649026d6 TWILIO_MESSAGING_SERVICE_SID=MGMGaa07992a9231c841b1bfb879649026d6
# ─── Anything new goes here ────────────── # ─── Anything new goes here ──────────────
JWT_SECRET=a35F0iFAkkdWvSjnaLzepAl/JIxPRUh4NpcGptJgry2Z3KVLX4ZcYY5KaTf7kJY0 JWT_SECRET=gW4QsOu4AJA4MooIUC9ld2i71VbBovzV1INsaU6ftxYPrxLIeMq6/OY61j0X2RV7
# ------------ CORS ------------ # ------------ CORS ------------
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://34.16.120.118:3000,https://dev1.aptivaai.com CORS_ALLOWED_ORIGINS=https://dev1.aptivaai.com,http://34.16.120.118:3000,http://localhost:3000
SERVER1_PORT=5000 SERVER1_PORT=5000
SERVER2_PORT=5001 SERVER2_PORT=5001
SERVER3_PORT=5002 SERVER3_PORT=5002

View File

@ -3,9 +3,9 @@ ONET_USERNAME=aptivaai
ONET_PASSWORD=2296ahq ONET_PASSWORD=2296ahq
# ─── Publicfacing React build ─────────── # ─── Publicfacing React build ───────────
NODE_ENV=staging NODE_ENV=production
REACT_APP_ENV=production REACT_APP_ENV=staging
APTIVA_API_BASE=https://staging.aptivaai.com/api APTIVA_API_BASE=https://staging.aptivaai.com
REACT_APP_API_URL=${APTIVA_API_BASE} REACT_APP_API_URL=${APTIVA_API_BASE}
REACT_APP_GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20 REACT_APP_GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
REACT_APP_BLS_API_KEY=80d7a65a809a43f3a306a41ec874d231 REACT_APP_BLS_API_KEY=80d7a65a809a43f3a306a41ec874d231
@ -15,7 +15,7 @@ REACT_APP_OPENAI_API_KEY=sk-proj-IyBOKc2T9RyViN_WBZwnjNCwUiRDBekmrghpHTKyf6OsqWx
OPENAI_API_KEY=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 OPENAI_API_KEY=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
GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20 GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
COLLEGE_SCORECARD_KEY=BlZ0tIdmXVGI4G8NxJ9e6dXEiGUfAfnQJyw8bumj COLLEGE_SCORECARD_KEY=BlZ0tIdmXVGI4G8NxJ9e6dXEiGUfAfnQJyw8bumj
SALARY_DB=/home/jcoakley/aptiva-dev1-app/salary_info.db SALARY_DB=/salary_info.db
# ─── Database (premium server) ─────────── # ─── Database (premium server) ───────────
DB_HOST=34.67.180.54 DB_HOST=34.67.180.54

View File

@ -1,5 +1,5 @@
ARG APPPORT=5000 ARG APPPORT=5000
FROM --platform=$TARGETPLATFORM node:20-slim FROM node:20-slim
WORKDIR /app WORKDIR /app
COPY package*.json ./ COPY package*.json ./
RUN apt-get update -y && apt-get install -y --no-install-recommends build-essential python3 make g++ && rm -rf /var/lib/apt/lists/* RUN apt-get update -y && apt-get install -y --no-install-recommends build-essential python3 make g++ && rm -rf /var/lib/apt/lists/*

View File

@ -1,10 +1,16 @@
ARG APPPORT=5001 FROM node:20-bullseye AS base
FROM --platform=$TARGETPLATFORM node:20-slim
WORKDIR /app WORKDIR /app
# ---- native build deps ----
RUN apt-get update -y && \
apt-get install -y --no-install-recommends \
build-essential python3 pkg-config && \
rm -rf /var/lib/apt/lists/*
# ---------------------------
COPY package*.json ./ COPY package*.json ./
RUN npm ci --omit=dev --ignore-scripts RUN npm ci --unsafe-perm
RUN apt-get update && apt-get install -y build-essential python3 make g++ sqlite3 && rm -rf /var/lib/apt/lists/* COPY public/ /app/public/
COPY . . COPY . .
ENV PORT=5001
EXPOSE 5001
CMD ["node", "backend/server2.js"] CMD ["node", "backend/server2.js"]

View File

@ -1,5 +1,5 @@
ARG APPPORT=5002 ARG APPPORT=5002
FROM --platform=$TARGETPLATFORM node:20-slim FROM node:20-slim
WORKDIR /app WORKDIR /app
COPY package*.json ./ COPY package*.json ./
RUN apt-get update -y \ RUN apt-get update -y \

View File

@ -30,6 +30,21 @@ const env = process.env.NODE_ENV?.trim() || 'development';
const envPath = path.resolve(rootPath, `.env.${env}`); const envPath = path.resolve(rootPath, `.env.${env}`);
dotenv.config({ path: envPath }); // Load .env dotenv.config({ path: envPath }); // Load .env
const ROOT_DIR = path.resolve(__dirname, '..'); // repo root
const PUBLIC_DIR = path.join(ROOT_DIR, 'public'); // static json files
const CIP_TO_SOC_PATH = path.join(PUBLIC_DIR, 'CIP_to_ONET_SOC.xlsx');
const INSTITUTION_DATA_PATH = path.join(PUBLIC_DIR, 'Institution_data.json');
const SALARY_DB_PATH = path.join(ROOT_DIR, 'salary_info.db');
const USER_PROFILE_DB_PATH = path.join(ROOT_DIR, 'user_profile.db');
for (const p of [CIP_TO_SOC_PATH, INSTITUTION_DATA_PATH,
SALARY_DB_PATH, USER_PROFILE_DB_PATH]) {
if (!fs.existsSync(p)) {
console.error(`FATAL Required data file not found → ${p}`);
process.exit(1);
}
}
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const chatLimiter = rateLimit({ const chatLimiter = rateLimit({
@ -38,16 +53,8 @@ const chatLimiter = rateLimit({
keyGenerator: req => req.user?.id || req.ip keyGenerator: req => req.user?.id || req.ip
}); });
if (!process.env.APTIVA_API_BASE) {
console.error('FATAL APTIVA_API_BASE is not set');
process.exit(1);
}
// CIP->SOC mapping file
const mappingFilePath = '/home/jcoakley/aptiva-dev1-app/public/CIP_to_ONET_SOC.xlsx';
// Institution data // Institution data
const institutionFilePath = 'home/jcoakley/aptiva-dev1-app/public/Institution_data.json'; const institutionData = JSON.parse(fs.readFileSync(INSTITUTION_DATA_PATH, 'utf8'));
// Create Express app // Create Express app
const app = express(); const app = express();
@ -66,13 +73,14 @@ let userProfileDb;
async function initDatabases() { async function initDatabases() {
try { try {
db = await open({ db = await open({
filename: '/home/jcoakley/aptiva-dev1-app/salary_info.db', filename: SALARY_DB_PATH,
driver : sqlite3.Database driver : sqlite3.Database,
mode : sqlite3.OPEN_READONLY
}); });
console.log('✅ Connected to salary_info.db'); console.log('✅ Connected to salary_info.db');
userProfileDb = await open({ userProfileDb = await open({
filename: '/home/jcoakley/aptiva-dev1-app/user_profile.db', filename: USER_PROFILE_DB_PATH,
driver : sqlite3.Database driver : sqlite3.Database
}); });
console.log('✅ Connected to user_profile.db'); console.log('✅ Connected to user_profile.db');
@ -164,14 +172,9 @@ app.use((req, res, next) => next());
* Load CIP->SOC mapping * Load CIP->SOC mapping
**************************************************/ **************************************************/
function loadMapping() { function loadMapping() {
try { const wb = xlsx.readFile(CIP_TO_SOC_PATH);
const workbook = xlsx.readFile(mappingFilePath); const sheet= wb.Sheets[wb.SheetNames[0]];
const sheet = workbook.Sheets[workbook.SheetNames[0]]; return xlsx.utils.sheet_to_json(sheet); // socToCipMapping array
return xlsx.utils.sheet_to_json(sheet);
} catch (error) {
console.error('Error reading CIP_to_ONET_SOC:', error);
return [];
}
} }
const socToCipMapping = loadMapping(); const socToCipMapping = loadMapping();
if (socToCipMapping.length === 0) { if (socToCipMapping.length === 0) {
@ -404,7 +407,7 @@ app.post('/api/onet/submit_answers', async (req, res) => {
const token = req.headers.authorization?.split(' ')[1]; const token = req.headers.authorization?.split(' ')[1];
if (token) { if (token) {
try { try {
await axios.post(`${process.env.APTIVA_API_BASE}/api/user-profile`, await axios.post('/api/user-profile',
{ {
interest_inventory_answers: answers, interest_inventory_answers: answers,
riasec: riasecCode riasec: riasecCode
@ -579,8 +582,7 @@ app.get('/api/schools', (req, res) => {
// 3) Load your raw schools data // 3) Load your raw schools data
let schoolsData = []; let schoolsData = [];
try { try {
const rawData = fs.readFileSync(institutionFilePath, 'utf8'); schoolsData = institutionData;
schoolsData = JSON.parse(rawData);
} catch (err) { } catch (err) {
console.error('Error parsing institution data:', err.message); console.error('Error parsing institution data:', err.message);
return res.status(500).json({ error: 'Failed to load schools data.' }); return res.status(500).json({ error: 'Failed to load schools data.' });
@ -633,8 +635,7 @@ app.get('/api/tuition', (req, res) => {
} }
try { try {
const raw = fs.readFileSync(institutionFilePath, 'utf8'); schoolsData = institutionData;
const schoolsData = JSON.parse(raw);
const cipArray = cipCodes const cipArray = cipCodes
.split(',') .split(',')

View File

@ -1,5 +0,0 @@
services:
server1: { env_file: ./env/dev.env }
server2: { env_file: ./env/dev.env }
server3: { env_file: ./env/dev.env }
nginx : { env_file: ./env/dev.env }

View File

@ -1,5 +0,0 @@
services:
server1: { env_file: ./env/prod.env }
server2: { env_file: ./env/prod.env }
server3: { env_file: ./env/prod.env }
nginx : { env_file: ./env/prod.env }

View File

@ -1,5 +0,0 @@
services:
server1: { env_file: ./env/staging.env }
server2: { env_file: ./env/staging.env }
server3: { env_file: ./env/staging.env }
nginx : { env_file: ./env/staging.env }

View File

@ -1,8 +1,13 @@
x-env: &with-env
env_file:
- ${RUNTIME_ENV_FILE:-.env.production} # default for local runs
restart: unless-stopped
services: services:
server1: server1:
<<: *with-env
image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server1:${IMG_TAG} image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server1:${IMG_TAG}
expose: ["${SERVER1_PORT}"] expose: ["${SERVER1_PORT}"]
restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER1_PORT}/healthz || exit 1"] test: ["CMD-SHELL", "curl -f http://localhost:${SERVER1_PORT}/healthz || exit 1"]
interval: 30s interval: 30s
@ -13,6 +18,12 @@ services:
image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server2:${IMG_TAG} image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server2:${IMG_TAG}
expose: ["${SERVER2_PORT}"] expose: ["${SERVER2_PORT}"]
restart: unless-stopped restart: unless-stopped
env_file:
- ${RUNTIME_ENV_FILE}
volumes:
- ./public:/app/public:ro
- ./salary_info.db:/app/salary_info.db:ro
- ./user_profile.db:/app/user_profile.db
healthcheck: healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER2_PORT}/healthz || exit 1"] test: ["CMD-SHELL", "curl -f http://localhost:${SERVER2_PORT}/healthz || exit 1"]
interval: 30s interval: 30s
@ -20,9 +31,9 @@ services:
retries: 3 retries: 3
server3: server3:
<<: *with-env
image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server3:${IMG_TAG} image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server3:${IMG_TAG}
expose: ["${SERVER3_PORT}"] expose: ["${SERVER3_PORT}"]
restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER3_PORT}/healthz || exit 1"] test: ["CMD-SHELL", "curl -f http://localhost:${SERVER3_PORT}/healthz || exit 1"]
interval: 30s interval: 30s
@ -30,8 +41,10 @@ services:
retries: 3 retries: 3
nginx: nginx:
<<: *with-env
image: nginx:1.25-alpine image: nginx:1.25-alpine
command: ["nginx", "-g", "daemon off;"] command: ["nginx", "-g", "daemon off;"]
depends_on: [server1, server2, server3]
ports: ports:
- "80:80" - "80:80"
- "443:443" - "443:443"
@ -40,4 +53,3 @@ services:
- ./nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx.conf:/etc/nginx/nginx.conf:ro
- /etc/letsencrypt:/etc/letsencrypt:ro - /etc/letsencrypt:/etc/letsencrypt:ro
- ./empty:/etc/nginx/conf.d - ./empty:/etc/nginx/conf.d
depends_on: [server1, server2, server3]

View File

@ -38,7 +38,6 @@ import ChatCtx from './contexts/ChatCtx.js';
export const ProfileCtx = React.createContext(); export const ProfileCtx = React.createContext();
const apiUrl = process.env.REACT_APP_API_URL || '';
function App() { function App() {
@ -131,7 +130,7 @@ const uiToolHandlers = useMemo(() => {
} }
// If we have a token, validate it by fetching user // If we have a token, validate it by fetching user
fetch(`${apiUrl}/user-profile`, { fetch('/api/user-profile', {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}) })
.then((res) => { .then((res) => {

View File

@ -58,7 +58,6 @@ function getFullStateName(code) {
function CareerExplorer() { function CareerExplorer() {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const apiUrl = process.env.REACT_APP_API_URL || '';
// ---------- Component States ---------- // ---------- Component States ----------
const [userProfile, setUserProfile] = useState(null); const [userProfile, setUserProfile] = useState(null);
@ -170,7 +169,7 @@ function CareerExplorer() {
setProgress(0); setProgress(0);
// 1) O*NET answers -> initial career list // 1) O*NET answers -> initial career list
const submitRes = await axios.post(`${apiUrl}/onet/submit_answers`, { const submitRes = await axios.post('/api/onet/submit_answers', {
answers, answers,
state: profileData.state, state: profileData.state,
area: profileData.area, area: profileData.area,
@ -204,7 +203,7 @@ function CareerExplorer() {
// 2) job zones (one call for all SOC codes) // 2) job zones (one call for all SOC codes)
const socCodes = flattened.map((c) => c.code); const socCodes = flattened.map((c) => c.code);
const zonesRes = await axios.post(`${apiUrl}/job-zones`, { socCodes }).catch(() => null); const zonesRes = await axios.post('/api/job-zones', { socCodes }).catch(() => null);
// increment progress for this single request // increment progress for this single request
increment(); increment();
@ -215,9 +214,9 @@ function CareerExplorer() {
const strippedSoc = career.code.split('.')[0]; const strippedSoc = career.code.split('.')[0];
// build URLs // build URLs
const cipUrl = `${apiUrl}/cip/${career.code}`; const cipUrl = `/api/cip/${career.code}`;
const jobDetailsUrl = `${apiUrl}/onet/career-description/${career.code}`; const jobDetailsUrl = `/api/onet/career-description/${career.code}`;
const economicUrl = `${apiUrl}/projections/${strippedSoc}`; const economicUrl = `/api/projections/${strippedSoc}`;
const salaryParams = { socCode: strippedSoc, area: profileData.area }; const salaryParams = { socCode: strippedSoc, area: profileData.area };
// We'll fetch them in parallel with our custom fetchWithProgress: // We'll fetch them in parallel with our custom fetchWithProgress:
@ -225,7 +224,7 @@ function CareerExplorer() {
fetchWithProgress(cipUrl), fetchWithProgress(cipUrl),
fetchWithProgress(jobDetailsUrl), fetchWithProgress(jobDetailsUrl),
fetchWithProgress(economicUrl), fetchWithProgress(economicUrl),
fetchWithProgress(`${apiUrl}/salary`, salaryParams), fetchWithProgress('/api/salary', salaryParams),
]); ]);
// parse data // parse data
@ -292,7 +291,7 @@ function CareerExplorer() {
const fetchUserProfile = async () => { const fetchUserProfile = async () => {
try { try {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
const res = await axios.get(`${apiUrl}/user-profile`, { const res = await axios.get('/api/user-profile', {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
@ -323,7 +322,7 @@ function CareerExplorer() {
}; };
fetchUserProfile(); fetchUserProfile();
}, [apiUrl]); }, []);
// ------------------------------------------------------ // ------------------------------------------------------
// If user came from Interest Inventory => auto-fetch // If user came from Interest Inventory => auto-fetch
@ -363,7 +362,7 @@ function CareerExplorer() {
try { try {
// 1) CIP fetch // 1) CIP fetch
const cipResponse = await fetch(`${apiUrl}/cip/${socCode}`); const cipResponse = await fetch(`/api/cip/${socCode}`);
if (!cipResponse.ok) { if (!cipResponse.ok) {
setError( setError(
`We're sorry, but specific details for "${career.title}" are not available at this time.` `We're sorry, but specific details for "${career.title}" are not available at this time.`
@ -379,7 +378,7 @@ function CareerExplorer() {
// 2) Job details (description + tasks) // 2) Job details (description + tasks)
const jobDetailsResponse = await fetch( const jobDetailsResponse = await fetch(
`${apiUrl}/onet/career-description/${socCode}` `/api/onet/career-description/${socCode}`
); );
if (!jobDetailsResponse.ok) { if (!jobDetailsResponse.ok) {
setCareerDetails({ setCareerDetails({
@ -393,7 +392,7 @@ function CareerExplorer() {
// 3) Salary data // 3) Salary data
let salaryResponse; let salaryResponse;
try { try {
salaryResponse = await axios.get(`${apiUrl}/salary`, { salaryResponse = await axios.get('/api/salary', {
params: { socCode: socCode.split('.')[0], area: areaTitle }, params: { socCode: socCode.split('.')[0], area: areaTitle },
}); });
} catch (error) { } catch (error) {
@ -437,7 +436,7 @@ function CareerExplorer() {
let economicResponse = { data: {} }; let economicResponse = { data: {} };
try { try {
economicResponse = await axios.get( economicResponse = await axios.get(
`${apiUrl}/projections/${socCode.split('.')[0]}`, `/api/projections/${socCode.split('.')[0]}`,
{ {
params: { state: fullStateName }, params: { state: fullStateName },
} }
@ -457,13 +456,13 @@ function CareerExplorer() {
try { try {
// Check local DB first (SQLite -> server2) // Check local DB first (SQLite -> server2)
const localRiskRes = await axios.get(`${apiUrl}/ai-risk/${socCode}`); const localRiskRes = await axios.get(`/api/ai-risk/${socCode}`);
aiRisk = localRiskRes.data; aiRisk = localRiskRes.data;
} catch (err) { } catch (err) {
// If 404, we call server3's ChatGPT route at the SAME base url // If 404, we call server3's ChatGPT route at the SAME base url
if (err.response && err.response.status === 404) { if (err.response && err.response.status === 404) {
try { try {
const aiRes = await axios.post(`${apiUrl}/public/ai-risk-analysis`, { const aiRes = await axios.post('/api/public/ai-risk-analysis', {
socCode, socCode,
careerName: career.title, careerName: career.title,
jobDescription: description, jobDescription: description,
@ -473,7 +472,7 @@ function CareerExplorer() {
const { riskLevel, reasoning } = aiRes.data; const { riskLevel, reasoning } = aiRes.data;
// store it back in server2 to avoid repeated GPT calls // store it back in server2 to avoid repeated GPT calls
await axios.post(`${apiUrl}/ai-risk`, { await axios.post('/api/ai-risk', {
socCode, socCode,
careerName: aiRes.data.careerName, careerName: aiRes.data.careerName,
jobDescription: aiRes.data.jobDescription, jobDescription: aiRes.data.jobDescription,
@ -521,7 +520,7 @@ function CareerExplorer() {
setLoading(false); setLoading(false);
} }
}, },
[userState, apiUrl, areaTitle] [userState, areaTitle]
); );
// ------------------------------------------------------ // ------------------------------------------------------
@ -644,7 +643,7 @@ useEffect(() => {
try { try {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
await axios.post( await axios.post(
`${apiUrl}/user-profile`, '/api/user-profile',
{ {
firstName: userProfile?.firstname, firstName: userProfile?.firstname,
lastName: userProfile?.lastname, lastName: userProfile?.lastname,

View File

@ -1,7 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import axios from 'axios'; import axios from 'axios';
const apiUrl = process.env.REACT_APP_API_URL;
function CareerModal({ career, careerDetails, closeModal, addCareerToList }) { function CareerModal({ career, careerDetails, closeModal, addCareerToList }) {

View File

@ -38,10 +38,6 @@ import InfoTooltip from "./ui/infoTooltip.js";
import differenceInMonths from 'date-fns/differenceInMonths'; import differenceInMonths from 'date-fns/differenceInMonths';
import "../styles/legacy/MilestoneTimeline.legacy.css"; import "../styles/legacy/MilestoneTimeline.legacy.css";
const apiUrl = process.env.REACT_APP_API_URL || '';
// -------------- // --------------
// Register ChartJS Plugins // Register ChartJS Plugins
@ -339,7 +335,6 @@ function getYearsInCareer(startDateString) {
export default function CareerRoadmap({ selectedCareer: initialCareer }) { export default function CareerRoadmap({ selectedCareer: initialCareer }) {
const { careerId } = useParams(); const { careerId } = useParams();
const location = useLocation(); const location = useLocation();
const apiURL = process.env.REACT_APP_API_URL;
const [interestStrategy, setInterestStrategy] = useState('FLAT'); // 'NONE' | 'FLAT' | 'MONTE_CARLO' const [interestStrategy, setInterestStrategy] = useState('FLAT'); // 'NONE' | 'FLAT' | 'MONTE_CARLO'
const [flatAnnualRate, setFlatAnnualRate] = useState(0.06); const [flatAnnualRate, setFlatAnnualRate] = useState(0.06);
@ -398,7 +393,7 @@ export default function CareerRoadmap({ selectedCareer: initialCareer }) {
const reloadScenarioAndCollege = useCallback(async () => { const reloadScenarioAndCollege = useCallback(async () => {
if (!careerProfileId) return; if (!careerProfileId) return;
const s = await authFetch( const s = await authFetch(
`${apiURL}/premium/career-profile/${careerProfileId}` `api/premium/career-profile/${careerProfileId}`
); );
if (s.ok) { if (s.ok) {
const row = await s.json(); const row = await s.json();
@ -408,10 +403,10 @@ export default function CareerRoadmap({ selectedCareer: initialCareer }) {
} }
const c = await authFetch( const c = await authFetch(
`${apiURL}/premium/college-profile?careerProfileId=${careerProfileId}` `api/premium/college-profile?careerProfileId=${careerProfileId}`
); );
if (c.ok) setCollegeProfile(await c.json()); if (c.ok) setCollegeProfile(await c.json());
}, [careerProfileId, apiURL]); }, [careerProfileId]);
const milestoneGroups = useMemo(() => { const milestoneGroups = useMemo(() => {
if (!scenarioMilestones.length) return []; if (!scenarioMilestones.length) return [];
@ -514,10 +509,10 @@ useEffect(() => {
const up = await authFetch('/api/user-profile'); const up = await authFetch('/api/user-profile');
if (up.ok) setUserProfile(await up.json()); if (up.ok) setUserProfile(await up.json());
const fp = await authFetch(`${apiURL}/premium/financial-profile`); const fp = await authFetch('api/premium/financial-profile');
if (fp.ok) setFinancialProfile(await fp.json()); if (fp.ok) setFinancialProfile(await fp.json());
})(); })();
}, [apiURL]); }, []);
/* quick derived helpers */ /* quick derived helpers */
const userSalary = parseFloatOrZero(financialProfile?.current_salary); const userSalary = parseFloatOrZero(financialProfile?.current_salary);
@ -687,7 +682,7 @@ useEffect(() => {
(async function init () { (async function init () {
/* 1 ▸ get every row the user owns */ /* 1 ▸ get every row the user owns */
const r = await authFetch(`${apiURL}/premium/career-profile/all`); const r = await authFetch('api/premium/career-profile/all');
if (!r?.ok || cancelled) return; if (!r?.ok || cancelled) return;
const { careerProfiles=[] } = await r.json(); const { careerProfiles=[] } = await r.json();
setExistingCareerProfiles(careerProfiles); setExistingCareerProfiles(careerProfiles);
@ -771,9 +766,9 @@ useEffect(() => {
const refetchScenario = useCallback(async () => { const refetchScenario = useCallback(async () => {
if (!careerProfileId) return; if (!careerProfileId) return;
const r = await authFetch(`${apiURL}/premium/career-profile/${careerProfileId}`); const r = await authFetch('api/premium/career-profile/${careerProfileId}');
if (r.ok) setScenarioRow(await r.json()); if (r.ok) setScenarioRow(await r.json());
}, [careerProfileId, apiURL]); }, [careerProfileId]);
// 5) from scenarioRow => find the full SOC => strip // 5) from scenarioRow => find the full SOC => strip
useEffect(() => { useEffect(() => {
@ -822,14 +817,14 @@ async function fetchAiRisk(socCode, careerName, description, tasks) {
try { try {
// 1) Check server2 for existing entry // 1) Check server2 for existing entry
const localRiskRes = await axios.get(`${apiUrl}/ai-risk/${socCode}`); const localRiskRes = await axios.get('api/ai-risk/${socCode}');
aiRisk = localRiskRes.data; // { socCode, riskLevel, ... } aiRisk = localRiskRes.data; // { socCode, riskLevel, ... }
} catch (err) { } catch (err) {
// 2) If 404 => call server3 // 2) If 404 => call server3
if (err.response && err.response.status === 404) { if (err.response && err.response.status === 404) {
try { try {
// Call GPT via server3 // Call GPT via server3
const aiRes = await axios.post(`${apiUrl}/public/ai-risk-analysis`, { const aiRes = await axios.post('api/public/ai-risk-analysis', {
socCode, socCode,
careerName, careerName,
jobDescription: description, jobDescription: description,
@ -863,7 +858,7 @@ try {
} }
// 3) Store in server2 // 3) Store in server2
await axios.post(`${apiUrl}/ai-risk`, storePayload); await axios.post('api/ai-risk', storePayload);
// Construct final object for usage here // Construct final object for usage here
aiRisk = { aiRisk = {
@ -900,7 +895,7 @@ useEffect(() => {
(async () => { (async () => {
try { try {
const qs = new URLSearchParams({ socCode: strippedSocCode, area: userArea }); const qs = new URLSearchParams({ socCode: strippedSocCode, area: userArea });
const res = await fetch(`${apiURL}/salary?${qs}`, { signal: ctrl.signal }); const res = await fetch('api/salary?${qs}', { signal: ctrl.signal });
if (res.ok) { if (res.ok) {
setSalaryData(await res.json()); setSalaryData(await res.json());
@ -916,7 +911,7 @@ useEffect(() => {
// cancel if strippedSocCode / userArea changes before the fetch ends // cancel if strippedSocCode / userArea changes before the fetch ends
return () => ctrl.abort(); return () => ctrl.abort();
}, [strippedSocCode, userArea, apiURL]); }, [strippedSocCode, userArea]);
/* 7) Economic Projections ---------------------------------------- */ /* 7) Economic Projections ---------------------------------------- */
useEffect(() => { useEffect(() => {
@ -932,7 +927,7 @@ useEffect(() => {
try { try {
const qs = new URLSearchParams({ state: userState }); const qs = new URLSearchParams({ state: userState });
const res = await authFetch( const res = await authFetch(
`${apiURL}/projections/${strippedSocCode}?${qs}`, `api/projections/${strippedSocCode}?${qs}`,
{ signal: ctrl.signal } { signal: ctrl.signal }
); );
@ -949,7 +944,7 @@ useEffect(() => {
})(); })();
return () => ctrl.abort(); return () => ctrl.abort();
}, [strippedSocCode, userState, apiURL]); }, [strippedSocCode, userState]);
// 8) Build financial projection // 8) Build financial projection
async function buildProjection(milestones) { async function buildProjection(milestones) {
@ -960,7 +955,7 @@ useEffect(() => {
// fetch impacts // fetch impacts
const imPromises = allMilestones.map((m) => const imPromises = allMilestones.map((m) =>
authFetch(`${apiURL}/premium/milestone-impacts?milestone_id=${m.id}`) authFetch(`api/premium/milestone-impacts?milestone_id=${m.id}`)
.then((r) => (r.ok ? r.json() : null)) .then((r) => (r.ok ? r.json() : null))
.then((dd) => dd?.impacts || []) .then((dd) => dd?.impacts || [])
.catch((e) => { .catch((e) => {
@ -1280,8 +1275,8 @@ const fetchMilestones = useCallback(async () => {
if (!careerProfileId) return; if (!careerProfileId) return;
const [profRes, uniRes] = await Promise.all([ const [profRes, uniRes] = await Promise.all([
authFetch(`${apiURL}/premium/milestones?careerProfileId=${careerProfileId}`), authFetch(`api/premium/milestones?careerProfileId=${careerProfileId}`),
authFetch(`${apiURL}/premium/milestones?careerProfileId=universal`) authFetch(`api/premium/milestones?careerProfileId=universal`)
]); ]);
if (!profRes.ok || !uniRes.ok) return; if (!profRes.ok || !uniRes.ok) return;
@ -1293,7 +1288,7 @@ const fetchMilestones = useCallback(async () => {
if (financialProfile && scenarioRow && collegeProfile) { if (financialProfile && scenarioRow && collegeProfile) {
buildProjection(merged); buildProjection(merged);
} // single rebuild } // single rebuild
}, [financialProfile, scenarioRow, careerProfileId, apiURL]); // ← NOTICE: no buildProjection here }, [financialProfile, scenarioRow, careerProfileId]); // ← NOTICE: no buildProjection here
return ( return (
@ -1543,7 +1538,6 @@ const fetchMilestones = useCallback(async () => {
setFinancialProfile={setFinancialProfile} setFinancialProfile={setFinancialProfile}
collegeProfile={collegeProfile} collegeProfile={collegeProfile}
setCollegeProfile={setCollegeProfile} setCollegeProfile={setCollegeProfile}
apiURL={apiURL}
authFetch={authFetch} authFetch={authFetch}
/> />

View File

@ -159,7 +159,6 @@ function Dashboard() {
const popoutVisible = !!selectedCareer; const popoutVisible = !!selectedCareer;
// ============= Auth & URL Setup ============= // ============= Auth & URL Setup =============
const apiUrl = process.env.REACT_APP_API_URL || '';
// AUTH fetch // AUTH fetch
const authFetch = async (url, options = {}, onUnauthorized) => { const authFetch = async (url, options = {}, onUnauthorized) => {
@ -194,7 +193,7 @@ function Dashboard() {
// ============= Fetch user profile ============= // ============= Fetch user profile =============
const fetchUserProfile = async () => { const fetchUserProfile = async () => {
const res = await authFetch(`${apiUrl}/user-profile`); const res = await authFetch('api/user-profile');
if (!res) return; if (!res) return;
if (res.ok) { if (res.ok) {
@ -215,7 +214,7 @@ function Dashboard() {
// ============= Lifecycle: Load Profile ============= // ============= Lifecycle: Load Profile =============
useEffect(() => { useEffect(() => {
fetchUserProfile(); fetchUserProfile();
}, [apiUrl]); // load once }, []); // load once
// ============= jobZone & Fit Setup ============= // ============= jobZone & Fit Setup =============
const jobZoneLabels = { const jobZoneLabels = {
@ -237,7 +236,7 @@ function Dashboard() {
try { try {
setLoading(true); setLoading(true);
setProgress(0); setProgress(0);
const response = await authFetch(`${apiUrl}/onet/submit_answers`, { const response = await authFetch('api/onet/submit_answers', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ answers }), body: JSON.stringify({ answers }),
@ -294,7 +293,7 @@ function Dashboard() {
if (careerSuggestions.length === 0) return; if (careerSuggestions.length === 0) return;
const socCodes = careerSuggestions.map((career) => career.code); const socCodes = careerSuggestions.map((career) => career.code);
try { try {
const response = await axios.post(`${apiUrl}/job-zones`, { socCodes }); const response = await axios.post('api/job-zones', { socCodes });
const jobZoneData = response.data; const jobZoneData = response.data;
const updatedCareers = careerSuggestions.map((career) => ({ const updatedCareers = careerSuggestions.map((career) => ({
...career, ...career,
@ -306,7 +305,7 @@ function Dashboard() {
} }
}; };
fetchJobZones(); fetchJobZones();
}, [careerSuggestions, apiUrl]); }, [careerSuggestions]);
// ============= Filter by job zone, fit ============= // ============= Filter by job zone, fit =============
const filteredCareers = useMemo(() => { const filteredCareers = useMemo(() => {
@ -382,20 +381,20 @@ function Dashboard() {
try { try {
// CIP fetch // CIP fetch
const cipResponse = await fetch(`${apiUrl}/cip/${socCode}`); const cipResponse = await fetch(`api/cip/${socCode}`);
if (!cipResponse.ok) throw new Error('Failed to fetch CIP Code'); if (!cipResponse.ok) throw new Error('Failed to fetch CIP Code');
const { cipCode } = await cipResponse.json(); const { cipCode } = await cipResponse.json();
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4); const cleanedCipCode = cipCode.replace('.', '').slice(0, 4);
// Job details // Job details
const jobDetailsResponse = await fetch(`${apiUrl}/onet/career-description/${socCode}`); const jobDetailsResponse = await fetch(`api/onet/career-description/${socCode}`);
if (!jobDetailsResponse.ok) throw new Error('Failed to fetch job description'); if (!jobDetailsResponse.ok) throw new Error('Failed to fetch job description');
const { description, tasks } = await jobDetailsResponse.json(); const { description, tasks } = await jobDetailsResponse.json();
// Salary // Salary
let salaryResponse; let salaryResponse;
try { try {
salaryResponse = await axios.get(`${apiUrl}/salary`, { salaryResponse = await axios.get('api/salary', {
params: { socCode: socCode.split('.')[0], area: areaTitle }, params: { socCode: socCode.split('.')[0], area: areaTitle },
}); });
} catch (error) { } catch (error) {
@ -407,7 +406,7 @@ function Dashboard() {
// Economic // Economic
let economicResponse; let economicResponse;
try { try {
economicResponse = await axios.get(`${apiUrl}/projections/${socCode.split('.')[0]}`, { economicResponse = await axios.get(`api/projections/${socCode.split('.')[0]}`, {
params: { state: fullName }, // e.g. "Kentucky" params: { state: fullName }, // e.g. "Kentucky"
}); });
} catch (error) { } catch (error) {
@ -417,7 +416,7 @@ function Dashboard() {
// Tuition // Tuition
let tuitionResponse; let tuitionResponse;
try { try {
tuitionResponse = await axios.get(`${apiUrl}/tuition`, { tuitionResponse = await axios.get('api/tuition', {
params: { cipCode: cleanedCipCode, state: userState }, params: { cipCode: cleanedCipCode, state: userState },
}); });
} catch (error) { } catch (error) {
@ -515,7 +514,7 @@ function Dashboard() {
setLoading(false); setLoading(false);
} }
}, },
[userState, apiUrl, areaTitle, userZipcode, updateChatbotContext] [userState, areaTitle, userZipcode, updateChatbotContext]
); );
// ============= Let typed careers open PopoutPanel ============= // ============= Let typed careers open PopoutPanel =============

View File

@ -8,7 +8,6 @@ const MilestoneAddModal = ({
defaultScenarioId, defaultScenarioId,
scenarioId, // which scenario this milestone applies to scenarioId, // which scenario this milestone applies to
editMilestone, // if editing an existing milestone, pass its data editMilestone, // if editing an existing milestone, pass its data
apiURL
}) => { }) => {
// Basic milestone fields // Basic milestone fields
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
@ -74,7 +73,7 @@ const MilestoneAddModal = ({
if (editMilestone) { if (editMilestone) {
// 1) Update existing milestone // 1) Update existing milestone
milestoneId = editMilestone.id; milestoneId = editMilestone.id;
await authFetch(`${apiURL}/premium/milestones/${milestoneId}`, { await authFetch(`api/premium/milestones/${milestoneId}`, {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
@ -87,7 +86,7 @@ const MilestoneAddModal = ({
// Then handle impacts below... // Then handle impacts below...
} else { } else {
// 1) Create new milestone // 1) Create new milestone
const res = await authFetch(`${apiURL}/premium/milestones`, { const res = await authFetch('api/premium/milestones', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
@ -105,7 +104,7 @@ const MilestoneAddModal = ({
// For simplicity, let's do multiple POST calls // For simplicity, let's do multiple POST calls
for (const impact of impacts) { for (const impact of impacts) {
// If editing, you might do a PUT if the impact already has an id // If editing, you might do a PUT if the impact already has an id
await authFetch(`${apiURL}/premium/milestone-impacts`, { await authFetch('api/premium/milestone-impacts', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
@ -117,8 +116,8 @@ const MilestoneAddModal = ({
end_month: impact.end_month !== null end_month: impact.end_month !== null
? parseInt(impact.end_month, 10) ? parseInt(impact.end_month, 10)
: null, : null,
created_at: new Date()..toISOString().slice(0, 10), created_at: new Date().toISOString().slice(0, 10),
updated_at: new Date()..toISOString().slice(0, 10) updated_at: new Date().toISOString().slice(0, 10)
}) })
}); });
} }

View File

@ -8,8 +8,6 @@ import authFetch from '../../utils/authFetch.js';
// 1) Import your CareerSearch component // 1) Import your CareerSearch component
import CareerSearch from '../CareerSearch.js'; // adjust path as necessary import CareerSearch from '../CareerSearch.js'; // adjust path as necessary
const apiURL = process.env.REACT_APP_API_URL;
const CareerOnboarding = ({ nextStep, prevStep, data, setData }) => { const CareerOnboarding = ({ nextStep, prevStep, data, setData }) => {
// We store local state for “are you working,” “selectedCareer,” etc. // We store local state for “are you working,” “selectedCareer,” etc.
const [currentlyWorking, setCurrentlyWorking] = useState(''); const [currentlyWorking, setCurrentlyWorking] = useState('');

View File

@ -10,7 +10,6 @@ function SignIn({ setIsAuthenticated, setUser }) {
const [error, setError] = useState(''); const [error, setError] = useState('');
const [showSessionExpiredMsg, setShowSessionExpiredMsg] = useState(false); const [showSessionExpiredMsg, setShowSessionExpiredMsg] = useState(false);
const location = useLocation(); const location = useLocation();
const apiUrl = process.env.REACT_APP_API_URL;
useEffect(() => { useEffect(() => {
// Check if the URL query param has ?session=expired // Check if the URL query param has ?session=expired
@ -43,7 +42,7 @@ function SignIn({ setIsAuthenticated, setUser }) {
} }
try { try {
const resp = await fetch(`${apiUrl}/signin`, { const resp = await fetch('api/signin', {
method : 'POST', method : 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body : JSON.stringify({username, password}), body : JSON.stringify({username, password}),

View File

@ -49,7 +49,6 @@ export function haversineDistance(lat1, lon1, lat2, lon2) {
export async function fetchSchools(cipCodes) { export async function fetchSchools(cipCodes) {
try { try {
const apiUrl = process.env.REACT_APP_API_URL || '';
// 1) If `cipCodes` is a single string => wrap in array // 1) If `cipCodes` is a single string => wrap in array
let codesArray = Array.isArray(cipCodes) ? cipCodes : [cipCodes]; let codesArray = Array.isArray(cipCodes) ? cipCodes : [cipCodes];
@ -59,7 +58,7 @@ export async function fetchSchools(cipCodes) {
const cipParam = codesArray.join(','); const cipParam = codesArray.join(',');
// 3) Call your endpoint with `?cipCodes=1101,1409&state=NY` // 3) Call your endpoint with `?cipCodes=1101,1409&state=NY`
const response = await axios.get(`${apiUrl}/schools`, { const response = await axios.get('api/schools', {
params: { params: {
cipCodes: cipParam, cipCodes: cipParam,
}, },

View File

@ -2,15 +2,15 @@
import axios from 'axios'; import axios from 'axios';
export async function fetchCareerEnrichment(apiUrl, socCode, area) { export async function fetchCareerEnrichment(socCode, area) {
// strippedSoc = remove decimals from e.g. "15-1132.00" => "15-1132" // strippedSoc = remove decimals from e.g. "15-1132.00" => "15-1132"
const strippedSoc = socCode.includes('.') ? socCode.split('.')[0] : socCode; const strippedSoc = socCode.includes('.') ? socCode.split('.')[0] : socCode;
const [cipData, jobDetailsData, economicData, salaryData] = await Promise.all([ const [cipData, jobDetailsData, economicData, salaryData] = await Promise.all([
axios.get(`${apiUrl}/cip/${socCode}`).catch(() => null), axios.get(`api/cip/${socCode}`).catch(() => null),
axios.get(`${apiUrl}/onet/career-description/${socCode}`).catch(() => null), axios.get(`api/onet/career-description/${socCode}`).catch(() => null),
axios.get(`${apiUrl}/projections/${strippedSoc}`, { params: { area } }).catch(() => null), axios.get(`api/projections/${strippedSoc}`, { params: { area } }).catch(() => null),
axios.get(`${apiUrl}/salary`, { params: { socCode: strippedSoc, area } }).catch(() => null), axios.get('api/salary', { params: { socCode: strippedSoc, area } }).catch(() => null),
]); ]);
return { return {

View File

@ -21,20 +21,20 @@
try { try {
// CIP fetch // CIP fetch
const cipResponse = await fetch(`${apiUrl}/cip/${socCode}`); const cipResponse = await fetch(`api/cip/${socCode}`);
if (!cipResponse.ok) throw new Error('Failed to fetch CIP Code'); if (!cipResponse.ok) throw new Error('Failed to fetch CIP Code');
const { cipCode } = await cipResponse.json(); const { cipCode } = await cipResponse.json();
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4); const cleanedCipCode = cipCode.replace('.', '').slice(0, 4);
// Job details // Job details
const jobDetailsResponse = await fetch(`${apiUrl}/onet/career-description/${socCode}`); const jobDetailsResponse = await fetch(`api/onet/career-description/${socCode}`);
if (!jobDetailsResponse.ok) throw new Error('Failed to fetch job description'); if (!jobDetailsResponse.ok) throw new Error('Failed to fetch job description');
const { description, tasks } = await jobDetailsResponse.json(); const { description, tasks } = await jobDetailsResponse.json();
// Salary // Salary
let salaryResponse; let salaryResponse;
try { try {
salaryResponse = await axios.get(`${apiUrl}/salary`, { salaryResponse = await axios.get('api/salary', {
params: { socCode: socCode.split('.')[0], area: areaTitle }, params: { socCode: socCode.split('.')[0], area: areaTitle },
}); });
} catch (error) { } catch (error) {
@ -46,7 +46,7 @@
// Economic // Economic
let economicResponse; let economicResponse;
try { try {
economicResponse = await axios.get(`${apiUrl}/projections/${socCode.split('.')[0]}`, { economicResponse = await axios.get(`api/projections/${socCode.split('.')[0]}`, {
params: { state: fullName }, // e.g. "Kentucky" params: { state: fullName }, // e.g. "Kentucky"
}); });
} catch (error) { } catch (error) {
@ -56,7 +56,7 @@
// Tuition // Tuition
let tuitionResponse; let tuitionResponse;
try { try {
tuitionResponse = await axios.get(`${apiUrl}/tuition`, { tuitionResponse = await axios.get('api/tuition', {
params: { cipCode: cleanedCipCode, state: userState }, params: { cipCode: cleanedCipCode, state: userState },
}); });
} catch (error) { } catch (error) {
@ -154,5 +154,5 @@
setLoading(false); setLoading(false);
} }
}, },
[userState, apiUrl, areaTitle, userZipcode, updateChatbotContext] [userState, areaTitle, userZipcode, updateChatbotContext]
); );