861 lines
31 KiB
JavaScript
861 lines
31 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
||
import Modal from '../../components/ui/modal.js';
|
||
import FinancialAidWizard from '../../components/FinancialAidWizard.js';
|
||
import { useLocation } from 'react-router-dom';
|
||
|
||
const Req = () => <span className="text-red-600 ml-0.5">*</span>;
|
||
|
||
function CollegeOnboarding({ nextStep, prevStep, data, setData }) {
|
||
// CIP / iPEDS local states
|
||
const [schoolData, setSchoolData] = useState([]);
|
||
const [icTuitionData, setIcTuitionData] = useState([]);
|
||
const [schoolSuggestions, setSchoolSuggestions] = useState([]);
|
||
const [programSuggestions, setProgramSuggestions] = useState([]);
|
||
const [availableProgramTypes, setAvailableProgramTypes] = useState([]);
|
||
const [schoolValid, setSchoolValid] = useState(false);
|
||
const [programValid, setProgramValid] = useState(false);
|
||
const [enrollmentDate, setEnrollmentDate] = useState(
|
||
data.enrollment_date || '' // carry forward if the user goes back
|
||
);
|
||
const [expectedGraduation, setExpectedGraduation] = useState(data.expected_graduation || '');
|
||
|
||
|
||
|
||
// Show/hide the financial aid wizard
|
||
const [showAidWizard, setShowAidWizard] = useState(false);
|
||
|
||
|
||
const location = useLocation();
|
||
const navSelectedSchoolRaw = location.state?.selectedSchool;
|
||
const navSelectedSchool = toSchoolName(navSelectedSchoolRaw);
|
||
|
||
|
||
function dehydrate(schObj) {
|
||
if (!schObj || typeof schObj !== 'object') return null;
|
||
/* keep only the fields you really need */
|
||
const { INSTNM, CIPDESC, CREDDESC, ...rest } = schObj;
|
||
return { INSTNM, CIPDESC, CREDDESC, ...rest };
|
||
}
|
||
|
||
const [selectedSchool, setSelectedSchool] = useState(() =>
|
||
dehydrate(navSelectedSchool) ||
|
||
dehydrate(JSON.parse(localStorage.getItem('premiumOnboardingState') || '{}'
|
||
).collegeData?.selectedSchool)
|
||
);
|
||
|
||
function toSchoolName(objOrStr) {
|
||
if (!objOrStr) return '';
|
||
if (typeof objOrStr === 'object') return objOrStr.INSTNM || '';
|
||
return objOrStr; // already a string
|
||
}
|
||
|
||
|
||
const infoIcon = (msg) => (
|
||
<span
|
||
className="ml-1 inline-flex h-4 w-4 items-center justify-center rounded-full bg-blue-500 text-white text-xs cursor-help"
|
||
title={msg}
|
||
>
|
||
i
|
||
</span>
|
||
);
|
||
|
||
// Destructure parent data
|
||
const {
|
||
college_enrollment_status = '',
|
||
selected_school = selectedSchool,
|
||
selected_program = '',
|
||
program_type = '',
|
||
academic_calendar = 'semester',
|
||
annual_financial_aid = '',
|
||
is_online = false,
|
||
existing_college_debt = '',
|
||
enrollment_date = '',
|
||
expected_graduation = '',
|
||
interest_rate = 5.5,
|
||
loan_term = 10,
|
||
extra_payment = '',
|
||
expected_salary = '',
|
||
is_in_state = false,
|
||
is_in_district = false,
|
||
loan_deferral_until_graduation = false,
|
||
credit_hours_per_year = '',
|
||
hours_completed = '',
|
||
credit_hours_required = '',
|
||
tuition_paid = '',
|
||
} = data;
|
||
|
||
// Local states for auto/manual logic on tuition & program length
|
||
const [manualTuition, setManualTuition] = useState('');
|
||
const [autoTuition, setAutoTuition] = useState(0);
|
||
const [manualProgramLength, setManualProgramLength] = useState('');
|
||
const [autoProgramLength, setAutoProgramLength] = useState(0);
|
||
|
||
const inSchool = ['currently_enrolled','prospective_student']
|
||
.includes(college_enrollment_status);
|
||
|
||
useEffect(() => {
|
||
if (selectedSchool) {
|
||
setData(prev => ({
|
||
...prev,
|
||
selected_school : selectedSchool.INSTNM,
|
||
selected_program: selectedSchool.CIPDESC || prev.selected_program,
|
||
program_type : selectedSchool.CREDDESC || prev.program_type
|
||
}));
|
||
}
|
||
}, [selectedSchool, setData]);
|
||
|
||
|
||
useEffect(() => {
|
||
if (data.expected_graduation && !expectedGraduation)
|
||
setExpectedGraduation(data.expected_graduation);
|
||
}, [data.expected_graduation]);
|
||
|
||
/**
|
||
* handleParentFieldChange
|
||
* If user leaves numeric fields blank, store '' in local state, not 0.
|
||
* Only parseFloat if there's an actual numeric value.
|
||
*/
|
||
const handleParentFieldChange = (e) => {
|
||
const { name, value, type, checked } = e.target;
|
||
let val = value;
|
||
|
||
if (type === 'checkbox') {
|
||
val = checked;
|
||
setData(prev => ({ ...prev, [name]: val }));
|
||
return;
|
||
}
|
||
|
||
// If the user typed an empty string, store '' so they can see it's blank
|
||
if (val.trim() === '') {
|
||
setData(prev => ({ ...prev, [name]: '' }));
|
||
return;
|
||
}
|
||
|
||
// Otherwise, parse it if it's one of the numeric fields
|
||
if (['interest_rate', 'loan_term', 'extra_payment', 'expected_salary'].includes(name)) {
|
||
const parsed = parseFloat(val);
|
||
// If parse fails => store '' (or fallback to old value)
|
||
if (isNaN(parsed)) {
|
||
setData(prev => ({ ...prev, [name]: '' }));
|
||
} else {
|
||
setData(prev => ({ ...prev, [name]: parsed }));
|
||
}
|
||
} else if ([
|
||
'annual_financial_aid','existing_college_debt','credit_hours_per_year',
|
||
'hours_completed','credit_hours_required','tuition_paid'
|
||
].includes(name)) {
|
||
const parsed = parseFloat(val);
|
||
setData(prev => ({ ...prev, [name]: isNaN(parsed) ? '' : parsed }));
|
||
} else {
|
||
// For non-numeric or strings
|
||
setData(prev => ({ ...prev, [name]: val }));
|
||
}
|
||
};
|
||
|
||
const handleManualTuitionChange = (e) => {
|
||
setManualTuition(e.target.value);
|
||
};
|
||
|
||
const handleManualProgramLengthChange = (e) => {
|
||
setManualProgramLength(e.target.value);
|
||
};
|
||
|
||
// CIP data
|
||
useEffect(() => {
|
||
async function fetchCipData() {
|
||
try {
|
||
const res = await fetch('/cip_institution_mapping_new.json');
|
||
const text = await res.text();
|
||
const lines = text.split('\n');
|
||
const parsed = lines.map(line => {
|
||
try { return JSON.parse(line); } catch { return null; }
|
||
}).filter(Boolean);
|
||
setSchoolData(parsed);
|
||
} catch (err) {
|
||
console.error("Failed to load CIP data:", err);
|
||
}
|
||
}
|
||
fetchCipData();
|
||
}, []);
|
||
|
||
// iPEDS data
|
||
useEffect(() => {
|
||
async function fetchIpedsData() {
|
||
try {
|
||
const res = await fetch('/ic2023_ay.csv');
|
||
const text = await res.text();
|
||
const rows = text.split('\n').map(line => line.split(','));
|
||
const headers = rows[0];
|
||
const dataRows = rows.slice(1).map(row =>
|
||
Object.fromEntries(row.map((val, idx) => [headers[idx], val]))
|
||
);
|
||
setIcTuitionData(dataRows);
|
||
} catch (err) {
|
||
console.error("Failed to load iPEDS data:", err);
|
||
}
|
||
}
|
||
fetchIpedsData();
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
if (college_enrollment_status !== 'prospective_student') return;
|
||
|
||
const lenYears = Number(data.program_length || '');
|
||
if (!enrollmentDate || !lenYears) return;
|
||
|
||
const start = new Date(enrollmentDate);
|
||
const est = new Date(start.getFullYear() + lenYears, start.getMonth(), start.getDate());
|
||
const iso = firstOfNextMonth(est);
|
||
|
||
setExpectedGraduation(iso);
|
||
setData(prev => ({ ...prev, expected_graduation: iso }));
|
||
}, [college_enrollment_status, enrollmentDate, data.program_length, setData]);
|
||
|
||
// School Name
|
||
const handleSchoolChange = (eOrVal) => {
|
||
const value =
|
||
typeof eOrVal === 'string' ? eOrVal : eOrVal?.target?.value || '';
|
||
setData(prev => ({
|
||
...prev,
|
||
selected_school: value,
|
||
selected_program: '',
|
||
program_type: '',
|
||
credit_hours_required: '',
|
||
}));
|
||
const filtered = schoolData.filter(s =>
|
||
s.INSTNM.toLowerCase().includes(value.toLowerCase())
|
||
);
|
||
const uniqueSchools = [...new Set(filtered.map(s => s.INSTNM))];
|
||
setSchoolSuggestions(uniqueSchools.slice(0, 10));
|
||
setProgramSuggestions([]);
|
||
setAvailableProgramTypes([]);
|
||
};
|
||
|
||
const handleSchoolSelect = (schoolName) => {
|
||
setData(prev => ({
|
||
...prev,
|
||
selected_school: schoolName,
|
||
selected_program: '',
|
||
program_type: '',
|
||
credit_hours_required: '',
|
||
}));
|
||
setSchoolSuggestions([]);
|
||
setProgramSuggestions([]);
|
||
setAvailableProgramTypes([]);
|
||
};
|
||
|
||
// Program
|
||
const handleProgramChange = (e) => {
|
||
const value = e.target.value;
|
||
setData(prev => ({ ...prev, selected_program: value }));
|
||
if (!value) {
|
||
setProgramSuggestions([]);
|
||
return;
|
||
}
|
||
const filtered = schoolData.filter(
|
||
s => s.INSTNM.toLowerCase() === selected_school.toLowerCase() &&
|
||
s.CIPDESC.toLowerCase().includes(value.toLowerCase())
|
||
);
|
||
const uniquePrograms = [...new Set(filtered.map(s => s.CIPDESC))];
|
||
setProgramSuggestions(uniquePrograms.slice(0, 10));
|
||
};
|
||
|
||
const handleProgramSelect = (prog) => {
|
||
setData(prev => ({ ...prev, selected_program: prog }));
|
||
setProgramSuggestions([]);
|
||
};
|
||
|
||
const handleProgramTypeSelect = (e) => {
|
||
const val = e.target.value;
|
||
setData(prev => ({
|
||
...prev,
|
||
program_type: val,
|
||
credit_hours_required: '',
|
||
}));
|
||
setManualProgramLength('');
|
||
setAutoProgramLength('0.00');
|
||
};
|
||
|
||
// once we have school+program => load possible program types
|
||
useEffect(() => {
|
||
if (!selected_program || !selected_school || !schoolData.length) return;
|
||
const possibleTypes = schoolData
|
||
.filter(
|
||
s => s.INSTNM.toLowerCase() === selected_school.toLowerCase() &&
|
||
s.CIPDESC === selected_program
|
||
)
|
||
.map(s => s.CREDDESC);
|
||
setAvailableProgramTypes([...new Set(possibleTypes)]);
|
||
}, [selected_program, selected_school, schoolData]);
|
||
|
||
// auto-calc tuition
|
||
useEffect(() => {
|
||
if (!icTuitionData.length) return;
|
||
if (!selected_school || !program_type || !credit_hours_per_year) return;
|
||
|
||
const found = schoolData.find(
|
||
s => s.INSTNM.toLowerCase() === selected_school.toLowerCase()
|
||
);
|
||
if (!found) return;
|
||
|
||
const unitId = found.UNITID;
|
||
if (!unitId) return;
|
||
|
||
const match = icTuitionData.find(row => row.UNITID === unitId);
|
||
if (!match) return;
|
||
|
||
const isGradOrProf = [
|
||
"Master's Degree",
|
||
"Doctoral Degree",
|
||
"Graduate/Professional Certificate",
|
||
"First Professional Degree"
|
||
].includes(program_type);
|
||
|
||
let partTimeRate = 0;
|
||
let fullTimeTuition = 0;
|
||
if (isGradOrProf) {
|
||
if (is_in_district) {
|
||
partTimeRate = parseFloat(match.HRCHG5 || 0);
|
||
fullTimeTuition = parseFloat(match.TUITION5 || 0);
|
||
} else if (is_in_state) {
|
||
partTimeRate = parseFloat(match.HRCHG6 || 0);
|
||
fullTimeTuition = parseFloat(match.TUITION6 || 0);
|
||
} else {
|
||
partTimeRate = parseFloat(match.HRCHG7 || 0);
|
||
fullTimeTuition = parseFloat(match.TUITION7 || 0);
|
||
}
|
||
} else {
|
||
// undergrad
|
||
if (is_in_district) {
|
||
partTimeRate = parseFloat(match.HRCHG1 || 0);
|
||
fullTimeTuition = parseFloat(match.TUITION1 || 0);
|
||
} else if (is_in_state) {
|
||
partTimeRate = parseFloat(match.HRCHG2 || 0);
|
||
fullTimeTuition = parseFloat(match.TUITION2 || 0);
|
||
} else {
|
||
partTimeRate = parseFloat(match.HRCHG3 || 0);
|
||
fullTimeTuition = parseFloat(match.TUITION3 || 0);
|
||
}
|
||
}
|
||
|
||
const chpy = parseFloat(credit_hours_per_year) || 0;
|
||
let estimate = 0;
|
||
if (chpy < 24 && partTimeRate) {
|
||
estimate = partTimeRate * chpy;
|
||
} else {
|
||
estimate = fullTimeTuition;
|
||
}
|
||
|
||
setAutoTuition(Math.round(estimate));
|
||
}, [
|
||
icTuitionData, selected_school, program_type,
|
||
credit_hours_per_year, is_in_state, is_in_district, schoolData
|
||
]);
|
||
|
||
// auto-calc program length
|
||
useEffect(() => {
|
||
if (!program_type || !credit_hours_per_year) return;
|
||
|
||
const completed = parseInt(hours_completed, 10) || 0;
|
||
const perYear = parseFloat(credit_hours_per_year) || 1;
|
||
|
||
let required = 0;
|
||
switch (program_type) {
|
||
case "Associate's Degree": required = 60; break;
|
||
case "Bachelor's Degree": required = 120; break;
|
||
case "Master's Degree": required = 180; break;
|
||
case "Doctoral Degree": required = 240; break;
|
||
case "First Professional Degree": required = 180; break;
|
||
case "Graduate/Professional Certificate":
|
||
required = parseInt(credit_hours_required, 10) || 0;
|
||
break;
|
||
case "Undergraduate Certificate or Diploma":
|
||
required = parseInt(credit_hours_required, 10) || 30; // sensible default
|
||
break;
|
||
default:
|
||
required = parseInt(credit_hours_required, 10) || 0;
|
||
}
|
||
|
||
/* never negative */
|
||
const remain = Math.max(0, required - completed);
|
||
const yrs = remain / perYear;
|
||
|
||
setAutoProgramLength(parseFloat(yrs.toFixed(2)));
|
||
}, [
|
||
program_type,
|
||
hours_completed,
|
||
credit_hours_per_year,
|
||
credit_hours_required,
|
||
]);
|
||
|
||
/* ------------------------------------------------------------------ */
|
||
/* Whenever the user changes enrollmentDate OR programLength */
|
||
/* (program_length is already in parent data), compute grad date. */
|
||
/* ------------------------------------------------------------------ */
|
||
useEffect(() => {
|
||
/* decide which “length” the user is looking at right now */
|
||
const lenRaw =
|
||
manualProgramLength.trim() !== ''
|
||
? manualProgramLength
|
||
: autoProgramLength;
|
||
|
||
const len = parseFloat(lenRaw); // years (may be fractional)
|
||
const startISO = pickStartDate(); // '' or yyyy‑mm‑dd
|
||
if (!startISO || !len) return; // nothing to do yet
|
||
|
||
const start = new Date(startISO);
|
||
/* naïve add – assuming program_length is years; *
|
||
* adjust if you store months instead */
|
||
/* 1 year = 12 months ‑‑ preserve fractions (e.g. 1.75 y = 21 m) */
|
||
const monthsToAdd = Math.round(len * 12);
|
||
|
||
const estGrad = new Date(start); // clone
|
||
estGrad.setMonth(estGrad.getMonth() + monthsToAdd);
|
||
|
||
const gradISO = firstOfNextMonth(estGrad);
|
||
|
||
setExpectedGraduation(gradISO);
|
||
setData(prev => ({ ...prev, expected_graduation: gradISO }));
|
||
}, [college_enrollment_status,
|
||
enrollmentDate,
|
||
manualProgramLength,
|
||
autoProgramLength,
|
||
setData]);
|
||
|
||
|
||
// final handleSubmit => we store chosen tuition + program_length, then move on
|
||
const handleSubmit = () => {
|
||
const chosenTuition = manualTuition.trim() === ''
|
||
? autoTuition
|
||
: parseFloat(manualTuition);
|
||
const chosenProgramLength = manualProgramLength.trim() === ''
|
||
? autoProgramLength
|
||
: manualProgramLength;
|
||
|
||
setData(prev => ({
|
||
...prev,
|
||
interest_rate,
|
||
loan_term,
|
||
tuition: chosenTuition,
|
||
program_length: chosenProgramLength
|
||
}));
|
||
|
||
nextStep();
|
||
};
|
||
|
||
// displayedTuition / displayedProgramLength
|
||
const displayedTuition = (manualTuition.trim() === '' ? autoTuition : manualTuition);
|
||
const displayedProgramLength = (manualProgramLength.trim() === '' ? autoProgramLength : manualProgramLength);
|
||
|
||
function pickStartDate() {
|
||
if (college_enrollment_status === 'prospective_student') {
|
||
return enrollmentDate; // may still be ''
|
||
}
|
||
if (college_enrollment_status === 'currently_enrolled') {
|
||
return firstOfNextMonth(new Date()); // today → 1st next month
|
||
}
|
||
return ''; // anybody else
|
||
}
|
||
|
||
function firstOfNextMonth(dateObj) {
|
||
return new Date(dateObj.getFullYear(), dateObj.getMonth() + 1, 1)
|
||
.toISOString()
|
||
.slice(0, 10); // yyyy‑mm‑dd
|
||
}
|
||
|
||
const ready =
|
||
(!inSchool || expectedGraduation) && // grad date iff in school
|
||
selected_school && program_type;
|
||
|
||
return (
|
||
<div className="max-w-md mx-auto p-6 space-y-4">
|
||
<h2 className="text-2xl font-semibold">College Details</h2>
|
||
|
||
{(college_enrollment_status === 'currently_enrolled' ||
|
||
college_enrollment_status === 'prospective_student') ? (
|
||
<div className="space-y-4">
|
||
{/* In District, In State, Online, etc. */}
|
||
<div className="flex items-center space-x-2">
|
||
<input
|
||
type="checkbox"
|
||
name="is_in_district"
|
||
checked={is_in_district}
|
||
onChange={handleParentFieldChange}
|
||
className="h-4 w-4"
|
||
/>
|
||
<label className="font-medium">In District? {infoIcon("Used by Community Colleges usually - local discounts")}</label>
|
||
</div>
|
||
|
||
<div className="flex items-center space-x-2">
|
||
<input
|
||
type="checkbox"
|
||
name="is_in_state"
|
||
checked={is_in_state}
|
||
onChange={handleParentFieldChange}
|
||
className="h-4 w-4"
|
||
/>
|
||
<label className="font-medium">In State Tuition? {infoIcon("Students can qualify for discounted tuition if they are a resident of the same state as the college - private institutions rarely have this discounts")}</label>
|
||
</div>
|
||
|
||
<div className="flex items-center space-x-2">
|
||
<input
|
||
type="checkbox"
|
||
name="is_online"
|
||
checked={is_online}
|
||
onChange={handleParentFieldChange}
|
||
className="h-4 w-4"
|
||
/>
|
||
<label className="font-medium">Program is Fully Online {infoIcon("Tuition rates for fully online programs are usually different than traditional")}</label>
|
||
</div>
|
||
|
||
<div className="flex items-center space-x-2">
|
||
<input
|
||
type="checkbox"
|
||
name="loan_deferral_until_graduation"
|
||
checked={loan_deferral_until_graduation}
|
||
onChange={handleParentFieldChange}
|
||
className="h-4 w-4"
|
||
/>
|
||
<label className="font-medium">Defer Loan Payments until Graduation? {infoIcon("You can delay paying tuition loans while still in college, but they will still accrue interest during this time")}</label>
|
||
</div>
|
||
|
||
{/* School */}
|
||
<div className="space-y-1">
|
||
<label className="block font-medium">School Name* (Please select from drop-down after typing){infoIcon("Start typing and click from auto-suggest")}</label>
|
||
<input
|
||
name="selected_school"
|
||
value={selected_school}
|
||
onChange={handleSchoolChange}
|
||
onBlur={() => {
|
||
const ok = schoolData.some(
|
||
s => s.INSTNM.toLowerCase() === selected_school.toLowerCase()
|
||
);
|
||
setSchoolValid(ok);
|
||
if (!ok) alert("Please pick a school from the list.");
|
||
}}
|
||
list="school-suggestions"
|
||
className={`w-full border rounded p-2 ${schoolValid ? '' : 'border-red-500'}`}
|
||
placeholder="Start typing and choose…"
|
||
/>
|
||
<datalist id="school-suggestions">
|
||
{schoolSuggestions.map((sch, idx) => (
|
||
<option
|
||
key={idx}
|
||
value={sch}
|
||
onClick={() => handleSchoolSelect(sch)}
|
||
/>
|
||
))}
|
||
</datalist>
|
||
</div>
|
||
|
||
<div className="space-y-1">
|
||
<label className="block font-medium">Marjor/Program Name* (Please select from drop-down after typing){infoIcon("Search and click from auto-suggest. If for some reason your major isn't listed, please send us a note.")}</label>
|
||
<input
|
||
name="selected_program"
|
||
value={selected_program}
|
||
onChange={handleProgramChange}
|
||
onBlur={() => {
|
||
const ok =
|
||
selected_school && // need a school first
|
||
schoolData.some(
|
||
s =>
|
||
s.INSTNM.toLowerCase() === selected_school.toLowerCase() &&
|
||
s.CIPDESC.toLowerCase() === selected_program.toLowerCase()
|
||
);
|
||
setProgramValid(ok);
|
||
if (!ok) alert("Please pick a program from the list.");
|
||
}}
|
||
list="program-suggestions"
|
||
className={`w-full border rounded p-2 ${programValid ? '' : 'border-red-500'}`}
|
||
placeholder="Start typing and choose…"
|
||
/>
|
||
<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">Degree Type* {infoIcon("What level of degree are you/will you be persuing?")}</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>
|
||
|
||
{/* Academic Calendar */}
|
||
<div className="space-y-1">
|
||
<label className="block font-medium">Academic Calendar {infoIcon("What annual calendar type does the college operate under - pick semester if you are unsure.")}</label>
|
||
<select
|
||
name="academic_calendar"
|
||
value={academic_calendar}
|
||
onChange={handleParentFieldChange}
|
||
className="w-full border rounded p-2"
|
||
>
|
||
<option value="semester">Semester</option>
|
||
<option value="quarter">Quarter</option>
|
||
<option value="trimester">Trimester</option>
|
||
<option value="other">Other</option>
|
||
</select>
|
||
</div>
|
||
|
||
{/* If Grad/Professional => credit_hours_required */}
|
||
{(program_type === 'Graduate/Professional Certificate' ||
|
||
program_type === 'First Professional Degree' ||
|
||
program_type === 'Doctoral Degree' ||
|
||
program_type === 'Undergraduate Certificate or Diploma'
|
||
) && (
|
||
<div className="space-y-1">
|
||
<label className="block font-medium">Credit Hours Required {infoIcon("Some Certificate, Graduate, and Professional Programs differ on number of hours required.")}</label>
|
||
<input
|
||
type="number"
|
||
name="credit_hours_required"
|
||
value={credit_hours_required}
|
||
onChange={handleParentFieldChange}
|
||
placeholder="e.g. 120"
|
||
className="w-full border rounded p-2"
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
<div className="space-y-1">
|
||
<label className="block font-medium">Credit Hours Per Year {infoIcon("How many hours do you plan to take each year? 24 is usually considered full-time, but to finish a Bachelor's in 4 you need to take 30.")}</label>
|
||
<input
|
||
type="number"
|
||
name="credit_hours_per_year"
|
||
value={credit_hours_per_year}
|
||
onChange={handleParentFieldChange}
|
||
placeholder="e.g. 24"
|
||
className="w-full border rounded p-2"
|
||
/>
|
||
</div>
|
||
|
||
{/* Tuition (auto or override) */}
|
||
<div className="space-y-1">
|
||
<label className="block font-medium">Yearly Tuition</label>
|
||
<input
|
||
type="number"
|
||
value={displayedTuition}
|
||
onChange={handleManualTuitionChange}
|
||
placeholder="Leave blank to use auto-calculated, or type an override"
|
||
className="w-full border rounded p-2"
|
||
/>
|
||
</div>
|
||
|
||
{/* Annual Financial Aid */}
|
||
<div className="space-y-1">
|
||
<label className="block font-medium">(Estimated) Annual Financial Aid {infoIcon("You usually won't know this exact value until right before you start college. Use estimates in AptivaAI.")}</label>
|
||
<div className="flex space-x-2">
|
||
<input
|
||
type="number"
|
||
name="annual_financial_aid"
|
||
value={annual_financial_aid}
|
||
onChange={handleParentFieldChange}
|
||
placeholder="e.g. 2000"
|
||
className="w-full border rounded p-2"
|
||
/>
|
||
<button
|
||
type="button"
|
||
onClick={() => setShowAidWizard(true)}
|
||
className="bg-blue-600 text-center px-3 py-2 rounded text-white"
|
||
>
|
||
Need Help?
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-1">
|
||
<label className="block font-medium">Existing College Loan Debt {infoIcon("If you have existing student loans, enter the value here. Estimates are just fine, but detailed forecasts require detailed inputs.")}</label>
|
||
<input
|
||
type="number"
|
||
name="existing_college_debt"
|
||
value={existing_college_debt}
|
||
onChange={handleParentFieldChange}
|
||
placeholder="e.g. 2000"
|
||
className="w-full border rounded p-2"
|
||
/>
|
||
</div>
|
||
|
||
{/* Show Program Length for both "currently_enrolled" & "prospective_student" */}
|
||
{(college_enrollment_status === 'currently_enrolled' ||
|
||
college_enrollment_status === 'prospective_student') && (
|
||
<div className="space-y-1">
|
||
<label className="block font-medium">Program Length {infoIcon("How many years it will take you to complete the degree/program given the number of hours you plan to take")}</label>
|
||
<input
|
||
type="number"
|
||
value={displayedProgramLength}
|
||
onChange={handleManualProgramLengthChange}
|
||
placeholder="Leave blank to use auto, or type an override"
|
||
className="w-full border rounded p-2"
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{/* If currently_enrolled => hours_completed */}
|
||
{college_enrollment_status === 'currently_enrolled' && (
|
||
<div className="space-y-1">
|
||
<label className="block font-medium">
|
||
Completed Credit Hours (that count towards program completion) {infoIcon("If you have already completed some college credits that apply to the selected Program above, enter them here. Bachelor's = 120")}
|
||
</label>
|
||
<input
|
||
type="number"
|
||
name="hours_completed"
|
||
value={hours_completed}
|
||
onChange={handleParentFieldChange}
|
||
placeholder="Credit hours done"
|
||
className="w-full border rounded p-2"
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{['currently_enrolled','prospective_student'].includes(college_enrollment_status) && (
|
||
<>
|
||
{/* A) Enrollment date – prospective only */}
|
||
{college_enrollment_status === 'prospective_student' && (
|
||
<div className="space-y-2">
|
||
<label className="block font-medium">
|
||
Anticipated Enrollment Date <Req />
|
||
</label>
|
||
<input
|
||
type="date"
|
||
value={enrollmentDate}
|
||
onChange={e => {
|
||
setEnrollmentDate(e.target.value);
|
||
setData(p => ({ ...p, enrollment_date: e.target.value }));
|
||
}}
|
||
className="w-full border rounded p-2"
|
||
required
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{/* B) Expected graduation – always editable */}
|
||
<div className="space-y-2">
|
||
<label className="block font-medium">
|
||
Expected Graduation Date <Req />
|
||
{college_enrollment_status === 'prospective_student' &&
|
||
enrollmentDate && data.program_length && (
|
||
<span
|
||
className="ml-1 cursor-help text-blue-600"
|
||
title="Automatically estimated from your enrollment date and program length. Adjust if needed—actual calendars vary by institution."
|
||
>ⓘ</span>
|
||
)}
|
||
</label>
|
||
|
||
<input
|
||
type="date"
|
||
value={expectedGraduation}
|
||
onChange={e => {
|
||
setExpectedGraduation(e.target.value);
|
||
setData(p => ({ ...p, expected_graduation: e.target.value }));
|
||
}}
|
||
className="w-full border rounded p-2"
|
||
required
|
||
/>
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
<div className="space-y-1">
|
||
<label className="block font-medium">Loan Interest Rate (%) {infoIcon("These can vary, enter the best blended rate you can approximate if you have multiple.")}</label>
|
||
<input
|
||
type="number"
|
||
name="interest_rate"
|
||
value={interest_rate}
|
||
onChange={handleParentFieldChange}
|
||
placeholder="e.g. 5.5"
|
||
className="w-full border rounded p-2"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-1">
|
||
<label className="block font-medium">Loan Term (years) {infoIcon("Education loans typically have a 10-year payback period, but can vary.")}</label>
|
||
<input
|
||
type="number"
|
||
name="loan_term"
|
||
value={loan_term}
|
||
onChange={handleParentFieldChange}
|
||
placeholder="e.g. 10"
|
||
className="w-full border rounded p-2"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-1">
|
||
<label className="block font-medium">Extra Monthly Payment {infoIcon("Extra money you plan to pay towards the loans each month.")}</label>
|
||
<input
|
||
type="number"
|
||
name="extra_payment"
|
||
value={extra_payment}
|
||
onChange={handleParentFieldChange}
|
||
placeholder="Optional"
|
||
className="w-full border rounded p-2"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-1">
|
||
<label className="block font-medium">Expected Salary After Graduation {infoIcon("If you're just starting out, expect towards the 10th percentile values for your targeted career. Can be found using Career Explorer if needed or input later.")}</label>
|
||
<input
|
||
type="number"
|
||
name="expected_salary"
|
||
value={expected_salary}
|
||
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>
|
||
)}
|
||
|
||
<div className="pt-4">
|
||
<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}
|
||
disabled={!ready}
|
||
className={`py-2 px-4 rounded font-semibold
|
||
${ready
|
||
? 'bg-blue-500 hover:bg-blue-600 text-white'
|
||
: 'bg-gray-300 text-gray-500 cursor-not-allowed'}`}
|
||
>
|
||
Finish Onboarding
|
||
</button>
|
||
</div>
|
||
|
||
{showAidWizard && (
|
||
<Modal onClose={() => setShowAidWizard(false)}>
|
||
<FinancialAidWizard
|
||
onAidEstimated={(estimate) => {
|
||
setData(prev => ({
|
||
...prev,
|
||
annual_financial_aid: estimate
|
||
}));
|
||
}}
|
||
onClose={() => setShowAidWizard(false)}
|
||
/>
|
||
</Modal>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default CollegeOnboarding;
|