fixed server2 database and public file access
This commit is contained in:
parent
a59ecd2b7a
commit
c2cc63e2f7
1
.env
1
.env
@ -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
|
@ -4,8 +4,8 @@ ONET_PASSWORD=2296ahq
|
|||||||
|
|
||||||
# ─── Public‐facing React build ───────────
|
# ─── Public‐facing 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
|
||||||
|
@ -3,9 +3,9 @@ ONET_USERNAME=aptivaai
|
|||||||
ONET_PASSWORD=2296ahq
|
ONET_PASSWORD=2296ahq
|
||||||
|
|
||||||
# ─── Public‐facing React build ───────────
|
# ─── Public‐facing 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
|
||||||
|
@ -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/*
|
||||||
|
@ -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"]
|
|
@ -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 \
|
||||||
|
@ -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(',')
|
||||||
|
@ -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 }
|
|
@ -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 }
|
|
@ -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 }
|
|
@ -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]
|
|
||||||
|
@ -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) => {
|
||||||
|
@ -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,
|
||||||
|
@ -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 }) {
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -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 =============
|
||||||
|
@ -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)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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('');
|
||||||
|
@ -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}),
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
|
@ -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 {
|
||||||
|
@ -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]
|
||||||
);
|
);
|
Loading…
Reference in New Issue
Block a user