// src/components/LoanRepaymentDrawer.js import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { X } from 'lucide-react'; import LoanRepayment from './LoanRepayment.js'; import { Button } from './ui/button.js'; import { getNearbySchools } from '../utils/getNearbySchools.js'; import UpsellSummary from './UpsellSummary.js'; import { useNavigate } from 'react-router-dom'; /* ───────────────────────── CONSTANTS ───────────────────────── */ const DEGREE_OPTS = [ "Associate's Degree", "Bachelor's Degree", "Master's Degree", 'Graduate / Professional Certificate', 'First Professional Degree', 'Doctoral Degree', ]; /* ──────────────────────── COMPONENT ────────────────────────── */ export default function LoanRepaymentDrawer({ open, onClose, schools, // comes from PreparingLanding setSchools, results, setResults, user, cipCodes = [], userZip = '', userState='', }) { /* Hooks must always run – return null later if !open */ /* ── Remote data for auto-suggest ─ */ const [cipData, setCipData] = useState([]); const [schoolSearch, setSchoolSearch] = useState(''); /* ── Simple form fields ─ */ const [degree, setDegree] = useState(''); const [tuition, setTuition] = useState(''); const [err, setErr] = useState(''); /* ── When “Continue” is pressed show true calculator ─ */ const [showCalc, setShowCalc] = useState(false); const navigate = useNavigate(); /* ▒▒▒ NEW auto-seed effect ▒▒▒ */ useEffect(() => { // run once every time the drawer opens if (!open) return; // drawer closed if (schools.length) return; // already have data if (!cipCodes.length) return; // no career → nothing to seed (async () => { try { const seed = await getNearbySchools(cipCodes, userZip, userState); if (seed.length) setSchools(seed); } catch (e) { console.warn('auto-seed schools failed:', e); } })(); }, [open, schools.length, cipCodes.join('-'), userZip, userState, setSchools]); /* ════════════════════════════════════════════════════ FETCH CIP DATA (only once the drawer is ever opened) ════════════════════════════════════════════════════ */ useEffect(() => { if (!open || cipData.length) return; fetch('/cip_institution_mapping_new.json') .then(r => r.text()) .then(text => text .split('\n') .map(l => { try { return JSON.parse(l); } catch { return null; } }) .filter(Boolean) ) .then(arr => setCipData(arr)) .catch(e => console.error('CIP fetch error', e)); }, [open, cipData.length]); /* ════════════════════════════════════════════════════ SCHOOL AUTOCOMPLETE LIST (memoised) ════════════════════════════════════════════════════ */ const suggestions = useMemo(() => { if (!schoolSearch.trim()) return []; const low = schoolSearch.toLowerCase(); const set = new Set( cipData .filter(r => r.INSTNM.toLowerCase().includes(low)) .map(r => r.INSTNM) ); return [...set].slice(0, 10); }, [schoolSearch, cipData]); /* ════════════════════════════════════════════════════ ESC --> close convenience ════════════════════════════════════════════════════ */ const escHandler = useCallback(e => { if (e.key === 'Escape') onClose(); }, [onClose]); useEffect(() => { if (open) window.addEventListener('keydown', escHandler); return () => window.removeEventListener('keydown', escHandler); }, [open, escHandler]); /* ════════════════════════════════════════════════════ HANDLE “CONTINUE” ════════════════════════════════════════════════════ */ const handleContinue = () => { // Tuition is the only truly required field if (!tuition.trim()) { setErr('Please enter an annual tuition estimate.'); return; } setErr(''); // Build a stub “school” object so LoanRepayment // can work with the same shape it expects const stub = { name : schoolSearch || 'Unknown School', degreeType : degree || 'Unspecified', programLength : 4, // sensible default inState : parseFloat(tuition), outOfState : parseFloat(tuition), inStateGraduate : parseFloat(tuition), outStateGraduate: parseFloat(tuition), }; setSchools([stub]); // overwrite – single-school calc setShowCalc(true); }; const showUpsell = user && !user.is_premium && !user.is_pro_premium; /* ════════════════════════════════════════════════════ RENDER ════════════════════════════════════════════════════ */ if (!open) return null; return (
Want to see how this loan fits into a full financial plan?
Premium subscribers can model salary growth, living costs, retirement goals, and more.
You’re on Premium — open the College Planning wizard to store this tuition in your plan (Profile ▸ Premium Onboarding ▸ College Details).
School | Monthly | Total Cost | Net Gain |
---|---|---|---|
{r.name} | ${r.totalMonthlyPayment} | ${r.totalLoanCost} | ${r.netGain} |