diff --git a/nextjs-app/components/Navigation.tsx b/nextjs-app/components/Navigation.tsx index b0d9469..10ac348 100644 --- a/nextjs-app/components/Navigation.tsx +++ b/nextjs-app/components/Navigation.tsx @@ -48,7 +48,9 @@ export function Navigation() { > Compare {selectedSchools.length > 0 && ( - {selectedSchools.length} + + {selectedSchools.length} + )} void; isSelected: (urn: number) => boolean; canAddMore: boolean; + isInitialized: boolean; mutate: () => void; } diff --git a/nextjs-app/context/ComparisonProvider.tsx b/nextjs-app/context/ComparisonProvider.tsx index 99fe774..ea5cee5 100644 --- a/nextjs-app/context/ComparisonProvider.tsx +++ b/nextjs-app/context/ComparisonProvider.tsx @@ -1,18 +1,98 @@ /** * ComparisonProvider - * Provides comparison state to all components + * Provides shared comparison state to all components */ 'use client'; -import { useComparison } from '@/hooks/useComparison'; +import { useState, useEffect, useCallback } from 'react'; +import { getFromLocalStorage, setToLocalStorage } from '@/lib/utils'; +import type { School } from '@/lib/types'; import { ComparisonContext } from './ComparisonContext'; +const STORAGE_KEY = 'selectedSchools'; +const MAX_SCHOOLS = 5; + export function ComparisonProvider({ children }: { children: React.ReactNode }) { - const comparisonState = useComparison(); + const [selectedSchools, setSelectedSchools] = useState([]); + const [isInitialized, setIsInitialized] = useState(false); + + // Load from localStorage on mount + useEffect(() => { + const stored = getFromLocalStorage(STORAGE_KEY, []); + setSelectedSchools(stored); + setIsInitialized(true); + }, []); + + // Save to localStorage when schools change + useEffect(() => { + if (isInitialized) { + setToLocalStorage(STORAGE_KEY, selectedSchools); + } + }, [selectedSchools, isInitialized]); + + // Listen for storage changes from other tabs + useEffect(() => { + const handleStorageChange = (e: StorageEvent) => { + if (e.key === STORAGE_KEY && e.newValue) { + try { + const parsed = JSON.parse(e.newValue); + setSelectedSchools(parsed); + } catch { + // Ignore parse errors + } + } + }; + + window.addEventListener('storage', handleStorageChange); + return () => window.removeEventListener('storage', handleStorageChange); + }, []); + + const addSchool = useCallback((school: School) => { + setSelectedSchools((prev) => { + if (prev.some((s) => s.urn === school.urn)) { + return prev; + } + if (prev.length >= MAX_SCHOOLS) { + alert(`Maximum ${MAX_SCHOOLS} schools can be compared`); + return prev; + } + return [...prev, school]; + }); + }, []); + + const removeSchool = useCallback((urn: number) => { + setSelectedSchools((prev) => prev.filter((s) => s.urn !== urn)); + }, []); + + const clearAll = useCallback(() => { + setSelectedSchools([]); + }, []); + + const isSelected = useCallback( + (urn: number) => selectedSchools.some((s) => s.urn === urn), + [selectedSchools] + ); + + // Placeholder mutate - actual SWR mutate is in useComparison hook + const mutate = useCallback(() => {}, []); return ( - + {children} ); diff --git a/nextjs-app/hooks/useComparison.ts b/nextjs-app/hooks/useComparison.ts index b42392f..7a165bc 100644 --- a/nextjs-app/hooks/useComparison.ts +++ b/nextjs-app/hooks/useComparison.ts @@ -1,35 +1,25 @@ /** * Custom hook for managing school comparison state + * Uses shared context for real-time updates across components */ 'use client'; -import { useState, useEffect, useCallback } from 'react'; import useSWR from 'swr'; import { fetcher } from '@/lib/api'; -import { getFromLocalStorage, setToLocalStorage } from '@/lib/utils'; -import type { School, ComparisonResponse } from '@/lib/types'; - -const STORAGE_KEY = 'selectedSchools'; -const MAX_SCHOOLS = 5; +import { useComparisonContext } from '@/context/ComparisonContext'; +import type { ComparisonResponse } from '@/lib/types'; export function useComparison() { - const [selectedSchools, setSelectedSchools] = useState([]); - const [isInitialized, setIsInitialized] = useState(false); - - // Load from localStorage on mount - useEffect(() => { - const stored = getFromLocalStorage(STORAGE_KEY, []); - setSelectedSchools(stored); - setIsInitialized(true); - }, []); - - // Save to localStorage when schools change - useEffect(() => { - if (isInitialized) { - setToLocalStorage(STORAGE_KEY, selectedSchools); - } - }, [selectedSchools, isInitialized]); + const { + selectedSchools, + addSchool, + removeSchool, + clearAll, + isSelected, + canAddMore, + isInitialized, + } = useComparisonContext(); // Fetch comparison data for selected schools const urns = selectedSchools.map((s) => s.urn).join(','); @@ -38,40 +28,10 @@ export function useComparison() { fetcher, { revalidateOnFocus: false, - dedupingInterval: 10000, // 10 seconds + dedupingInterval: 10000, } ); - const addSchool = useCallback((school: School) => { - setSelectedSchools((prev) => { - // Check if already selected - if (prev.some((s) => s.urn === school.urn)) { - return prev; - } - - // Check max limit - if (prev.length >= MAX_SCHOOLS) { - alert(`Maximum ${MAX_SCHOOLS} schools can be compared`); - return prev; - } - - return [...prev, school]; - }); - }, []); - - const removeSchool = useCallback((urn: number) => { - setSelectedSchools((prev) => prev.filter((s) => s.urn !== urn)); - }, []); - - const clearAll = useCallback(() => { - setSelectedSchools([]); - }, []); - - const isSelected = useCallback( - (urn: number) => selectedSchools.some((s) => s.urn === urn), - [selectedSchools] - ); - return { selectedSchools, comparisonData: data?.comparison, @@ -81,7 +41,8 @@ export function useComparison() { removeSchool, clearAll, isSelected, - canAddMore: selectedSchools.length < MAX_SCHOOLS, + canAddMore, + isInitialized, mutate, }; }