Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ VITE_LOGODEV_TOKEN=
# CAPTCHA off is harmless; enabling CAPTCHA in Supabase without this key makes
# every auth call fail.
VITE_TURNSTILE_SITE_KEY=

# PostHog error tracking. Unset means errors stay in the console.
VITE_POSTHOG_KEY=
VITE_POSTHOG_HOST=https://us.i.posthog.com
133 changes: 131 additions & 2 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@supabase/supabase-js": "^2.101.1",
"d3-geo": "^3.1.1",
"framer-motion": "^12.38.0",
"posthog-js": "^1.434.2",
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
Expand Down
13 changes: 10 additions & 3 deletions web/src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useToast } from '../contexts/ToastContext'
import { updateProfile, markIntroSeen } from '../lib/profiles'
import { yearGroupOf } from '../data/timelineData'
import { useDeadlineEvents } from '../lib/useDeadlineEvents'
import { reportError } from '../lib/errorTracking'
import { resolveDeadlinePreferences } from '../lib/preferences'
import WeekOverview from './WeekOverview'
import DeadlinePanel from './DeadlinePanel'
Expand Down Expand Up @@ -40,7 +41,10 @@ class ModuleErrorBoundary extends Component<
> {
state: { error: Error | null } = { error: null }
static getDerivedStateFromError(error: Error) { return { error } }
componentDidCatch(error: Error, info: ErrorInfo) { console.error('Module crash:', error, info) }
componentDidCatch(error: Error, info: ErrorInfo) {
console.error('Module crash:', error, info)
reportError(error, { kind: 'module-boundary', componentStack: info.componentStack ?? '' })
}
render() {
if (this.state.error) {
return (
Expand Down Expand Up @@ -174,11 +178,14 @@ export default function Dashboard({ startIdx, answers, firstName, onSignOut }: P
}
await refreshProfile()
} catch {
// Re-open editor so user can retry
// Re-open the editor so the student can retry — and say why it reopened.
// On its own the field just springs back open, which reads as the app
// rejecting what they typed rather than failing to save it.
setEditingField(field)
toast.error("Couldn't save that — check your connection and try again.")
}
setSaving(false)
}, [editingField, user, profile, demo, profileDraft, refreshProfile])
}, [editingField, user, profile, demo, profileDraft, refreshProfile, toast])

const navigateFromDropdown = (p: string) => {
setPage(p as typeof page)
Expand Down
21 changes: 18 additions & 3 deletions web/src/components/FinancialAidModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2505,8 +2505,12 @@ interface Props {
export default function FinancialAidModule({ open, onClose, year = 11 }: Props) {
const { user, profile, refreshProfile } = useAuth()
const toast = useToast()
// Held in a ref so the load effect below re-runs only when the module
// opens — not whenever a context re-render changes this object.
const toastRef = useRef(toast)
const tourSeen = profile?.settings?.intros_seen?.includes('fafsa-module-tour') ?? false
const [showTour, setShowTour] = useState(false)
useEffect(() => { toastRef.current = toast }, [toast])

useEffect(() => {
if (open && !tourSeen) {
Expand Down Expand Up @@ -2564,21 +2568,32 @@ export default function FinancialAidModule({ open, onClose, year = 11 }: Props)
})
})

// One message for the three of them. Each failing alone leaves a different
// part of the module quietly wrong — a tracked scholarship showing "+
// Track", a saved college missing from the aid comparison, a cost estimate
// gone — and three separate toasts for one dropped connection is noise.
let loadFailed = false
const noteFailure = () => {
if (cancelled || loadFailed) return
loadFailed = true
toastRef.current.error("Some of your saved work didn't load — reload before adding anything, or you may end up with duplicates.")
}

getTrackerItems()
.then((items) => {
if (!cancelled) {
setTrackerScholarshipIds(new Set(items.filter((i) => i.scholarshipId).map((i) => i.scholarshipId!)))
}
})
.catch(() => {})
.catch(noteFailure)

getCollegeList()
.then((ids) => { if (!cancelled) setCollegeIds(ids) })
.catch(() => {})
.catch(noteFailure)

getNpcRuns()
.then((runs) => { if (!cancelled) setNpcRuns(runs) })
.catch(() => {})
.catch(noteFailure)

return () => {
cancelled = true
Expand Down
Loading
Loading