UI Update for Premium Onboarding

This commit is contained in:
Josh 2025-05-01 15:55:37 +00:00
parent d207dce5d4
commit e1f1782b01
4 changed files with 549 additions and 409 deletions

View File

@ -1,4 +1,4 @@
// CareerOnboarding.js (inline implementation of career search) // CareerOnboarding.js
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import axios from 'axios'; import axios from 'axios';
import { Input } from '../ui/input.js'; // Ensure path matches your structure import { Input } from '../ui/input.js'; // Ensure path matches your structure
@ -25,24 +25,23 @@ const CareerOnboarding = ({ nextStep, prevStep, data, setData }) => {
} }
}, []); }, []);
// Fetch careers exactly once on mount
useEffect(() => { useEffect(() => {
const fetchCareerTitles = async () => { const fetchCareerTitles = async () => {
try { try {
const response = await fetch('/career_clusters.json'); const response = await fetch('/career_clusters.json');
const data = await response.json(); const data = await response.json();
const careerTitlesSet = new Set(); const careerTitlesSet = new Set();
const clusters = Object.keys(data); const clusters = Object.keys(data);
for (let i = 0; i < clusters.length; i++) { for (let i = 0; i < clusters.length; i++) {
const cluster = clusters[i]; const cluster = clusters[i];
const subdivisions = Object.keys(data[cluster]); const subdivisions = Object.keys(data[cluster]);
for (let j = 0; j < subdivisions.length; j++) { for (let j = 0; j < subdivisions.length; j++) {
const subdivision = subdivisions[j]; const subdivision = subdivisions[j];
const careersArray = data[cluster][subdivision]; const careersArray = data[cluster][subdivision];
for (let k = 0; k < careersArray.length; k++) { for (let k = 0; k < careersArray.length; k++) {
const careerObj = careersArray[k]; const careerObj = careersArray[k];
if (careerObj.title) { if (careerObj.title) {
@ -51,18 +50,17 @@ const CareerOnboarding = ({ nextStep, prevStep, data, setData }) => {
} }
} }
} }
setCareers([...careerTitlesSet]); setCareers([...careerTitlesSet]);
} catch (error) { } catch (error) {
console.error("Error fetching or processing career_clusters.json:", error); console.error("Error fetching or processing career_clusters.json:", error);
} }
}; };
fetchCareerTitles(); fetchCareerTitles();
}, []); }, []);
// Update career selection automatically whenever the searchInput matches a valid career explicitly
useEffect(() => { useEffect(() => {
if (careers.includes(searchInput)) { if (careers.includes(searchInput)) {
setSelectedCareer(searchInput); setSelectedCareer(searchInput);
@ -77,14 +75,13 @@ const CareerOnboarding = ({ nextStep, prevStep, data, setData }) => {
const handleCareerInputChange = (e) => { const handleCareerInputChange = (e) => {
const inputValue = e.target.value; const inputValue = e.target.value;
setSearchInput(inputValue); setSearchInput(inputValue);
// only set explicitly when an exact match occurs
if (careers.includes(inputValue)) { if (careers.includes(inputValue)) {
setSelectedCareer(inputValue); setSelectedCareer(inputValue);
setData(prev => ({ ...prev, career_name: inputValue })); setData(prev => ({ ...prev, career_name: inputValue }));
} }
}; };
const handleSubmit = () => { const handleSubmit = () => {
if (!selectedCareer || !currentlyWorking || !collegeEnrollmentStatus) { if (!selectedCareer || !currentlyWorking || !collegeEnrollmentStatus) {
alert("Please complete all required fields before continuing."); alert("Please complete all required fields before continuing.");
@ -110,31 +107,33 @@ const CareerOnboarding = ({ nextStep, prevStep, data, setData }) => {
nextStep(); nextStep();
}; };
return ( return (
<div> <div className="max-w-md mx-auto p-6 space-y-4">
<h2>Career Details</h2> <h2 className="text-2xl font-semibold">Career Details</h2>
<label className="block font-medium"> <div className="space-y-2">
Are you currently working or earning any income (even part-time)? <label className="block font-medium">
</label> Are you currently working or earning any income (even part-time)?
<select </label>
value={currentlyWorking} <select
onChange={(e) => setCurrentlyWorking(e.target.value)} value={currentlyWorking}
className="w-full border rounded p-2" onChange={(e) => setCurrentlyWorking(e.target.value)}
> className="w-full border rounded p-2"
<option value="">Select one</option> >
<option value="yes">Yes</option> <option value="">Select one</option>
<option value="no">No</option> <option value="yes">Yes</option>
</select> <option value="no">No</option>
</select>
</div>
<div> <div className="space-y-2">
<h3>Search for Career</h3> <h3 className="font-medium">Search for Career</h3>
<input <input
value={searchInput} value={searchInput}
onChange={handleCareerInputChange} onChange={handleCareerInputChange}
placeholder="Start typing a career..." placeholder="Start typing a career..."
list="career-titles" list="career-titles"
className="w-full border rounded p-2"
/> />
<datalist id="career-titles"> <datalist id="career-titles">
{careers.map((career, index) => ( {careers.map((career, index) => (
@ -143,38 +142,79 @@ const CareerOnboarding = ({ nextStep, prevStep, data, setData }) => {
</datalist> </datalist>
</div> </div>
{selectedCareer && <p>Selected Career: <strong>{selectedCareer}</strong></p>} {selectedCareer && (
<p className="text-gray-700">
Selected Career: <strong>{selectedCareer}</strong>
</p>
)}
<label>Status:</label> <div className="space-y-2">
<select name="status" onChange={handleChange} value={data.status || ''}> <label className="block font-medium">Status:</label>
<option value="">Select Status</option> <select
<option value="Planned">Planned</option> name="status"
<option value="Current">Current</option> onChange={handleChange}
<option value="Exploring">Exploring</option> value={data.status || ''}
</select> className="w-full border rounded p-2"
>
<option value="">Select Status</option>
<option value="Planned">Planned</option>
<option value="Current">Current</option>
<option value="Exploring">Exploring</option>
</select>
</div>
<label>Career Start Date:</label> <div className="space-y-2">
<input name="start_date" type="date" onChange={handleChange} value={data.start_date || ''} /> <label className="block font-medium">Career Start Date:</label>
<input
name="start_date"
type="date"
onChange={handleChange}
value={data.start_date || ''}
className="w-full border rounded p-2"
/>
</div>
<label>Projected End Date (optional):</label> <div className="space-y-2">
<input name="projected_end_date" type="date" onChange={handleChange} value={data.projected_end_date || ''} /> <label className="block font-medium">Projected End Date (optional):</label>
<input
name="projected_end_date"
type="date"
onChange={handleChange}
value={data.projected_end_date || ''}
className="w-full border rounded p-2"
/>
</div>
<label className="block font-medium"> <div className="space-y-2">
Are you currently enrolled in college or planning to enroll? <label className="block font-medium">
</label> Are you currently enrolled in college or planning to enroll?
<select </label>
value={collegeEnrollmentStatus} <select
onChange={(e) => setCollegeEnrollmentStatus(e.target.value)} value={collegeEnrollmentStatus}
className="w-full border rounded p-2" onChange={(e) => setCollegeEnrollmentStatus(e.target.value)}
> className="w-full border rounded p-2"
<option value="">Select one</option> >
<option value="not_enrolled">Not Enrolled / Not Planning</option> <option value="">Select one</option>
<option value="currently_enrolled">Currently Enrolled</option> <option value="not_enrolled">Not Enrolled / Not Planning</option>
<option value="prospective_student">Planning to Enroll (Prospective Student)</option> <option value="currently_enrolled">Currently Enrolled</option>
</select> <option value="prospective_student">Planning to Enroll (Prospective Student)</option>
</select>
</div>
<button onClick={prevStep}>Back</button> <div className="flex justify-between pt-4">
<button onClick={handleSubmit}>Financial </button> <button
onClick={prevStep}
className="bg-gray-200 hover:bg-gray-300 text-gray-700 font-semibold py-2 px-4 rounded"
>
Back
</button>
<button
onClick={handleSubmit}
className="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded"
>
Financial
</button>
</div>
</div> </div>
); );
}; };

View File

@ -1,7 +1,7 @@
// CollegeOnboarding.js
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import authFetch from '../../utils/authFetch.js'; import authFetch from '../../utils/authFetch.js';
function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId }) { function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId }) {
// CIP / iPEDS local states (purely for CIP data and suggestions) // CIP / iPEDS local states (purely for CIP data and suggestions)
const [schoolData, setSchoolData] = useState([]); const [schoolData, setSchoolData] = useState([]);
@ -11,8 +11,6 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId })
const [availableProgramTypes, setAvailableProgramTypes] = useState([]); const [availableProgramTypes, setAvailableProgramTypes] = useState([]);
// ---- DESCTRUCTURE PARENT DATA FOR ALL FIELDS EXCEPT TUITION/PROGRAM_LENGTH ---- // ---- DESCTRUCTURE PARENT DATA FOR ALL FIELDS EXCEPT TUITION/PROGRAM_LENGTH ----
// We'll store user "typed" values for tuition/program_length in local states,
// but everything else comes directly from `data`.
const { const {
college_enrollment_status = '', college_enrollment_status = '',
selected_school = '', selected_school = '',
@ -34,59 +32,42 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId })
hours_completed = '', hours_completed = '',
credit_hours_required = '', credit_hours_required = '',
tuition_paid = '', tuition_paid = '',
// We do NOT consume data.tuition or data.program_length directly here
// because we store them in local states (manualTuition, manualProgramLength).
} = data; } = data;
// ---- 1. LOCAL STATES for auto/manual logic on TWO fields ---- // ---- 1. LOCAL STATES for auto/manual logic on TWO fields ----
// manualTuition: user typed override const [manualTuition, setManualTuition] = useState('');
// autoTuition: iPEDS calculation
const [manualTuition, setManualTuition] = useState(''); // '' means no manual override
const [autoTuition, setAutoTuition] = useState(0); const [autoTuition, setAutoTuition] = useState(0);
// same approach for program_length
const [manualProgramLength, setManualProgramLength] = useState(''); const [manualProgramLength, setManualProgramLength] = useState('');
const [autoProgramLength, setAutoProgramLength] = useState('0.00'); const [autoProgramLength, setAutoProgramLength] = useState('0.00');
// ------------------------------------------ // -- universal handleChange for all parent fields except tuition/program_length
// Universal handleChange for all parent fields
// ------------------------------------------
const handleParentFieldChange = (e) => { const handleParentFieldChange = (e) => {
const { name, value, type, checked } = e.target; const { name, value, type, checked } = e.target;
let val = value; let val = value;
if (type === 'checkbox') { if (type === 'checkbox') {
val = checked; val = checked;
} }
// parse numeric fields that are NOT tuition or program_length
if (['interest_rate','loan_term','extra_payment','expected_salary'].includes(name)) { if (['interest_rate','loan_term','extra_payment','expected_salary'].includes(name)) {
val = parseFloat(val) || 0; val = parseFloat(val) || 0;
} else if ( } else if (
['annual_financial_aid','existing_college_debt','credit_hours_per_year', ['annual_financial_aid','existing_college_debt','credit_hours_per_year',
'hours_completed','credit_hours_required','tuition_paid'] 'hours_completed','credit_hours_required','tuition_paid'].includes(name)
.includes(name)
) { ) {
val = val === '' ? '' : parseFloat(val); val = val === '' ? '' : parseFloat(val);
} }
setData(prev => ({ ...prev, [name]: val })); setData(prev => ({ ...prev, [name]: val }));
}; };
// ------------------------------------------
// handleManualTuition, handleManualProgramLength
// for local fields
// ------------------------------------------
const handleManualTuitionChange = (e) => { const handleManualTuitionChange = (e) => {
// user typed something => override setManualTuition(e.target.value);
setManualTuition(e.target.value); // store as string for partial typing
}; };
const handleManualProgramLengthChange = (e) => { const handleManualProgramLengthChange = (e) => {
setManualProgramLength(e.target.value); setManualProgramLength(e.target.value);
}; };
// ------------------------------------------ // CIP fetch
// CIP Data fetch once
// ------------------------------------------
useEffect(() => { useEffect(() => {
async function fetchCipData() { async function fetchCipData() {
try { try {
@ -104,9 +85,7 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId })
fetchCipData(); fetchCipData();
}, []); }, []);
// ------------------------------------------ // iPEDS fetch
// iPEDS Data fetch once
// ------------------------------------------
useEffect(() => { useEffect(() => {
async function fetchIpedsData() { async function fetchIpedsData() {
try { try {
@ -125,9 +104,7 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId })
fetchIpedsData(); fetchIpedsData();
}, []); }, []);
// ------------------------------------------ // handleSchoolChange
// handleSchoolChange, handleProgramChange, etc. => update parent fields
// ------------------------------------------
const handleSchoolChange = (e) => { const handleSchoolChange = (e) => {
const value = e.target.value; const value = e.target.value;
setData(prev => ({ setData(prev => ({
@ -137,7 +114,6 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId })
program_type: '', program_type: '',
credit_hours_required: '', credit_hours_required: '',
})); }));
// CIP suggestions
const filtered = schoolData.filter(s => const filtered = schoolData.filter(s =>
s.INSTNM.toLowerCase().includes(value.toLowerCase()) s.INSTNM.toLowerCase().includes(value.toLowerCase())
); );
@ -163,7 +139,6 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId })
const handleProgramChange = (e) => { const handleProgramChange = (e) => {
const value = e.target.value; const value = e.target.value;
setData(prev => ({ ...prev, selected_program: value })); setData(prev => ({ ...prev, selected_program: value }));
if (!value) { if (!value) {
setProgramSuggestions([]); setProgramSuggestions([]);
return; return;
@ -188,11 +163,11 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId })
program_type: val, program_type: val,
credit_hours_required: '', credit_hours_required: '',
})); }));
setManualProgramLength(''); // reset manual override setManualProgramLength('');
setAutoProgramLength('0.00'); setAutoProgramLength('0.00');
}; };
// Once we have school+program, load possible program types // once we have school+program, load possible program types
useEffect(() => { useEffect(() => {
if (!selected_program || !selected_school || !schoolData.length) return; if (!selected_program || !selected_school || !schoolData.length) return;
const possibleTypes = schoolData const possibleTypes = schoolData
@ -204,15 +179,11 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId })
setAvailableProgramTypes([...new Set(possibleTypes)]); setAvailableProgramTypes([...new Set(possibleTypes)]);
}, [selected_program, selected_school, schoolData]); }, [selected_program, selected_school, schoolData]);
// ------------------------------------------ // auto-calc tuition
// Auto-calc Tuition => store in local autoTuition
// ------------------------------------------
useEffect(() => { useEffect(() => {
// do we have enough to calc?
if (!icTuitionData.length) return; if (!icTuitionData.length) return;
if (!selected_school || !program_type || !credit_hours_per_year) return; if (!selected_school || !program_type || !credit_hours_per_year) return;
// find row
const found = schoolData.find(s => s.INSTNM.toLowerCase() === selected_school.toLowerCase()); const found = schoolData.find(s => s.INSTNM.toLowerCase() === selected_school.toLowerCase());
if (!found) return; if (!found) return;
const unitId = found.UNITID; const unitId = found.UNITID;
@ -221,7 +192,6 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId })
const match = icTuitionData.find(row => row.UNITID === unitId); const match = icTuitionData.find(row => row.UNITID === unitId);
if (!match) return; if (!match) return;
// grad or undergrad
const isGradOrProf = [ const isGradOrProf = [
"Master's Degree", "Master's Degree",
"Doctoral Degree", "Doctoral Degree",
@ -258,7 +228,6 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId })
const chpy = parseFloat(credit_hours_per_year) || 0; const chpy = parseFloat(credit_hours_per_year) || 0;
let estimate = 0; let estimate = 0;
// threshold
if (chpy < 24 && partTimeRate) { if (chpy < 24 && partTimeRate) {
estimate = partTimeRate * chpy; estimate = partTimeRate * chpy;
} else { } else {
@ -266,15 +235,12 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId })
} }
setAutoTuition(Math.round(estimate)); setAutoTuition(Math.round(estimate));
}, [ }, [
icTuitionData, selected_school, program_type, icTuitionData, selected_school, program_type,
credit_hours_per_year, is_in_state, is_in_district, schoolData credit_hours_per_year, is_in_state, is_in_district, schoolData
]); ]);
// ------------------------------------------ // auto-calc program length
// Auto-calc Program Length => store in local autoProgramLength
// ------------------------------------------
useEffect(() => { useEffect(() => {
if (!program_type) return; if (!program_type) return;
if (!hours_completed || !credit_hours_per_year) return; if (!hours_completed || !credit_hours_per_year) return;
@ -299,264 +265,315 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId })
setAutoProgramLength(calcLength); setAutoProgramLength(calcLength);
}, [program_type, hours_completed, credit_hours_per_year, credit_hours_required]); }, [program_type, hours_completed, credit_hours_per_year, credit_hours_required]);
// ------------------------------------------ // final handleSubmit
// handleSubmit => merges final chosen values
// ------------------------------------------
const handleSubmit = () => { const handleSubmit = () => {
const chosenTuition = manualTuition.trim() === '' const chosenTuition = manualTuition.trim() === ''
? autoTuition ? autoTuition
: parseFloat(manualTuition); : parseFloat(manualTuition);
const chosenProgramLength = manualProgramLength.trim() === '' const chosenProgramLength = manualProgramLength.trim() === ''
? autoProgramLength ? autoProgramLength
: manualProgramLength; : manualProgramLength;
// Update parents data (collegeData)
setData(prev => ({ setData(prev => ({
...prev, ...prev,
tuition: chosenTuition, // match name used by parent or server tuition: chosenTuition,
program_length: chosenProgramLength // match name used by parent program_length: chosenProgramLength
})); }));
// Then go to the next step in the parents wizard
nextStep(); nextStep();
}; };
// The displayed tuition => (manualTuition !== '' ? manualTuition : autoTuition)
const displayedTuition = (manualTuition.trim() === '' ? autoTuition : manualTuition); const displayedTuition = (manualTuition.trim() === '' ? autoTuition : manualTuition);
// The displayed program length => (manualProgramLength !== '' ? manualProgramLength : autoProgramLength)
const displayedProgramLength = (manualProgramLength.trim() === '' ? autoProgramLength : manualProgramLength); const displayedProgramLength = (manualProgramLength.trim() === '' ? autoProgramLength : manualProgramLength);
return ( return (
<div> <div className="max-w-md mx-auto p-6 space-y-4">
<h2>College Details</h2> <h2 className="text-2xl font-semibold">College Details</h2>
{(college_enrollment_status === 'currently_enrolled' || {(college_enrollment_status === 'currently_enrolled' ||
college_enrollment_status === 'prospective_student') ? ( college_enrollment_status === 'prospective_student') ? (
<> <div className="space-y-4">
<label> <div className="flex items-center space-x-2">
<input <input
type="checkbox" type="checkbox"
name="is_in_district" name="is_in_district"
checked={is_in_district} checked={is_in_district}
onChange={handleParentFieldChange} onChange={handleParentFieldChange}
className="h-4 w-4"
/> />
In District? <label className="font-medium">In District?</label>
</label> </div>
<label> <div className="flex items-center space-x-2">
<input <input
type="checkbox" type="checkbox"
name="is_in_state" name="is_in_state"
checked={is_in_state} checked={is_in_state}
onChange={handleParentFieldChange} onChange={handleParentFieldChange}
className="h-4 w-4"
/> />
In State Tuition? <label className="font-medium">In State Tuition?</label>
</label> </div>
<label> <div className="flex items-center space-x-2">
<input <input
type="checkbox" type="checkbox"
name="is_online" name="is_online"
checked={is_online} checked={is_online}
onChange={handleParentFieldChange} onChange={handleParentFieldChange}
className="h-4 w-4"
/> />
Program is Fully Online <label className="font-medium">Program is Fully Online</label>
</label> </div>
<label> <div className="flex items-center space-x-2">
<input <input
type="checkbox" type="checkbox"
name="loan_deferral_until_graduation" name="loan_deferral_until_graduation"
checked={loan_deferral_until_graduation} checked={loan_deferral_until_graduation}
onChange={handleParentFieldChange} onChange={handleParentFieldChange}
className="h-4 w-4"
/> />
Defer Loan Payments until Graduation? <label className="font-medium">Defer Loan Payments until Graduation?</label>
</label> </div>
<label>School Name*</label>
<input
name="selected_school"
value={selected_school}
onChange={handleSchoolChange}
list="school-suggestions"
placeholder="Enter school name..."
/>
<datalist id="school-suggestions">
{schoolSuggestions.map((sch, idx) => (
<option
key={idx}
value={sch}
onClick={() => handleSchoolSelect(sch)}
/>
))}
</datalist>
<label>Program Name*</label> <div className="space-y-1">
<input <label className="block font-medium">School Name*</label>
name="selected_program" <input
value={selected_program} name="selected_school"
onChange={handleProgramChange} value={selected_school}
list="program-suggestions" onChange={handleSchoolChange}
placeholder="Enter program name..." list="school-suggestions"
/> placeholder="Enter school name..."
<datalist id="program-suggestions"> className="w-full border rounded p-2"
{programSuggestions.map((prog, idx) => ( />
<option <datalist id="school-suggestions">
key={idx} {schoolSuggestions.map((sch, idx) => (
value={prog} <option
onClick={() => handleProgramSelect(prog)} key={idx}
/> value={sch}
))} onClick={() => handleSchoolSelect(sch)}
</datalist> />
))}
</datalist>
</div>
<label>Program Type*</label> <div className="space-y-1">
<select <label className="block font-medium">Program Name*</label>
name="program_type" <input
value={program_type} name="selected_program"
onChange={handleProgramTypeSelect} value={selected_program}
> onChange={handleProgramChange}
<option value="">Select Program Type</option> list="program-suggestions"
{availableProgramTypes.map((t, i) => ( placeholder="Enter program name..."
<option key={i} value={t}>{t}</option> className="w-full border rounded p-2"
))} />
</select> <datalist id="program-suggestions">
{programSuggestions.map((prog, idx) => (
<option
key={idx}
value={prog}
onClick={() => handleProgramSelect(prog)}
/>
))}
</datalist>
</div>
<div className="space-y-1">
<label className="block font-medium">Program Type*</label>
<select
name="program_type"
value={program_type}
onChange={handleProgramTypeSelect}
className="w-full border rounded p-2"
>
<option value="">Select Program Type</option>
{availableProgramTypes.map((t, i) => (
<option key={i} value={t}>{t}</option>
))}
</select>
</div>
{(program_type === 'Graduate/Professional Certificate' || {(program_type === 'Graduate/Professional Certificate' ||
program_type === 'First Professional Degree' || program_type === 'First Professional Degree' ||
program_type === 'Doctoral Degree') && ( program_type === 'Doctoral Degree') && (
<> <div className="space-y-1">
<label>Credit Hours Required</label> <label className="block font-medium">Credit Hours Required</label>
<input <input
type="number" type="number"
name="credit_hours_required" name="credit_hours_required"
value={credit_hours_required} value={credit_hours_required}
onChange={handleParentFieldChange} onChange={handleParentFieldChange}
placeholder="e.g. 30" placeholder="e.g. 30"
className="w-full border rounded p-2"
/> />
</> </div>
)} )}
<label>Credit Hours Per Year</label> <div className="space-y-1">
<input <label className="block font-medium">Credit Hours Per Year</label>
type="number" <input
name="credit_hours_per_year" type="number"
value={credit_hours_per_year} name="credit_hours_per_year"
onChange={handleParentFieldChange} value={credit_hours_per_year}
placeholder="e.g. 24" onChange={handleParentFieldChange}
/> placeholder="e.g. 24"
className="w-full border rounded p-2"
/>
</div>
<label>Yearly Tuition</label> <div className="space-y-1">
{/* If user typed a custom value => manualTuition, else autoTuition */} <label className="block font-medium">Yearly Tuition</label>
<input <input
type="number" type="number"
value={displayedTuition} value={displayedTuition}
onChange={handleManualTuitionChange} onChange={handleManualTuitionChange}
placeholder="Leave blank to use auto, or type an override" placeholder="Leave blank to use auto, or type an override"
/> className="w-full border rounded p-2"
/>
</div>
<label>Annual Financial Aid</label> <div className="space-y-1">
<input <label className="block font-medium">Annual Financial Aid</label>
type="number" <input
name="annual_financial_aid" type="number"
value={annual_financial_aid} name="annual_financial_aid"
onChange={handleParentFieldChange} value={annual_financial_aid}
placeholder="e.g. 2000" onChange={handleParentFieldChange}
/> placeholder="e.g. 2000"
className="w-full border rounded p-2"
/>
</div>
<label>Existing College Loan Debt</label> <div className="space-y-1">
<input <label className="block font-medium">Existing College Loan Debt</label>
type="number" <input
name="existing_college_debt" type="number"
value={existing_college_debt} name="existing_college_debt"
onChange={handleParentFieldChange} value={existing_college_debt}
placeholder="e.g. 2000" onChange={handleParentFieldChange}
/> placeholder="e.g. 2000"
className="w-full border rounded p-2"
/>
</div>
{college_enrollment_status === 'currently_enrolled' && ( {college_enrollment_status === 'currently_enrolled' && (
<> <>
<label>Tuition Paid</label> <div className="space-y-1">
<input <label className="block font-medium">Tuition Paid</label>
type="number" <input
name="tuition_paid" type="number"
value={tuition_paid} name="tuition_paid"
onChange={handleParentFieldChange} value={tuition_paid}
placeholder="Already paid" onChange={handleParentFieldChange}
/> placeholder="Already paid"
className="w-full border rounded p-2"
/>
</div>
<label>Hours Completed</label> <div className="space-y-1">
<input <label className="block font-medium">Hours Completed</label>
type="number" <input
name="hours_completed" type="number"
value={hours_completed} name="hours_completed"
onChange={handleParentFieldChange} value={hours_completed}
placeholder="Credit hours done" onChange={handleParentFieldChange}
/> placeholder="Credit hours done"
className="w-full border rounded p-2"
/>
</div>
<label>Program Length</label> <div className="space-y-1">
{/* If user typed a custom => manualProgramLength, else autoProgramLength */} <label className="block font-medium">Program Length</label>
<input <input
type="number" type="number"
value={displayedProgramLength} value={displayedProgramLength}
onChange={handleManualProgramLengthChange} onChange={handleManualProgramLengthChange}
placeholder="Leave blank to use auto, or type an override" placeholder="Leave blank to use auto, or type an override"
/> className="w-full border rounded p-2"
/>
</div>
</> </>
)} )}
<label>Expected Graduation</label> <div className="space-y-1">
<input <label className="block font-medium">Expected Graduation</label>
type="date" <input
name="expected_graduation" type="date"
value={expected_graduation} name="expected_graduation"
onChange={handleParentFieldChange} value={expected_graduation}
/> onChange={handleParentFieldChange}
className="w-full border rounded p-2"
/>
</div>
<label>Loan Interest Rate (%)</label> <div className="space-y-1">
<input <label className="block font-medium">Loan Interest Rate (%)</label>
type="number" <input
name="interest_rate" type="number"
value={interest_rate} name="interest_rate"
onChange={handleParentFieldChange} value={interest_rate}
placeholder="e.g. 5.5" onChange={handleParentFieldChange}
/> placeholder="e.g. 5.5"
className="w-full border rounded p-2"
/>
</div>
<label>Loan Term (years)</label> <div className="space-y-1">
<input <label className="block font-medium">Loan Term (years)</label>
type="number" <input
name="loan_term" type="number"
value={loan_term} name="loan_term"
onChange={handleParentFieldChange} value={loan_term}
placeholder="e.g. 10" onChange={handleParentFieldChange}
/> placeholder="e.g. 10"
className="w-full border rounded p-2"
/>
</div>
<label>Extra Monthly Payment</label> <div className="space-y-1">
<input <label className="block font-medium">Extra Monthly Payment</label>
type="number" <input
name="extra_payment" type="number"
value={extra_payment} name="extra_payment"
onChange={handleParentFieldChange} value={extra_payment}
placeholder="Optional" onChange={handleParentFieldChange}
/> placeholder="Optional"
className="w-full border rounded p-2"
/>
</div>
<label>Expected Salary After Graduation</label> <div className="space-y-1">
<input <label className="block font-medium">Expected Salary After Graduation</label>
type="number" <input
name="expected_salary" type="number"
value={expected_salary} name="expected_salary"
onChange={handleParentFieldChange} value={expected_salary}
placeholder="e.g. 65000" onChange={handleParentFieldChange}
/> placeholder="e.g. 65000"
className="w-full border rounded p-2"
/>
</> </div>
</div>
) : ( ) : (
<p>Not currently enrolled or prospective student. Skipping college onboarding.</p> <p>Not currently enrolled or prospective student. Skipping college onboarding.</p>
)} )}
<button onClick={prevStep} style={{ marginRight: '1rem' }}> Previous</button> <div className="pt-4">
<button onClick={handleSubmit}>Finish Onboarding</button> <button
onClick={prevStep}
style={{ marginRight: '1rem' }}
className="bg-gray-200 hover:bg-gray-300 text-gray-700 font-semibold py-2 px-4 rounded"
>
Previous
</button>
<button
onClick={handleSubmit}
className="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded"
>
Finish Onboarding
</button>
</div>
</div> </div>
); );
} }

View File

@ -1,3 +1,4 @@
// FinancialOnboarding.js
import React from 'react'; import React from 'react';
const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = false }) => { const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = false }) => {
@ -13,7 +14,6 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = f
emergency_contribution = 0, emergency_contribution = 0,
extra_cash_emergency_pct = "", extra_cash_emergency_pct = "",
extra_cash_retirement_pct = "", extra_cash_retirement_pct = "",
planned_monthly_expenses = '', planned_monthly_expenses = '',
planned_monthly_debt_payments = '', planned_monthly_debt_payments = '',
planned_monthly_retirement_contribution = '', planned_monthly_retirement_contribution = '',
@ -22,7 +22,7 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = f
planned_surplus_retirement_pct = '', planned_surplus_retirement_pct = '',
planned_additional_income = '' planned_additional_income = ''
} = data; } = data;
const handleChange = (e) => { const handleChange = (e) => {
const { name, value } = e.target; const { name, value } = e.target;
let val = parseFloat(value) || 0; let val = parseFloat(value) || 0;
@ -42,172 +42,250 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = f
extra_cash_emergency_pct: 100 - val extra_cash_emergency_pct: 100 - val
})); }));
} else { } else {
setData(prevData => ({ setData(prevData => ({ ...prevData, [name]: val }));
...prevData,
[name]: val
}));
} }
}; };
return ( return (
<div> <div className="max-w-md mx-auto p-6 space-y-6">
<h2>Financial Details</h2> <h2 className="text-2xl font-semibold">Financial Details</h2>
{currently_working === 'yes' && ( {currently_working === 'yes' && (
<> <div className="space-y-4">
<input <div>
name="current_salary" <label className="block font-medium">Current Annual Salary</label>
type="number" <input
placeholder="Current Annual Salary" name="current_salary"
value={current_salary || ''} // controlled type="number"
onChange={handleChange} placeholder="Current Annual Salary"
/> value={current_salary || ''}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
<input <div>
name="additional_income" <label className="block font-medium">Additional Annual Income</label>
type="number" <input
placeholder="Additional Annual Income (Investments, annuitites, additional jobs, etc. - optional)" name="additional_income"
value={additional_income || ''} type="number"
onChange={handleChange} placeholder="Investments, side jobs, etc. (optional)"
/> value={additional_income || ''}
</> onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
</div>
)} )}
<input <div className="space-y-4">
name="monthly_expenses" <div>
type="number" <label className="block font-medium">Monthly Expenses</label>
placeholder="Monthly Expenses"
value={monthly_expenses || ''}
onChange={handleChange}
/>
<input
name="monthly_debt_payments"
type="number"
placeholder="Monthly Debt Payments (optional)"
value={monthly_debt_payments || ''}
onChange={handleChange}
/>
<input
name="retirement_savings"
type="number"
placeholder="Retirement Savings"
value={retirement_savings || ''}
onChange={handleChange}
/>
<input
name="retirement_contribution"
type="number"
placeholder="Monthly Retirement Contribution"
value={retirement_contribution || ''}
onChange={handleChange}
/>
<input
name="emergency_fund"
type="number"
placeholder="Emergency Fund Savings"
value={emergency_fund || ''}
onChange={handleChange}
/>
<input
name="emergency_contribution"
type="number"
placeholder="Monthly Emergency Fund Contribution (optional)"
value={emergency_contribution || ''}
onChange={handleChange}
/>
<h3>Extra Monthly Cash Allocation</h3>
<p>If you have extra money left each month after expenses, how would you like to allocate it? (Must add to 100%)</p>
<label>Extra Monthly Cash to Emergency Fund (%)</label>
<input
name="extra_cash_emergency_pct"
type="number"
placeholder="% to Emergency Savings (e.g., 30)"
value={extra_cash_emergency_pct}
onChange={handleChange}
/>
<label>Extra Monthly Cash to Retirement Fund (%)</label>
<input
name="extra_cash_retirement_pct"
type="number"
placeholder="% to Retirement Savings (e.g., 70)"
value={extra_cash_retirement_pct}
onChange={handleChange}
/>
{/* Only show the planned overrides if isEditMode is true */}
{isEditMode && (
<>
<hr />
<h2>Planned Scenario Overrides</h2>
<p>These fields let you override your real finances for this scenario.</p>
<label>Planned Monthly Expenses</label>
<input <input
name="monthly_expenses"
type="number" type="number"
name="planned_monthly_expenses" placeholder="Monthly Expenses"
value={planned_monthly_expenses} value={monthly_expenses || ''}
onChange={handleChange} onChange={handleChange}
className="w-full border rounded p-2"
/> />
</div>
<label>Planned Monthly Debt Payments</label> <div>
<label className="block font-medium">Monthly Debt Payments (optional)</label>
<input <input
name="monthly_debt_payments"
type="number" type="number"
name="planned_monthly_debt_payments" placeholder="Monthly Debt Payments"
value={planned_monthly_debt_payments} value={monthly_debt_payments || ''}
onChange={handleChange} onChange={handleChange}
className="w-full border rounded p-2"
/> />
</div>
<label>Planned Monthly Retirement Contribution</label> <div>
<label className="block font-medium">Retirement Savings</label>
<input <input
name="retirement_savings"
type="number" type="number"
name="planned_monthly_retirement_contribution" placeholder="Retirement Savings"
value={planned_monthly_retirement_contribution} value={retirement_savings || ''}
onChange={handleChange} onChange={handleChange}
className="w-full border rounded p-2"
/> />
</div>
<label>Planned Monthly Emergency Contribution</label> <div>
<label className="block font-medium">Monthly Retirement Contribution</label>
<input <input
name="retirement_contribution"
type="number" type="number"
name="planned_monthly_emergency_contribution" placeholder="Monthly Retirement Contribution"
value={planned_monthly_emergency_contribution} value={retirement_contribution || ''}
onChange={handleChange} onChange={handleChange}
className="w-full border rounded p-2"
/> />
</div>
<label>Planned Surplus % to Emergency</label> <div>
<label className="block font-medium">Emergency Fund Savings</label>
<input <input
name="emergency_fund"
type="number" type="number"
name="planned_surplus_emergency_pct" placeholder="Emergency Fund Savings"
value={planned_surplus_emergency_pct} value={emergency_fund || ''}
onChange={handleChange} onChange={handleChange}
className="w-full border rounded p-2"
/> />
</div>
<label>Planned Surplus % to Retirement</label> <div>
<label className="block font-medium">Monthly Emergency Fund Contribution</label>
<input <input
name="emergency_contribution"
type="number" type="number"
name="planned_surplus_retirement_pct" placeholder="Monthly Emergency Fund Contribution (optional)"
value={planned_surplus_retirement_pct} value={emergency_contribution || ''}
onChange={handleChange} onChange={handleChange}
className="w-full border rounded p-2"
/> />
</div>
</div>
<label>Planned Additional Annual Income</label> <div className="space-y-2">
<h3 className="text-lg font-medium">Extra Monthly Cash Allocation</h3>
<p className="text-gray-600">
If you have extra money left each month after expenses, how would you like to allocate it?
(Must add to 100%)
</p>
<div>
<label className="block font-medium">Extra Monthly Cash to Emergency Fund (%)</label>
<input <input
name="extra_cash_emergency_pct"
type="number" type="number"
name="planned_additional_income" placeholder="% to Emergency Savings (e.g., 30)"
value={planned_additional_income} value={extra_cash_emergency_pct}
onChange={handleChange} onChange={handleChange}
className="w-full border rounded p-2"
/> />
</> </div>
<div>
<label className="block font-medium">Extra Monthly Cash to Retirement Fund (%)</label>
<input
name="extra_cash_retirement_pct"
type="number"
placeholder="% to Retirement Savings (e.g., 70)"
value={extra_cash_retirement_pct}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
</div>
{/* Only show the planned overrides if isEditMode is true */}
{isEditMode && (
<div className="space-y-4">
<hr className="my-4" />
<h2 className="text-xl font-medium">Planned Scenario Overrides</h2>
<p className="text-gray-600">
These fields let you override your real finances for this scenario.
</p>
<div>
<label className="block font-medium">Planned Monthly Expenses</label>
<input
type="number"
name="planned_monthly_expenses"
value={planned_monthly_expenses}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Planned Monthly Debt Payments</label>
<input
type="number"
name="planned_monthly_debt_payments"
value={planned_monthly_debt_payments}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Planned Monthly Retirement Contribution</label>
<input
type="number"
name="planned_monthly_retirement_contribution"
value={planned_monthly_retirement_contribution}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Planned Monthly Emergency Contribution</label>
<input
type="number"
name="planned_monthly_emergency_contribution"
value={planned_monthly_emergency_contribution}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Planned Surplus % to Emergency</label>
<input
type="number"
name="planned_surplus_emergency_pct"
value={planned_surplus_emergency_pct}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Planned Surplus % to Retirement</label>
<input
type="number"
name="planned_surplus_retirement_pct"
value={planned_surplus_retirement_pct}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Planned Additional Annual Income</label>
<input
type="number"
name="planned_additional_income"
value={planned_additional_income}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
</div>
)} )}
<button onClick={prevStep}> Previous: Career</button>
<button onClick={nextStep}>Next: College </button> <div className="flex justify-between pt-4">
<button
onClick={prevStep}
className="bg-gray-200 hover:bg-gray-300 text-gray-700 font-semibold py-2 px-4 rounded"
>
Previous: Career
</button>
<button
onClick={nextStep}
className="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded"
>
Next: College
</button>
</div>
</div> </div>
); );
}; };

View File

@ -2,12 +2,17 @@
import React from 'react'; import React from 'react';
const PremiumWelcome = ({ nextStep }) => ( const PremiumWelcome = ({ nextStep }) => (
<div> <div className="max-w-md mx-auto text-center p-6">
<h2>Welcome to AptivaAI Premium!</h2> <h2 className="text-2xl font-semibold mb-4">Welcome to AptivaAI Premium!</h2>
<p> <p className="mb-6">
Let's get started by gathering some quick information to personalize your experience. Let's get started by gathering some quick information to personalize your experience.
</p> </p>
<button onClick={nextStep}>Get Started</button> <button
className="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded"
onClick={nextStep}
>
Get Started
</button>
</div> </div>
); );