From 86eda98bc59dc80f935c40d95859b68a25e367ef Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 6 Jun 2025 15:40:06 +0000 Subject: [PATCH] Added CareerCoach - adjusted CareerRoadmap. --- .env.development | 1 + .env.production | 1 + backend/server3.js | 339 +++++++++++++++++- src/App.js | 2 +- src/components/CareerCoach.js | 275 ++++++++++++++ src/components/CareerRoadmap.js | 183 ++++------ .../PremiumOnboarding/CareerOnboarding.js | 13 +- src/utils/parseAIJson.js | 29 ++ 8 files changed, 709 insertions(+), 134 deletions(-) create mode 100644 src/components/CareerCoach.js create mode 100644 src/utils/parseAIJson.js diff --git a/.env.development b/.env.development index 151704d..40de1f1 100755 --- a/.env.development +++ b/.env.development @@ -12,6 +12,7 @@ DB_USER=sqluser DB_NAME=user_profile_db DB_PASSWORD=ps= ${isoToday}. Titles must be <= 5 words. + +IMPORTANT RESTRICTIONS: +- NEVER suggest specific investments in cryptocurrency, stocks, or other speculative financial instruments. +- NEVER provide specific investment advice without appropriate risk disclosures. +- NEVER provide legal, medical, or psychological advice. +- ALWAYS promote responsible and low-risk financial planning strategies. +- Emphasize skills enhancement, networking, and education as primary pathways to financial success. + Respond ONLY in the requested JSON format.` }, { @@ -380,7 +389,7 @@ Respond ONLY in the requested JSON format.` Here is the user's current situation: ${summaryText} -Please provide exactly 3 short-term (within 6 months) and 2 long-term (1–3 years) milestones. Avoid any previously suggested milestones. +Please provide exactly 2 short-term (within 6 months) and 1 long-term (1–3 years) milestones. Avoid any previously suggested milestones. Each milestone must have: - "title" (up to 5 words) - "date" in YYYY-MM-DD format (>= ${isoToday}) @@ -429,11 +438,9 @@ function buildUserSummary({ userProfile = {}, scenarioRow = {}, financialProfile = {}, - collegeProfile = {} + collegeProfile = {}, + aiRisk = null }) { - // Provide a short multiline string about the user's finances, goals, etc. - // but avoid referencing scenarioRow.start_date - // e.g.: const location = `${userProfile.state || 'Unknown State'}, ${userProfile.area || 'N/A'}`; const careerName = scenarioRow.career_name || 'Unknown'; const careerGoals = scenarioRow.career_goals || 'No goals specified'; @@ -446,7 +453,13 @@ function buildUserSummary({ const retirementSavings = financialProfile.retirement_savings || 0; const emergencyFund = financialProfile.emergency_fund || 0; - // And similarly for collegeProfile if needed, ignoring start_date + let riskText = ''; + if (aiRisk?.riskLevel) { + riskText = ` +AI Automation Risk: ${aiRisk.riskLevel} +Reasoning: ${aiRisk.reasoning}`; + } + return ` User Location: ${location} Career Name: ${careerName} @@ -460,9 +473,317 @@ Financial: - Monthly Debt: \$${monthlyDebt} - Retirement Savings: \$${retirementSavings} - Emergency Fund: \$${emergencyFund} + +${riskText} `.trim(); } +// Example: ai/chat with correct milestone-saving logic +app.post('/api/premium/ai/chat', authenticatePremiumUser, async (req, res) => { + try { + const { + userProfile = {}, + scenarioRow = {}, + financialProfile = {}, + collegeProfile = {}, + chatHistory = [] + } = req.body; + + // ------------------------------------------------ + // 1. Helper Functions + // ------------------------------------------------ + + // A. Build a "where you are now" vs. "where you want to go" message + function buildStatusSituationMessage(status, situation, careerName) { + const sStatus = (status || "").toLowerCase(); // e.g. "planned", "current", "exploring" + const sSituation = (situation || "").toLowerCase(); // e.g. "planning", "preparing", "enhancing", "retirement" + + // "Where you are now" + let nowPart = ""; + switch (sStatus) { + case "planned": + nowPart = `It appears you're looking ahead to a possible future in ${careerName}.`; + break; + case "current": + nowPart = `It appears you're already working in the ${careerName} field.`; + break; + case "exploring": + nowPart = `It appears you're exploring how ${careerName} might fit your future plans.`; + break; + default: + nowPart = `I don’t have a clear picture of where you stand currently with ${careerName}.`; + break; + } + + // "Where you’d like to go next" + let nextPart = ""; + switch (sSituation) { + case "planning": + nextPart = `You're aiming to clarify your strategy for moving into this field.`; + break; + case "preparing": + nextPart = `You're actively developing the skills you need to step into ${careerName}.`; + break; + case "enhancing": + nextPart = `You’d like to deepen or expand your responsibilities within ${careerName}.`; + break; + case "retirement": + nextPart = `You're considering how to transition toward retirement in this role.`; + break; + default: + nextPart = `I'm not entirely sure of your next direction.`; + break; + } + + const combinedDescription = `${nowPart} ${nextPart}`.trim(); + + // Add a friendly note about how there's no "wrong" answer + const friendlyNote = ` +No worries if these selections feel a bit overlapping or if you're just exploring. +One portion highlights where you currently see yourself, and the other points to where you'd like to go. +Feel free to refine these whenever you want, or just continue as is. + `.trim(); + + return `${combinedDescription}\n\n${friendlyNote}`; + } + + // B. Build a user summary that references all available info + function buildUserSummary({ userProfile, scenarioRow, financialProfile, collegeProfile, aiRisk }) { + // For illustration; adjust to your actual data fields. + const userName = userProfile.first_name || "N/A"; + const location = userProfile.location || "N/A"; + const userGoals = userProfile.goals || []; // maybe an array + + const careerName = scenarioRow?.career_name || "this career"; + const socCode = scenarioRow?.soc_code || "N/A"; + const jobDescription = scenarioRow?.job_description || "No description"; + const tasksList = scenarioRow?.tasks?.length + ? scenarioRow.tasks.join(", ") + : "No tasks info"; + + const income = financialProfile?.income ? `$${financialProfile.income}` : "N/A"; + const debt = financialProfile?.debt ? `$${financialProfile.debt}` : "N/A"; + const savings = financialProfile?.savings ? `$${financialProfile.savings}` : "N/A"; + + const major = collegeProfile?.major || "N/A"; + const creditsCompleted = collegeProfile?.credits_completed || 0; + const graduationDate = collegeProfile?.expected_graduation || "Unknown"; + + const aiRiskReport = aiRisk?.report || "No AI risk info provided."; + + return ` +[USER PROFILE] +- Name: ${userName} +- Location: ${location} +- Goals: ${userGoals.length ? userGoals.join(", ") : "Not specified"} + +[TARGET CAREER] +- Career Name: ${careerName} +- SOC Code: ${socCode} +- Job Description: ${jobDescription} +- Typical Tasks: ${tasksList} + +[FINANCIAL PROFILE] +- Income: ${income} +- Debt: ${debt} +- Savings: ${savings} + +[COLLEGE / EDUCATION] +- Major: ${major} +- Credits Completed: ${creditsCompleted} +- Expected Graduation Date: ${graduationDate} + +[AI RISK ANALYSIS] +${aiRiskReport} + `.trim(); + } + + // Example environment config + const MILESTONE_API_URL = process.env.APTIVA_INTERNAL_API + ? `${process.env.APTIVA_INTERNAL_API}/premium/milestone` + : "http://localhost:5002/api/premium/milestone"; + + const IMPACT_API_URL = process.env.APTIVA_INTERNAL_API + ? `${process.env.APTIVA_INTERNAL_API}/premium/milestone-impacts` + : "http://localhost:5002/api/premium/milestone-impacts"; + + // ------------------------------------------------ + // 2. AI Risk Fetch + // ------------------------------------------------ + const apiBase = process.env.APTIVA_INTERNAL_API || "http://localhost:5002/api"; + let aiRisk = null; + try { + const aiRiskRes = await fetch(`${apiBase}/premium/ai-risk-analysis`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + socCode: scenarioRow?.soc_code, + careerName: scenarioRow?.career_name, + jobDescription: scenarioRow?.job_description, + tasks: scenarioRow?.tasks || [] + }) + }); + if (aiRiskRes.ok) { + aiRisk = await aiRiskRes.json(); + } else { + console.warn("AI risk fetch failed with status:", aiRiskRes.status); + } + } catch (err) { + console.error("Error fetching AI risk analysis:", err); + } + + // ------------------------------------------------ + // 3. Build Status + Situation text + // ------------------------------------------------ + const { status: userStatus } = scenarioRow; + const { career_situation: userSituation } = userProfile; + const careerName = scenarioRow?.career_name || "this career"; + + const combinedStatusSituation = buildStatusSituationMessage( + userStatus, + userSituation, + careerName + ); + + // ------------------------------------------------ + // 4. Build Additional Context Summary + // ------------------------------------------------ + const summaryText = buildUserSummary({ + userProfile, + scenarioRow, + financialProfile, + collegeProfile, + aiRisk + }); + + // ------------------------------------------------ + // 5. Construct System-Level Prompts + // ------------------------------------------------ + const systemPromptIntro = ` +You are Jess, a professional career coach working inside AptivaAI. + +The user has already provided detailed information about their situation, career goals, finances, education, and more. +Your job is to leverage *all* this context to provide specific, empathetic, and helpful advice. + +Do not re-ask for the details below unless you need clarifications. +Reflect and use the user's actual data. Avoid purely generic responses. +`.trim(); + + const systemPromptStatusSituation = ` +[CURRENT AND NEXT STEP OVERVIEW] +${combinedStatusSituation} +`.trim(); + + const systemPromptDetailedContext = ` +[DETAILED USER PROFILE & CONTEXT] +${summaryText} +`.trim(); + + // Build up the final messages array + const messagesToSend = [ + { role: "system", content: systemPromptIntro }, + { role: "system", content: systemPromptStatusSituation }, + { role: "system", content: systemPromptDetailedContext }, + ...chatHistory // includes user and assistant messages so far + ]; + + // ------------------------------------------------ + // 6. Call GPT + // ------------------------------------------------ + const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); + const completion = await openai.chat.completions.create({ + model: "gpt-4", + messages: messagesToSend, + temperature: 0.7, + max_tokens: 600 + }); + + let coachResponse = completion?.choices?.[0]?.message?.content?.trim(); + + // ------------------------------------------------ + // 7. Detect and Possibly Save Milestones + // ------------------------------------------------ + let milestones = []; + let isMilestoneFormat = false; + + try { + milestones = JSON.parse(coachResponse); + isMilestoneFormat = Array.isArray(milestones); + } catch (e) { + isMilestoneFormat = false; + } + + if (isMilestoneFormat && milestones.length) { + // 7a. Prepare data for milestone creation + const rawForMilestonesEndpoint = milestones.map(m => { + return { + title: m.title, + description: m.description, + date: m.date, + career_profile_id: scenarioRow?.id + }; + }); + + // 7b. Bulk-create milestones + const mileRes = await fetch(MILESTONE_API_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ milestones: rawForMilestonesEndpoint }) + }); + + if (!mileRes.ok) { + console.error("Failed to save milestones =>", mileRes.status); + coachResponse = "I prepared milestones, but couldn't save them right now. Please try again later."; + } else { + // 7c. newly created milestones with IDs + const createdMils = await mileRes.json(); + + // 7d. For each milestone, if it has "impacts", create them + for (let i = 0; i < milestones.length; i++) { + const originalMilestone = milestones[i]; + const newMilestone = createdMils[i]; + + if (Array.isArray(originalMilestone.impacts) && originalMilestone.impacts.length) { + for (const imp of originalMilestone.impacts) { + const impactPayload = { + milestone_id: newMilestone.id, + impact_type: imp.impact_type, + direction: imp.direction || 'subtract', + amount: imp.amount || 0, + start_date: imp.start_date || null, + end_date: imp.end_date || null + }; + + try { + const impactRes = await fetch(IMPACT_API_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(impactPayload) + }); + if (!impactRes.ok) { + console.error(`Failed to create impact for milestone ${newMilestone.id}`); + } + } catch (err) { + console.error(`Error creating impact for milestone ${newMilestone.id}`, err); + } + } + } + } + + coachResponse = "I've created some actionable milestones for you (with financial impacts). You can view them in your roadmap!"; + } + } + + // ------------------------------------------------ + // 8. Send JSON Response + // ------------------------------------------------ + res.json({ reply: coachResponse }); + } catch (err) { + console.error("Error in /api/premium/ai/chat =>", err); + res.status(500).json({ error: "Failed to generate conversational response." }); + } +}); + /*************************************************** AI MILESTONE CONVERSION ENDPOINT ****************************************************/ diff --git a/src/App.js b/src/App.js index 371752c..ad4a23a 100644 --- a/src/App.js +++ b/src/App.js @@ -257,7 +257,7 @@ function App() { to="/career-roadmap" className="block px-4 py-2 hover:bg-gray-100 text-sm text-gray-700" > - Career Roadmap + Roadmap & AI Career Coach { + if (chatContainerRef.current) { + chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight; + } + }, [messages, hasSentMessage]); + + useEffect(() => { + const introMessage = generatePersonalizedIntro(); + setMessages([introMessage]); + }, [scenarioRow, userProfile]); + + function buildStatusSituationMessage(status, situation, careerName) { + const sStatus = (status || "").toLowerCase(); + // e.g. "planned", "current", "exploring", etc. + + const sSituation = (situation || "").toLowerCase(); + // e.g. "planning", "preparing", "enhancing", "retirement" + + // ----------------------------- + // "Where you are right now" + // ----------------------------- + let nowPart = ""; + switch (sStatus) { + case "planned": + nowPart = `It sounds like you're looking ahead to a possible future in ${careerName}.`; + break; + case "current": + nowPart = `It sounds like you're already in the ${careerName} field.`; + break; + case "exploring": + nowPart = `It sounds like you're still exploring how ${careerName} might fit your plans.`; + break; + default: + nowPart = `I don’t have much info on your current involvement with ${careerName}.`; + break; + } + + // ----------------------------- + // "Where you’d like to go next" + // ----------------------------- + let nextPart = ""; + switch (sSituation) { + case "planning": + nextPart = `You're aiming to figure out your strategy for moving into this field.`; + break; + case "preparing": + nextPart = `You're actively developing the skills you need to step into ${careerName}.`; + break; + case "enhancing": + nextPart = `You’d like to deepen or broaden your responsibilities within ${careerName}.`; + break; + case "retirement": + nextPart = `You're contemplating how to transition toward retirement in this field.`; + break; + default: + nextPart = `I'm not entirely sure of your next direction.`; + break; + } + + // ----------------------------- + // Combine the descriptive text + // ----------------------------- + const combinedDescription = `${nowPart} ${nextPart}`.trim(); + + // ----------------------------- + // Optional “friendly note” if they seem to span different phases + // but we do *not* treat it as a mismatch or error. + // ----------------------------- + let friendlyNote = ` +Feel free to use AptivaAI however it best suits you—there’s no "wrong" answer. +One part highlights your current situation, the other indicates what you're aiming for next. +Some folks aren't working and want premium-level guidance, and that's where we shine. +We can refine details anytime or just jump straight to what you're most interested in exploring now! + `.trim(); + + // You could conditionally show the friendlyNote only if you detect certain combos, + // OR you can show it unconditionally to make the user comfortable. + + // For maximum inclusivity, we can always show it. + + return `${combinedDescription}\n\n${friendlyNote}`; +} + + const generatePersonalizedIntro = () => { + const careerName = scenarioRow?.career_name || null; + const goalsText = scenarioRow?.career_goals?.trim() || null; + const riskLevel = scenarioRow?.riskLevel; + const riskReasoning = scenarioRow?.riskReasoning; + + const userSituation = userProfile?.career_situation?.toLowerCase(); + const userStatus = scenarioRow?.status?.toLowerCase(); + + const combinedMessage = buildStatusSituationMessage(userStatus, userSituation, careerName); + + const interestInventoryMessage = userProfile?.riasec + ? `With your Interest Inventory profile (${userProfile.riasec}), I can tailor suggestions more precisely.` + : `If you complete the Interest Inventory, I’ll be able to offer more targeted suggestions based on your interests.`; + + const riskMessage = + riskLevel && riskReasoning + ? `Note: This role has a ${riskLevel} automation risk over the next 10 years. ${riskReasoning}` + : ""; + + const goalsMessage = goalsText + ? `Your goals include:
${goalsText + .split(/\d+\.\s?/) + .filter(Boolean) + .map((goal) => `• ${goal.trim()}`) + .join("
")}` + : null; + + const missingProfileFields = []; + if (!careerName) missingProfileFields.push("career choice"); + if (!goalsText) missingProfileFields.push("career goals"); + if (!userSituation) missingProfileFields.push("career phase"); + + let advisoryMessage = ""; + if (missingProfileFields.length > 0) { + advisoryMessage = `

If you provide ${ + missingProfileFields.length > 1 + ? "a few more details" + : "this information" + }, I’ll be able to offer more tailored and precise advice.`; + } + + return { + role: "assistant", + content: ` + Hi! ${combinedMessage}

+ ${goalsMessage ? goalsMessage + "

" : ""} + ${interestInventoryMessage}

+ ${riskMessage}
+ ${advisoryMessage}
+ I'm here to support you with personalized coaching. What would you like to focus on today? + `, + }; +}; + + + const handleSendMessage = async () => { + if (!input.trim() || loading) return; + + const userMessage = { role: "user", content: input.trim() }; + const updatedMessages = [...messages, userMessage]; + + setMessages(updatedMessages); + setInput(""); + setLoading(true); + + try { + const payload = { + userProfile, + financialProfile, + scenarioRow, + collegeProfile, + chatHistory: updatedMessages, + }; + + const res = await authFetch("/api/premium/ai/chat", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + + if (!res.ok) throw new Error("AI request failed"); + + const { reply } = await res.json(); + + setMessages((prev) => [ + ...prev, + { role: "assistant", content: reply }, + ]); + + if ( + reply.includes("created those milestones") || + reply.includes("milestones for you") + ) { + onMilestonesCreated?.(); + } + } catch (err) { + console.error("CareerCoach error:", err); + setMessages((prev) => [ + ...prev, + { + role: "assistant", + content: "Sorry, something went wrong. Please try again.", + }, + ]); + } finally { + setLoading(false); + } + }; + + const handleSubmit = (e) => { + e.preventDefault(); + handleSendMessage(); + setHasSentMessage(true); + }; + + return ( +
+

Career Coach

+ +
+ {messages.map((msg, i) => ( +
+
"), + }} + /> +
+ ))} + {loading && ( +
Coach is typing...
+ )} +
+ +
+ setInput(e.target.value)} + disabled={loading} + /> + +
+
+ ); +} diff --git a/src/components/CareerRoadmap.js b/src/components/CareerRoadmap.js index e32d53b..d1afd6c 100644 --- a/src/components/CareerRoadmap.js +++ b/src/components/CareerRoadmap.js @@ -18,9 +18,10 @@ import authFetch from '../utils/authFetch.js'; import { simulateFinancialProjection } from '../utils/FinancialProjectionService.js'; import parseFloatOrZero from '../utils/ParseFloatorZero.js'; import { getFullStateName } from '../utils/stateUtils.js'; - +import CareerCoach from "./CareerCoach.js"; import { Button } from './ui/button.js'; import ScenarioEditModal from './ScenarioEditModal.js'; +import parseAIJson from "../utils/parseAIJson.js"; // your shared parser import './CareerRoadmap.css'; import './MilestoneTimeline.css'; @@ -233,43 +234,15 @@ function getYearsInCareer(startDateString) { return Math.floor(diffYears).toString(); } -/** - * parseAiJson - * If ChatGPT returns a fenced code block like: - * ```json - * [ { ... }, ... ] - * ``` - * we extract that JSON. Otherwise, we parse the raw string. - */ -function parseAiJson(rawText) { - const fencedRegex = /```json\s*([\s\S]*?)\s*```/i; - const match = rawText.match(fencedRegex); - if (match) { - const jsonStr = match[1].trim(); - const arr = JSON.parse(jsonStr); - // Add an "id" for each milestone - arr.forEach((m) => { - m.id = crypto.randomUUID(); - }); - return arr; - } else { - // fallback if no fences - const arr = JSON.parse(rawText); - arr.forEach((m) => { - m.id = crypto.randomUUID(); - }); - return arr; - } -} export default function CareerRoadmap({ selectedCareer: initialCareer }) { const location = useLocation(); const apiURL = process.env.REACT_APP_API_URL; const [interestStrategy, setInterestStrategy] = useState('FLAT'); // 'NONE' | 'FLAT' | 'MONTE_CARLO' - const [flatAnnualRate, setFlatAnnualRate] = useState(0.06); // 6% default - const [randomRangeMin, setRandomRangeMin] = useState(-0.02); // -3% monthly - const [randomRangeMax, setRandomRangeMax] = useState(0.02); // 8% monthly + const [flatAnnualRate, setFlatAnnualRate] = useState(0.06); + const [randomRangeMin, setRandomRangeMin] = useState(-0.02); + const [randomRangeMax, setRandomRangeMax] = useState(0.02); // Basic states const [userProfile, setUserProfile] = useState(null); @@ -818,7 +791,7 @@ if (aiLoading || clickCount >= DAILY_CLICK_LIMIT) { const data = await res.json(); const rawText = data.recommendations || ''; - const arr = parseAiJson(rawText); + const arr = parseAIJson(rawText); setRecommendations(arr); localStorage.setItem('aiRecommendations', JSON.stringify(arr)); @@ -838,53 +811,6 @@ if (aiLoading || clickCount >= DAILY_CLICK_LIMIT) { } - - function handleToggle(recId) { - setSelectedIds((prev) => { - if (prev.includes(recId)) { - return prev.filter((x) => x !== recId); - } else { - return [...prev, recId]; - } - }); - } - - async function handleCreateSelectedMilestones() { - if (!careerProfileId) return; - const confirm = window.confirm('Create the selected AI suggestions as milestones?'); - if (!confirm) return; - - const selectedRecs = recommendations.filter((r) => selectedIds.includes(r.id)); - if (!selectedRecs.length) return; - - // Use the AI-suggested date: - const payload = selectedRecs.map((rec) => ({ - title: rec.title, - description: rec.description || '', - date: rec.date, // <-- use AI's date, not today's date - career_profile_id: careerProfileId - })); - - try { - const r = await authFetch('/api/premium/milestone', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ milestones: payload }) - }); - if (!r.ok) throw new Error('Failed to create new milestones'); - - // re-run projection to see them in the chart - await buildProjection(); - - // optionally clear - alert('Milestones created successfully!'); - setSelectedIds([]); - } catch (err) { - console.error('Error creating milestones =>', err); - alert('Error saving new AI milestones.'); - } - } - function handleSimulationYearsChange(e) { setSimulationYearsInput(e.target.value); } @@ -895,13 +821,26 @@ if (aiLoading || clickCount >= DAILY_CLICK_LIMIT) { const buttonLabel = recommendations.length > 0 ? 'New Suggestions' : 'What Should I Do Next?'; return ( -
-

Where Am I Now?

+
+ + {/* 0) New CareerCoach at the top */} + { + /* refresh or reload logic here */ + }} + /> + + {/* 1) Then your "Where Am I Now?" */} +

Where you are now and where you are going.

{/* 1) Career */}

- Current Career:{' '} + Target Career:{' '} {scenarioRow?.career_name || '(Select a career)'}

{yearsInCareer && ( @@ -915,7 +854,7 @@ if (aiLoading || clickCount >= DAILY_CLICK_LIMIT) { {/* 2) Salary Benchmarks */}
{salaryData?.regional && ( -
+

Regional Salary Data ({userArea || 'U.S.'})

@@ -948,7 +887,7 @@ if (aiLoading || clickCount >= DAILY_CLICK_LIMIT) { )} {salaryData?.national && ( -
+

National Salary Data

10th percentile:{' '} @@ -979,7 +918,7 @@ if (aiLoading || clickCount >= DAILY_CLICK_LIMIT) {

{/* 3) Economic Projections */} -
+
{economicProjections?.state && ( )} @@ -993,13 +932,13 @@ if (aiLoading || clickCount >= DAILY_CLICK_LIMIT) {
)} - {/* 4) Career Goals */} + {/* 4) Career Goals

Your Career Goals

{scenarioRow?.career_goals || 'No career goals entered yet.'}

-
+
*/} {/* 5) Financial Projection */}
@@ -1117,42 +1056,42 @@ if (aiLoading || clickCount >= DAILY_CLICK_LIMIT) {
)} - {/* 7) AI Next Steps */} -
- + {/* 7) AI Next Steps */} + {/*
+ - {aiLoading &&

Generating your next steps…

} + {aiLoading &&

Generating your next steps…

} - {/* If we have structured recs, show checkboxes */} - {recommendations.length > 0 && ( -
-

Select the Advice You Want to Keep

-
    - {recommendations.map((m) => ( -
  • - handleToggle(m.id)} - /> -
    - {m.title} - {m.date} -

    {m.description}

    -
    -
  • - ))} -
- {selectedIds.length > 0 && ( - - )} -
- )} -
+ {/* If we have structured recs, show checkboxes + {recommendations.length > 0 && ( +
+

Select the Advice You Want to Keep

+
    + {recommendations.map((m) => ( +
  • + handleToggle(m.id)} + /> +
    + {m.title} + {m.date} +

    {m.description}

    +
    +
  • + ))} +
+ {selectedIds.length > 0 && ( + + )} +
+ )} +
*/}
); } diff --git a/src/components/PremiumOnboarding/CareerOnboarding.js b/src/components/PremiumOnboarding/CareerOnboarding.js index 6411b9e..b7e9c48 100644 --- a/src/components/PremiumOnboarding/CareerOnboarding.js +++ b/src/components/PremiumOnboarding/CareerOnboarding.js @@ -69,8 +69,11 @@ const CareerOnboarding = ({ nextStep, prevStep, data, setData }) => {
+

+ (We ask this to understand your financial picture. This won’t affect how we track your progress toward your target career.) +