diff --git a/supabase/README.md b/supabase/README.md index d8eb45e..799c445 100644 --- a/supabase/README.md +++ b/supabase/README.md @@ -24,6 +24,7 @@ The full schema history for the project, in apply order: | `20260715010000_college_ingest_runs` | `college_ingest_runs` audit log for the Scorecard ingest. | | `20260717000000_college_student_body` | Adds `student_body` jsonb (diversity / retention / women / first-gen) to `colleges` for the Discover detail popup. | | `20260915000000_remove_paid_and_political_scholarships` | Archives nine curated scholarships: six that charge an application/entry fee, three political-advocacy essay contests. See `seed.sql`'s curation rules. | +| `20260930000000_college_aid_guarantees` | Adds `aid_guarantee*` columns to `colleges` (with a CHECK that the four text fields come together) and backfills 40 schools' income guarantees across 54 campus rows (Harvard, Go Blue Guarantee, Blue and Gold, …), checked 2026-09-30. The only source for guarantees; correct one with a new migration. | ### Notes diff --git a/supabase/migrations/20260930000000_college_aid_guarantees.sql b/supabase/migrations/20260930000000_college_aid_guarantees.sql new file mode 100644 index 0000000..c03cc98 --- /dev/null +++ b/supabase/migrations/20260930000000_college_aid_guarantees.sql @@ -0,0 +1,290 @@ +-- Income guarantees from each school's own financial aid program. +-- +-- A growing number of colleges promise free tuition (sometimes the full cost) +-- below a family income: Harvard under $200,000, Michigan's Go Blue Guarantee, +-- UC's Blue and Gold plan. Every one of them depends on the student finishing +-- that school's aid steps on time, so the app shows the guarantee next to the +-- school and turns it into a task on the school's application page. +-- +-- No public dataset carries these. The College Scorecard has nothing on them +-- and neither does the Common Data Set, so each row below was read off the +-- school's own site and checked on 2026-09-30. Which schools need the CSS +-- Profile was checked against College Board's 2027–28 participating list. +-- Thresholds that change for students entering in fall 2027 carry the fall 2027 +-- figure: that is this year's seniors. +-- +-- One guarantee per school, so plain columns rather than a side table. All are +-- null for a school without one, and null means "none we know of", never "this +-- school offers nothing". +-- +-- From here on this table is the only source: the app reads guarantees from it +-- (and copies the short form onto each saved school for its task list). To +-- change one, write a new migration with the corrected row and checked date. + +alter table public.colleges + add column if not exists aid_guarantee text, + add column if not exists aid_guarantee_residents text, + add column if not exists aid_guarantee_needs_css boolean, + add column if not exists aid_guarantee_detail text, + add column if not exists aid_guarantee_action text, + add column if not exists aid_guarantee_url text, + add column if not exists aid_guarantee_checked_on date; + +comment on column public.colleges.aid_guarantee is + 'Name or one-line summary of the school''s income guarantee (e.g. ''Go Blue Guarantee''). Null means none we know of.'; +comment on column public.colleges.aid_guarantee_residents is + 'Who the guarantee is limited to (e.g. ''Michigan residents''). Null when it is open to every U.S. student.'; +comment on column public.colleges.aid_guarantee_needs_css is + 'True when getting the guarantee takes the CSS Profile. Drives the CSS task on public schools, which otherwise only private ones get.'; +comment on column public.colleges.aid_guarantee_detail is + 'Who qualifies and what is covered, in plain language.'; +comment on column public.colleges.aid_guarantee_action is + 'What the student must file, and by when, to get it.'; +comment on column public.colleges.aid_guarantee_url is + 'The school''s own page for the guarantee. The source for the row.'; +comment on column public.colleges.aid_guarantee_checked_on is + 'When the row was last checked against aid_guarantee_url. These change most years.'; + +-- Backfill. Keyed on scorecard_id. Rows were found by website domain and then +-- checked by hand: satellite campuses a program covers are in (all six Ohio +-- State campuses, Emory's Oxford College), while graduate schools and online +-- divisions that share a parent's domain are out (Teachers College, Weill +-- Cornell, Texas A&M Health Science Center, UF Online, Notre Dame's second +-- non-degree row). +with guarantees(scorecard_id, aid_guarantee, aid_guarantee_residents, aid_guarantee_needs_css, + aid_guarantee_detail, aid_guarantee_action, aid_guarantee_url) as (values + (166027, 'Free tuition under $200K', null, true, + 'Tuition is covered for families earning $200,000 or less. Under $100,000, families pay nothing at all.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://college.harvard.edu/financial-aid'), + (166683, 'Free tuition under $200K', null, true, + 'Tuition is covered for families earning under $200,000. Under $100,000, families pay nothing at all.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://news.mit.edu/2024/mit-tuition-undergraduates-family-income-1120'), + (186131, 'Free tuition under $250K', null, false, + 'Most families earning up to $250,000 pay no tuition. Up to $150,000, most pay nothing at all.', + 'File the FAFSA and Princeton’s own financial aid application.', + 'https://www.princeton.edu/news/2025/08/07/princeton-enhances-financial-aid-again-it-welcomes-class-2029-which-includes'), + (130794, 'Free tuition under $200K', null, true, + 'From 2026–27, tuition is covered for families earning under $200,000. Under $100,000, families pay nothing at all.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://news.yale.edu/2026/01/27/yale-offer-free-tuition-families-incomes-below-200000'), + (215062, 'Quaker Commitment', null, true, + 'Full tuition for most families earning up to $200,000. Under $75,000, most get the full cost covered.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://www.upenn.edu/pennforward/penn-quaker-commitment'), + (190150, 'Free tuition under $150K', null, true, + 'Columbia College and Columbia Engineering are tuition-free for families earning under $150,000. Under $66,000, parents pay nothing.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://cc-seas.financialaid.columbia.edu/how/aid/works'), + (217156, 'Free tuition under $125K', null, true, + 'Full tuition is covered for families earning $125,000 or less. Under $60,000, all direct costs are covered.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://www.brown.edu/news/2026-02-09/fy27-tuition-fees-salaries'), + (182670, 'Free tuition under $175K', null, true, + 'Families earning under $125,000 pay no parent contribution. From $125,000 to $175,000, tuition is covered.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://financialaid.dartmouth.edu/how-aid-works/how-much-help-will-i-get'), + (190415, 'Free tuition under $125K', null, true, + 'Tuition is covered for families earning up to $125,000. Up to $75,000, the full cost is covered.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://news.cornell.edu/stories/2026/03/board-trustees-approves-2026-27-budget-parameters'), + (243744, 'Free tuition under $150K', null, true, + 'Tuition is free for families earning under $150,000. Under $100,000, tuition, housing and food are free.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://admission.stanford.edu/afford/'), + (110404, 'Free tuition under $200K', null, true, + 'Tuition is covered for families earning under $200,000. Under $100,000, the full cost is covered.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://www.finaid.caltech.edu/'), + (162928, 'Free tuition under $200K', null, true, + 'From 2026–27, tuition is free for families earning up to $200,000. Up to $100,000, families pay nothing.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://sfs.jhu.edu/tuition-promise/'), + (147767, 'Pay nothing under $70K', null, true, + 'Most families earning under $70,000 attend at no cost. Northwestern is fundraising to make it tuition-free under $150,000, but that is a goal, not yet a promise.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://undergradaid.northwestern.edu/'), + (144050, 'Free tuition under $250K', null, false, + 'For students starting in fall 2027, tuition is free for families earning up to $250,000. Up to $125,000, tuition, housing, meals and fees are covered.', + 'File the FAFSA and the school’s required aid forms by its deadline. Its aid page lists them.', + 'https://www.uchicago.edu/admissions/affordability'), + (221999, 'Free tuition under $200K', null, true, + 'Opportunity Vanderbilt covers full tuition for families earning $200,000 or less, starting with students who enter in fall 2027.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://admissions.vanderbilt.edu/affordability/opportunity-vanderbilt/'), + (227757, 'Free tuition under $200K', null, true, + 'For students entering in fall 2027, tuition is free for families earning up to $200,000. Under $100,000, tuition, fees, housing and food are covered.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://financialaid.rice.edu/rice-investment'), + (487092, 'Free tuition under $200K', null, true, + 'From fall 2026, Emory Advantage Plus makes Emory tuition-free for U.S. students from families earning $200,000 or less.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://studentaid.emory.edu/undergraduate/types/grants-scholarships/emory-grants/emory-advantage.html'), + (139658, 'Free tuition under $200K', null, true, + 'From fall 2026, Emory Advantage Plus makes Emory tuition-free for U.S. students from families earning $200,000 or less.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://studentaid.emory.edu/undergraduate/types/grants-scholarships/emory-grants/emory-advantage.html'), + (168148, 'Free tuition under $150K', null, true, + 'From fall 2026, the Tufts Tuition Pact makes Tufts tuition-free for U.S. families earning less than $150,000.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://now.tufts.edu/2025/09/09/tufts-will-be-tuition-free-us-families-earning-less-150000'), + (152080, 'Free tuition under $150K', null, true, + 'From 2026–27, tuition is covered for families earning under $150,000, and half of tuition under $200,000.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://financialaid.nd.edu/costs-and-affordability/'), + (164988, 'BU Promise', null, true, + 'From 2026–27, full tuition is covered for families earning under $200,000, with the parent share capped at $20,000 a year. Families earning $75,000 or less pay nothing.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://www.bu.edu/articles/2026/new-financial-aid-program-covers-tuition-for-certain-families/'), + (123961, 'Free tuition under $80K', null, true, + 'Tuition is free for students who start as first-years from families earning $80,000 or less.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://affordability.usc.edu/'), + (190099, 'Free tuition under $175K', null, true, + 'From fall 2026, the Colgate Commitment makes tuition free for families earning up to $175,000, with no loans up to $200,000.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://www.colgate.edu/admission-aid/financial-aid/colgate-commitment'), + (234207, 'Free tuition under $200K', null, true, + 'The W&L Promise makes tuition free for families earning under $200,000, starting with students who enter in fall 2027. Under $75,000, housing and food are covered too.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://www.wlu.edu/admissions/financial-aid/the-w-l-promise'), + (164465, 'Free tuition under $141K', null, true, + 'Most students from families earning under $141,000 receive a scholarship covering tuition. Under $67,500, most get tuition, housing and meals covered.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://www.amherst.edu/admission/financial_aid'), + (198419, 'Free tuition under $150K', 'North and South Carolina residents', true, + 'Full tuition for North and South Carolina residents from families earning $150,000 or less. Up to $65,000, housing and meals are covered too.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://financialaid.duke.edu/initiative-students-carolinas/'), + (199847, 'Free tuition under $200K', 'North Carolina residents', true, + 'From fall 2026, North Carolina students from families earning $200,000 or less attend tuition-free. At $100,000 or less, housing and food are covered too. From $200,001 to $300,000, half of tuition is covered.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://financialaid.wfu.edu/types-of-aid/ncgateway-to-wfu/'), + (179867, 'WashU Pledge', 'Missouri and southern Illinois students', true, + 'Tuition, fees, housing and food for first-year students from Missouri and southern Illinois whose families earn $75,000 or less.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://financialaid.washu.edu/how-washu-helps/washu-pledge/'), + (170976, 'Go Blue Guarantee', 'Michigan residents', true, + 'Free tuition for Michigan residents with family income and assets of $125,000 or less.', + 'No separate application, but file the FAFSA and the CSS Profile by March 31.', + 'https://finaid.umich.edu/apply-aid/new-undergraduates/michigan-residents/go-blue-guarantee-eligibility'), + (110635, 'Blue and Gold Opportunity Plan', 'California residents', false, + 'Most California families earning up to $100,000 get UC tuition covered.', + 'No separate application. File the FAFSA or the California Dream Act Application by March 2.', + 'https://admission.universityofcalifornia.edu/tuition-financial-aid/types-of-aid/blue-and-gold-opportunity-plan.html'), + (110662, 'Blue and Gold Opportunity Plan', 'California residents', false, + 'Most California families earning up to $100,000 get UC tuition covered.', + 'No separate application. File the FAFSA or the California Dream Act Application by March 2.', + 'https://admission.universityofcalifornia.edu/tuition-financial-aid/types-of-aid/blue-and-gold-opportunity-plan.html'), + (110680, 'Blue and Gold Opportunity Plan', 'California residents', false, + 'Most California families earning up to $100,000 get UC tuition covered.', + 'No separate application. File the FAFSA or the California Dream Act Application by March 2.', + 'https://admission.universityofcalifornia.edu/tuition-financial-aid/types-of-aid/blue-and-gold-opportunity-plan.html'), + (110653, 'Blue and Gold Opportunity Plan', 'California residents', false, + 'Most California families earning up to $100,000 get UC tuition covered.', + 'No separate application. File the FAFSA or the California Dream Act Application by March 2.', + 'https://admission.universityofcalifornia.edu/tuition-financial-aid/types-of-aid/blue-and-gold-opportunity-plan.html'), + (110644, 'Blue and Gold Opportunity Plan', 'California residents', false, + 'Most California families earning up to $100,000 get UC tuition covered.', + 'No separate application. File the FAFSA or the California Dream Act Application by March 2.', + 'https://admission.universityofcalifornia.edu/tuition-financial-aid/types-of-aid/blue-and-gold-opportunity-plan.html'), + (110705, 'Blue and Gold Opportunity Plan', 'California residents', false, + 'Most California families earning up to $100,000 get UC tuition covered.', + 'No separate application. File the FAFSA or the California Dream Act Application by March 2.', + 'https://admission.universityofcalifornia.edu/tuition-financial-aid/types-of-aid/blue-and-gold-opportunity-plan.html'), + (110714, 'Blue and Gold Opportunity Plan', 'California residents', false, + 'Most California families earning up to $100,000 get UC tuition covered.', + 'No separate application. File the FAFSA or the California Dream Act Application by March 2.', + 'https://admission.universityofcalifornia.edu/tuition-financial-aid/types-of-aid/blue-and-gold-opportunity-plan.html'), + (110671, 'Blue and Gold Opportunity Plan', 'California residents', false, + 'Most California families earning up to $100,000 get UC tuition covered.', + 'No separate application. File the FAFSA or the California Dream Act Application by March 2.', + 'https://admission.universityofcalifornia.edu/tuition-financial-aid/types-of-aid/blue-and-gold-opportunity-plan.html'), + (445188, 'Blue and Gold Opportunity Plan', 'California residents', false, + 'Most California families earning up to $100,000 get UC tuition covered.', + 'No separate application. File the FAFSA or the California Dream Act Application by March 2.', + 'https://admission.universityofcalifornia.edu/tuition-financial-aid/types-of-aid/blue-and-gold-opportunity-plan.html'), + (228778, 'Texas Advance Commitment', 'Texas residents', false, + 'Full tuition for Texas residents with family income up to $100,000, and help with tuition up to $125,000.', + 'File the FAFSA or TASFA by UT’s priority deadline.', + 'https://admissions.utexas.edu/cost-aid/financial-aid/texas-advance-commitment/'), + (228723, 'Aggie Assurance', 'Texas residents', false, + 'From fall 2026, free tuition and fees for Texas residents with family income and assets under $100,000.', + 'File the FAFSA or TASFA by the state priority deadline.', + 'https://stories.tamu.edu/news/2026/02/20/texas-am-university-reinforces-commitment-to-affordable-education-with-expanded-aid/'), + (234076, 'AccessUVA', 'Virginia residents', true, + 'Grants cover at least tuition and fees for Virginia families earning $100,000 or less. At $50,000 or less, housing and dining are covered too.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://news.virginia.edu/content/uva-expands-financial-aid-program-virginia-families'), + (199120, 'Free tuition under $80K', 'North Carolina residents', true, + 'Tuition and mandatory fees are covered for in-state students from families earning less than $80,000.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://studentaid.unc.edu/tar-heel-guarantee/'), + (145637, 'Illinois Commitment', 'Illinois residents', false, + 'Free tuition and campus fees for Illinois residents with family income and assets of $75,000 or less each.', + 'File the FAFSA before March 15.', + 'https://www.osfa.illinois.edu/illinois-commitment/'), + (240444, 'Bucky’s Tuition Promise', 'Wisconsin residents', false, + 'Free tuition and fees for Wisconsin residents with a household income (AGI) of $65,000 or less. Assets are not counted.', + 'No separate application. File the FAFSA by the December 1 priority deadline.', + 'https://financialaid.wisc.edu/types-of-aid/tuition-promise/'), + (204680, 'Buckeye Opportunity Program', 'Ohio residents', false, + 'Tuition and mandatory fees are covered for Ohio residents who qualify for a Pell Grant.', + 'File the FAFSA by Ohio State’s Feb 1 priority date, every year.', + 'https://news.osu.edu/buckeye-opportunity-program-helps-keep-college-affordable-at-ohio-state/'), + (204796, 'Buckeye Opportunity Program', 'Ohio residents', false, + 'Tuition and mandatory fees are covered for Ohio residents who qualify for a Pell Grant.', + 'File the FAFSA by Ohio State’s Feb 1 priority date, every year.', + 'https://news.osu.edu/buckeye-opportunity-program-helps-keep-college-affordable-at-ohio-state/'), + (204662, 'Buckeye Opportunity Program', 'Ohio residents', false, + 'Tuition and mandatory fees are covered for Ohio residents who qualify for a Pell Grant.', + 'File the FAFSA by Ohio State’s Feb 1 priority date, every year.', + 'https://news.osu.edu/buckeye-opportunity-program-helps-keep-college-affordable-at-ohio-state/'), + (204671, 'Buckeye Opportunity Program', 'Ohio residents', false, + 'Tuition and mandatory fees are covered for Ohio residents who qualify for a Pell Grant.', + 'File the FAFSA by Ohio State’s Feb 1 priority date, every year.', + 'https://news.osu.edu/buckeye-opportunity-program-helps-keep-college-affordable-at-ohio-state/'), + (204699, 'Buckeye Opportunity Program', 'Ohio residents', false, + 'Tuition and mandatory fees are covered for Ohio residents who qualify for a Pell Grant.', + 'File the FAFSA by Ohio State’s Feb 1 priority date, every year.', + 'https://news.osu.edu/buckeye-opportunity-program-helps-keep-college-affordable-at-ohio-state/'), + (204705, 'Buckeye Opportunity Program', 'Ohio residents', false, + 'Tuition and mandatory fees are covered for Ohio residents who qualify for a Pell Grant.', + 'File the FAFSA by Ohio State’s Feb 1 priority date, every year.', + 'https://news.osu.edu/buckeye-opportunity-program-helps-keep-college-affordable-at-ohio-state/'), + (163286, 'Terrapin Commitment', 'Maryland residents', false, + 'Tuition and fees are covered for Maryland residents who are Pell-eligible or from families earning $75,000 or less.', + 'File the FAFSA by March 1, every year.', + 'https://financialaid.umd.edu/terrapin-commitment'), + (139755, 'Tech Promise', 'Georgia residents', true, + 'A debt-free degree for Georgia residents from families earning under $55,500.', + 'File the FAFSA and the CSS Profile by the school’s financial aid deadline.', + 'https://www.techpromise.em.gatech.edu/'), + (236948, 'Husky Promise', 'Washington residents', false, + 'Full tuition and standard fees for Washington residents who qualify for a Pell Grant.', + 'File the FAFSA or WASFA by UW’s priority date.', + 'https://www.washington.edu/huskypromise/'), + (134130, 'Machen Florida Opportunity Scholars', null, false, + 'Full need met with grants and scholarships for first-generation students with family income under $55,000 and assets under $35,000.', + 'File the FAFSA by UF’s priority deadline.', + 'https://www.sfa.ufl.edu/mfos') +) +update public.colleges c set + aid_guarantee = v.aid_guarantee, + aid_guarantee_residents = v.aid_guarantee_residents, + aid_guarantee_needs_css = v.aid_guarantee_needs_css, + aid_guarantee_detail = v.aid_guarantee_detail, + aid_guarantee_action = v.aid_guarantee_action, + aid_guarantee_url = v.aid_guarantee_url, + aid_guarantee_checked_on = date '2026-09-30' +from guarantees v +where c.scorecard_id = v.scorecard_id; + +-- A guarantee is the four text fields together or none of them. A partial row +-- would show a guarantee with no steps to get it, so it is refused at the +-- database rather than hidden in the app. Residents may be null on its own. +alter table public.colleges drop constraint if exists colleges_aid_guarantee_complete; +alter table public.colleges add constraint colleges_aid_guarantee_complete check ( + num_nulls(aid_guarantee, aid_guarantee_detail, aid_guarantee_action, aid_guarantee_url) in (0, 4) +); diff --git a/web/src/components/AidGuideSections.tsx b/web/src/components/AidGuideSections.tsx new file mode 100644 index 0000000..84bed4f --- /dev/null +++ b/web/src/components/AidGuideSections.tsx @@ -0,0 +1,268 @@ +import { useEffect, useState, type ReactNode } from 'react' +import { C, MODULE_COLORS } from '../lib/designTokens' +import { fetchAllGuarantees, type AidGuarantee, type GuaranteeList } from '../lib/aidColleges' +import { formatCollegeDate, parseIsoDay } from '../data/applicationDeadlines' +import { SecLabel } from './moduleUI' +import { + AID_FACTS_AS_OF, + guaranteeLabel, + MAJOR_PROGRAMS, + SCHOLARSHIP_CATEGORIES, + STATE_GRANTS, + type AidProgram, + type GrantState, +} from '../data/aidPrograms' + +const MC = MODULE_COLORS.financialAid + +/** + * One color per source of money, carried through a row's icon, edge, figures + * and links: federal and national programs blue, the school itself amber (the + * row not to skip), the state green, scholarships purple. + */ +// Line icons in the module's own stroke style, drawn in the row's color. +const svg = (children: ReactNode) => ( + +) +const ICONS = { + // Columned building: federal and national programs. + big: svg(<>), + // Mortarboard: the school's own aid. + uni: svg(<>), + // Map pin: the student's state. + state: svg(<>), + // Rosette: scholarships. + types: svg(<>), +} + +const ROWS = { + big: { color: '#1D7FC4', icon: ICONS.big }, + uni: { color: '#C47A12', icon: ICONS.uni }, + state: { color: MC, icon: ICONS.state }, + types: { color: '#7048C8', icon: ICONS.types }, +} as const + +/** Each kind of scholarship gets its own chip, so the five read apart at a glance. */ +const CATEGORY_COLOR: Record = { + merit: '#7048C8', + need: '#1D7FC4', + institutional: '#C47A12', + national: MC, + specialized: '#3F5BA9', +} + +const SANS = "'Outfit',sans-serif" + +export type AidGuideDestination = 'fafsa' | 'deadlines' | 'aid-compare' | 'scholarship-search' + +const Chevron = () => ( + +) + +const ExtLink = ({ href, children, color = MC }: { href: string; children: ReactNode; color?: string }) => ( + + {children} ↗ + +) + +/** One entry inside an open row: name and figure on top, the explanation under it. */ +const Item = ({ title, figure, children, links, color = MC, chip }: { + title: string + figure?: string + children: ReactNode + links?: ReactNode + color?: string + /** Render the title as a colored chip instead of bold text. */ + chip?: boolean +}) => ( +
+
+
+ {chip + ? {title} + : {title}} + {figure && · {figure}} +
+
{children}
+
+ {links &&
{links}
} +
+) + +const ProgramItem = ({ p, color }: { p: AidProgram; color: string }) => ( + {l.label}) : Site} + > + {p.summary}{p.note && <> {p.note}} + +) + +const GuaranteeItem = ({ g }: { g: AidGuarantee }) => ( + Aid page}> + {g.detail} To get it: {g.action} + +) + +/** "Edvifi can ___" — one line at the foot of a row, pointing at the tool that does it. */ +const EdvifiCan = ({ can, cta, onClick, color = MC }: { can: string; cta: string; onClick: () => void; color?: string }) => ( + // A quiet tinted strip rather than a banner: it should read as the next step, + // not as an ad, but still be the first thing the eye lands on at the bottom. +
+ + + Edvifi can {can} + + +
+) + +/** A collapsible row in the same shape as the checklist sections below it. */ +const Row = ({ kind, title, summary, tag, open, onToggle, children }: { + kind: keyof typeof ROWS + title: string + summary: string + tag?: string + open: boolean + onToggle: () => void + children: ReactNode +}) => { + const { color, icon } = ROWS[kind] + const [hover, setHover] = useState(false) + const edge = open || hover ? `${color}45` : C.border + return ( + // Longhands only. Mixing the `border` shorthand with `borderLeft` lets React + // re-apply the shorthand on toggle and wipe the colored edge. +
+ + {open &&
{children}
} +
+ ) +} + +const Intro = ({ children }: { children: ReactNode }) => ( +

{children}

+) + +export type GuideRowId = 'big' | 'uni' | 'state' | 'types' + +interface Props { + /** The student's state, when their ZIP tells us. Puts their grant first. */ + homeState: GrantState | null + /** Guarantees at schools the student has added, in their list order. */ + yourSchools: AidGuarantee[] + /** The list lookup failed, so "none on your list" would be a guess. */ + listFailed: boolean + /** Which row is open. Held by the caller so it survives opening an article and coming back. */ + open: GuideRowId | null + onOpenChange: (row: GuideRowId | null) => void + onGoTo: (dest: AidGuideDestination) => void +} + +export default function AidGuideSections({ homeState, yourSchools, listFailed, open, onOpenChange, onGoTo }: Props) { + // The full list is only needed once the row is opened. + const [all, setAll] = useState(null) + const toggle = (id: GuideRowId) => { + // Reopening after a failed load tries again. + if (id === 'uni' && open !== 'uni' && all === 'failed') setAll(null) + onOpenChange(open === id ? null : id) + } + useEffect(() => { + if (open !== 'uni' || all !== null) return + let cancelled = false + fetchAllGuarantees() + .then((list) => { if (!cancelled) setAll(list) }) + .catch(() => { if (!cancelled) setAll('failed') }) + return () => { cancelled = true } + }, [open, all]) + + const homeGrant = STATE_GRANTS.find((g) => g.state === homeState) + const stateGrants = homeGrant ? [homeGrant, ...STATE_GRANTS.filter((g) => g !== homeGrant)] : STATE_GRANTS + const n = yourSchools.length + const checked = all && all !== 'failed' && all.checkedOn ? parseIsoDay(all.checkedOn) : null + + return ( +
+ Know your aid + + toggle('big')}> + {MAJOR_PROGRAMS.map((p) => )} + onGoTo('fafsa')} /> + + + 0 + ? `${n} ${n === 1 ? 'school' : 'schools'} on your list ${n === 1 ? 'has an aid guarantee' : 'have aid guarantees'}. Check whether you qualify` + : 'Many schools guarantee free tuition below a family income, if you file their forms'} + tag="Don’t skip" + open={open === 'uni'} + onToggle={() => toggle('uni')} + > + + Guarantees only apply if you finish that school’s aid steps on time. That can mean the CSS Profile, the + school’s own form, or the FAFSA by an early date. Check the aid page of every school on your list. + + {yourSchools.map((g) => )} +
+ + {n > 0 ? 'All' : 'See all'} {all && all !== 'failed' ? `${all.guarantees.length} ` : ''}schools with a guarantee + + {all === null && Loading…} + {all === 'failed' && Couldn’t load the full list just now.} + {all && all !== 'failed' && ( + <> + {all.guarantees.map((g) => )} + {checked &&

Checked against each school’s site on {formatCollegeDate(checked)}.

} + + )} +
+ onGoTo('aid-compare')} /> +
+ + toggle('state')} + > + Most states have aid for residents who stay in state. If yours isn’t here, search “[your state] higher education grant”. + {stateGrants.map((g) => )} + onGoTo('deadlines')} /> + + + toggle('types')}> + {SCHOLARSHIP_CATEGORIES.map((c) => {c.description} {c.example}.)} + onGoTo('scholarship-search')} /> + + +

+ Amounts are for {AID_FACTS_AS_OF} and change every year. Confirm on the official site. +

+
+ ) +} diff --git a/web/src/components/CollegeDiscoverTab.test.tsx b/web/src/components/CollegeDiscoverTab.test.tsx index 724f2e5..136b635 100644 --- a/web/src/components/CollegeDiscoverTab.test.tsx +++ b/web/src/components/CollegeDiscoverTab.test.tsx @@ -23,6 +23,7 @@ const H = vi.hoisted(() => { legacy_slug: null, emoji: null, early_action: null, early_decision: null, regular_decision: null, fafsa_priority: null, css_profile: null, aid_notification: null, meets_full_need: null, no_loan_policy: null, curated_at: null, + aid_guarantee: null, aid_guarantee_detail: null, aid_guarantee_needs_css: null, aid_guarantee_residents: null, aid_guarantee_action: null, aid_guarantee_url: null, aid_guarantee_checked_on: null, application_fee_cents: null, fee_waiver_policy: null, fees_verified_at: null, ...over, }) diff --git a/web/src/components/Dashboard.tsx b/web/src/components/Dashboard.tsx index bf4f583..4a9987c 100644 --- a/web/src/components/Dashboard.tsx +++ b/web/src/components/Dashboard.tsx @@ -17,6 +17,7 @@ import { reportError } from '../lib/errorTracking' import { resolveDeadlinePreferences } from '../lib/preferences' import WeekOverview from './WeekOverview' import DeadlinePanel from './DeadlinePanel' +import OverviewRail, { RailToggle } from './OverviewRail' import { nextDueForModule, type DeadlineEvent, @@ -270,6 +271,14 @@ export default function Dashboard({ startIdx, answers, firstName, onSignOut }: P onOpenDay={(day) => { setOpenModule(null); setCalendarDay(day); setPage('calendar') }} /> ) + + // The same rail inside each module's Overview, sharing this page's open state. + const moduleRail = ( + setSidebarOpen((o) => !o)}> + {deadlineRail} + + ) + const nextDueByModule = useMemo(() => { // A module card's "next due": not something already ticked off, and not a // date the student put on one of its tasks (those are steps toward a @@ -577,19 +586,11 @@ export default function Dashboard({ startIdx, answers, firstName, onSignOut }: P {/* Sidebar toggle — fixed on the border */} - + /> {/* Right sidebar — collapsible */}