diff --git a/Dockerfile.server b/Dockerfile.server
deleted file mode 100644
index 14429c4..0000000
--- a/Dockerfile.server
+++ /dev/null
@@ -1,10 +0,0 @@
-ARG APPPORT=5000
-FROM node:20-slim
-WORKDIR /app
-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 npm ci --omit=dev --ignore-scripts
-COPY . .
-ENV PORT=5000
-EXPOSE 5000
-CMD ["node","backend/server.js"]
diff --git a/Dockerfile.server1 b/Dockerfile.server1
new file mode 100644
index 0000000..39634b6
--- /dev/null
+++ b/Dockerfile.server1
@@ -0,0 +1,16 @@
+FROM node:20-bullseye AS base
+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 public/ /app/public/
+RUN npm ci --unsafe-perm
+COPY . .
+
+CMD ["node", "backend/server1.js"]
\ No newline at end of file
diff --git a/backend/server.js b/backend/server1.js
similarity index 95%
rename from backend/server.js
rename to backend/server1.js
index ed25ad8..b6f34c4 100755
--- a/backend/server.js
+++ b/backend/server1.js
@@ -579,9 +579,11 @@ app.get('/api/areas', (req, res) => {
}
// Use env when present (Docker), fall back for local dev
- const salaryDbPath =
- process.env.SALARY_DB || '/app/data/salary_info.db';
-
+const salaryDbPath =
+ process.env.SALARY_DB_PATH // ← preferred
+ || process.env.SALARY_DB // ← legacy
+ || '/app/salary_info.db'; // final fallback
+
const salaryDb = new sqlite3.Database(
salaryDbPath,
sqlite3.OPEN_READONLY,
diff --git a/backend/server3.js b/backend/server3.js
index 4cce5f6..a62a27d 100644
--- a/backend/server3.js
+++ b/backend/server3.js
@@ -2452,18 +2452,30 @@ app.delete('/api/premium/milestones/:milestoneId', authenticatePremiumUser, asyn
FINANCIAL PROFILES
------------------------------------------------------------------ */
-app.get('/api/premium/financial-profile', authenticatePremiumUser, async (req, res) => {
- try {
- const [rows] = await pool.query(`
- SELECT *
- FROM financial_profiles
- WHERE user_id = ?
- `, [req.id]);
- res.json(rows[0] || {});
- } catch (error) {
- console.error('Error fetching financial profile:', error);
- res.status(500).json({ error: 'Failed to fetch financial profile' });
- }
+// GET /api/premium/financial-profile
+app.get('/api/premium/financial-profile', auth, (req, res) => {
+ const uid = req.userId;
+ db.query('SELECT * FROM financial_profile WHERE user_id=?', [uid],
+ (err, rows) => {
+ if (err) return res.status(500).json({ error:'DB error' });
+
+ if (!rows.length) {
+ // ←———— send a benign default instead of 404
+ return res.json({
+ current_salary: 0,
+ additional_income: 0,
+ monthly_expenses: 0,
+ monthly_debt_payments: 0,
+ retirement_savings: 0,
+ emergency_fund: 0,
+ retirement_contribution: 0,
+ emergency_contribution: 0,
+ extra_cash_emergency_pct: 50,
+ extra_cash_retirement_pct: 50
+ });
+ }
+ res.json(rows[0]);
+ });
});
app.post('/api/premium/financial-profile', authenticatePremiumUser, async (req, res) => {
diff --git a/docker-compose.yml b/docker-compose.yml
index 3180b07..db7ce11 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,6 +8,11 @@ services:
<<: *with-env
image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server1:${IMG_TAG}
expose: ["${SERVER1_PORT}"]
+ environment:
+ SALARY_DB_PATH: /app/salary_info.db
+ volumes:
+ - ./salary_info.db:/app/salary_info.db:ro
+ - ./user_profile.db:/app/user_profile.db
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER1_PORT}/healthz || exit 1"]
interval: 30s
diff --git a/src/App.js b/src/App.js
index 671103f..84ddf58 100644
--- a/src/App.js
+++ b/src/App.js
@@ -171,8 +171,10 @@ const uiToolHandlers = useMemo(() => {
const confirmLogout = () => {
localStorage.removeItem('token');
+ localStorage.removeItem('id');
localStorage.removeItem('careerSuggestionsCache');
localStorage.removeItem('lastSelectedCareerProfileId');
+ localStorage.removeItem('selectedCareer');
localStorage.removeItem('aiClickCount');
localStorage.removeItem('aiClickDate');
localStorage.removeItem('aiRecommendations');
diff --git a/src/components/CareerRoadmap.js b/src/components/CareerRoadmap.js
index ba7ebe9..8417dfd 100644
--- a/src/components/CareerRoadmap.js
+++ b/src/components/CareerRoadmap.js
@@ -511,8 +511,13 @@ useEffect(() => {
const up = await authFetch('/api/user-profile');
if (up.ok) setUserProfile(await up.json());
- const fp = await authFetch('api/premium/financial-profile');
- if (fp.ok) setFinancialProfile(await fp.json());
+ const fp = await authFetch('/api/premium/financial-profile');
+ if (fp.status === 404) {
+ // user skipped onboarding – treat as empty object
+ setFinancialProfile({});
+ } else if (fp.ok) {
+ setFinancialProfile(await fp.json());
+ }
})();
}, []);
diff --git a/src/components/Paywall.js b/src/components/Paywall.js
index e79b6eb..dc2b827 100644
--- a/src/components/Paywall.js
+++ b/src/components/Paywall.js
@@ -5,7 +5,12 @@ import { Button } from './ui/button.js';
const Paywall = () => {
const navigate = useNavigate();
const { state } = useLocation();
- const { selectedCareer } = state || {};
+
+ const {
+ redirectTo = '/premium-onboarding', // wizard by default
+ prevState = {}, // any custom state we passed
+ selectedCareer
+ } = state || {};
const handleSubscribe = async () => {
const token = localStorage.getItem('token');
@@ -27,7 +32,7 @@ const Paywall = () => {
if (user) window.dispatchEvent(new Event('user-updated')); // or your context setter
// 2) give the auth context time to update, then push
- navigate('/premium-onboarding', { replace: true, state: { selectedCareer } });
+ navigate(redirectTo, { replace: true, state: prevState });
} else {
console.error('activate-premium failed:', await res.text());
}
diff --git a/src/components/PremiumOnboarding/CareerOnboarding.js b/src/components/PremiumOnboarding/CareerOnboarding.js
index 35396ea..cda102a 100644
--- a/src/components/PremiumOnboarding/CareerOnboarding.js
+++ b/src/components/PremiumOnboarding/CareerOnboarding.js
@@ -1,26 +1,38 @@
// CareerOnboarding.js
import React, { useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
-import axios from 'axios';
-import { Input } from '../ui/input.js'; // Ensure path matches your structure
-import authFetch from '../../utils/authFetch.js';
// 1) Import your CareerSearch component
import CareerSearch from '../CareerSearch.js'; // adjust path as necessary
-const CareerOnboarding = ({ nextStep, prevStep, data, setData }) => {
- // We store local state for “are you working,” “selectedCareer,” etc.
- const [currentlyWorking, setCurrentlyWorking] = useState('');
- const [selectedCareer, setSelectedCareer] = useState('');
- const [collegeEnrollmentStatus, setCollegeEnrollmentStatus] = useState('');
- const [showFinPrompt, setShowFinPrompt] = useState(false);
- const [financialReady, setFinancialReady] = useState(false); // persisted later if you wish
const Req = () => *;
- const ready = selectedCareer && currentlyWorking && collegeEnrollmentStatus;
-
- // 1) Grab the location state values, if any
+const CareerOnboarding = ({ nextStep, prevStep, data, setData, finishNow }) => {
+ // We store local state for “are you working,” “selectedCareer,” etc.
const location = useLocation();
+ const navCareerObj = location.state?.selectedCareer;
+ const [careerObj, setCareerObj] = useState(() => {
+ if (navCareerObj) return navCareerObj;
+ try {
+ return JSON.parse(localStorage.getItem('selectedCareer') || 'null');
+ } catch { return null; }
+ });
+ const [currentlyWorking, setCurrentlyWorking] = useState('');
+ const [collegeStatus, setCollegeStatus] = useState('');
+ const [showFinPrompt, setShowFinPrompt] = useState(false);
+
+ /* ── 2. derived helpers ───────────────────────────────────── */
+ const selectedCareerTitle = careerObj?.title || '';
+ const ready =
+ selectedCareerTitle &&
+ currentlyWorking &&
+ collegeStatus;
+
+ const inCollege = ['currently_enrolled', 'prospective_student']
+ .includes(collegeStatus);
+ const skipFin = !!data.skipFinancialStep;
+ // 1) Grab the location state values, if any
+
const {
socCode,
cipCodes,
@@ -29,63 +41,59 @@ const CareerOnboarding = ({ nextStep, prevStep, data, setData }) => {
userState,
} = location.state || {};
- // 2) On mount, see if location.state has a careerTitle and update local states if needed
+ /* ── 3. side‑effects when route brings a new career object ── */
useEffect(() => {
- if (careerTitle) {
- setSelectedCareer(careerTitle);
- setData((prev) => ({
- ...prev,
- career_name: careerTitle,
- soc_code: socCode || ''
- }));
- }
- }, [careerTitle, socCode, setData]);
+ if (!navCareerObj?.title) return;
+
+ setCareerObj(navCareerObj);
+ localStorage.setItem('selectedCareer', JSON.stringify(navCareerObj));
+
+ setData(prev => ({
+ ...prev,
+ career_name : navCareerObj.title,
+ soc_code : navCareerObj.soc_code || ''
+ }));
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [navCareerObj]); // ← run once per navigation change
+
// Called whenever other change
const handleChange = (e) => {
setData(prev => ({ ...prev, [e.target.name]: e.target.value }));
};
- // Called when user picks a career from CareerSearch and confirms it
- const handleCareerSelected = (careerObj) => {
- // e.g. { title, soc_code, cip_code, ... }
- setSelectedCareer(careerObj.title);
+ /* ── 4. callbacks ─────────────────────────────────────────── */
+ function handleCareerSelected(obj) {
+ setCareerObj(obj);
+ localStorage.setItem('selectedCareer', JSON.stringify(obj));
+ setData(prev => ({ ...prev, career_name: obj.title, soc_code: obj.soc_code || '' }));
+ }
+
+
+
+function handleSubmit() {
+ if (!ready) return alert('Fill all required fields.');
+ const inCollege = ['currently_enrolled', 'prospective_student'].includes(collegeStatus);
setData(prev => ({
...prev,
- career_name: careerObj.title,
- soc_code: careerObj.soc_code || '' // store SOC if needed
+ career_name : selectedCareerTitle,
+ college_enrollment_status : collegeStatus,
+ currently_working : currentlyWorking,
+ inCollege,
}));
- };
-
- const handleSubmit = () => {
- if (!selectedCareer || !currentlyWorking || !collegeEnrollmentStatus) {
- alert('Please complete all required fields before continuing.');
- return;
- }
- const isInCollege =
- collegeEnrollmentStatus === 'currently_enrolled' ||
- collegeEnrollmentStatus === 'prospective_student';
-
- // Merge local state into parent data
- setData(prevData => ({
- ...prevData,
- career_name: selectedCareer,
- college_enrollment_status: collegeEnrollmentStatus,
- currently_working: currentlyWorking,
- inCollege: isInCollege,
- // fallback defaults, or use user-provided
- status: prevData.status || 'planned',
- start_date: prevData.start_date || new Date().toISOString().slice(0, 10).slice(0, 10),
- projected_end_date: prevData.projected_end_date || null
- }));
-
- if (!showFinPrompt || financialReady) {
-
- nextStep();
+ /* — where do we go? — */
+ if (skipFin && !inCollege) {
+ /* user said “Skip” AND is not in college ⇒ jump to Review */
+ finishNow(); // ← the helper we just injected via props
} else {
-
+ nextStep(); // ordinary flow
}
- };
+}
+
+ const nextLabel = skipFin
+ ? inCollege ? 'College →' : 'Finish →'
+ : inCollege ? 'College →' : 'Financial →';
+
return (
@@ -116,18 +124,18 @@ const CareerOnboarding = ({ nextStep, prevStep, data, setData }) => {
{/* 2) Replace old local “Search for Career” with */}
- What career are you planning to pursue? (Please select from drop-down suggestions after typing)
+ Target Career
- This should be your target career path — whether it’s a new goal or the one you're already in.
+ This should be the career you are striving for — whether it’s a new goal or the one you're already in.
diff --git a/src/components/PremiumOnboarding/OnboardingContainer.js b/src/components/PremiumOnboarding/OnboardingContainer.js
index 9c68e53..2f6c780 100644
--- a/src/components/PremiumOnboarding/OnboardingContainer.js
+++ b/src/components/PremiumOnboarding/OnboardingContainer.js
@@ -86,6 +86,12 @@ const OnboardingContainer = () => {
console.log('Current careerData:', careerData);
console.log('Current collegeData:', collegeData);
+
+ function finishImmediately() {
+ // The review page is the last item in the steps array ⇒ index = onboardingSteps.length‑1
+ setStep(onboardingSteps.length - 1);
+}
+
// 4. Final “all done” submission
const handleFinalSubmit = async () => {
try {
@@ -209,6 +215,7 @@ navigate(`/career-roadmap/${finalCareerProfileId}`, {
,
diff --git a/src/components/PremiumRoute.js b/src/components/PremiumRoute.js
index 649fbf9..27dc219 100644
--- a/src/components/PremiumRoute.js
+++ b/src/components/PremiumRoute.js
@@ -1,21 +1,18 @@
-import React from 'react';
-import { Navigate } from 'react-router-dom';
+import { Navigate, useLocation } from 'react-router-dom';
-function PremiumRoute({ user, children }) {
- if (!user) {
- // Not even logged in; go to sign in
- return ;
+export default function PremiumRoute({ user, children }) {
+ const loc = useLocation();
+
+ /* Already premium → proceed */
+ if (user?.is_premium || user?.is_pro_premium) {
+ return children;
}
-
- // Check if user has *either* premium or pro
- const hasPremiumOrPro = user.is_premium || user.is_pro_premium;
- if (!hasPremiumOrPro) {
- // Logged in but neither plan; go to paywall
- return ;
- }
-
- // User is logged in and has premium or pro
- return children;
-}
-
-export default PremiumRoute;
+ /* NEW: send to paywall and remember where they wanted to go */
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/user_profile.db b/user_profile.db
index 051a0bf..cb40bcc 100644
Binary files a/user_profile.db and b/user_profile.db differ