diff --git a/src/components/App/content.tsx b/src/components/App/content.tsx index 1292a3af..66592193 100644 --- a/src/components/App/content.tsx +++ b/src/components/App/content.tsx @@ -16,6 +16,7 @@ import { } from './navigation'; import { classes } from '../../utils/misc'; import { AccountContextValue } from '../../contexts/account'; +import { Term } from '../../types'; /** * Renders the actual content at the root of the app @@ -85,7 +86,7 @@ export type AppSkeletonProps = { children: React.ReactNode; accountState?: AccountContextValue; termsState?: { - terms: string[]; + terms: Term[]; currentTerm: string; onChangeTerm: (next: string) => void; }; diff --git a/src/components/AppDataLoader/stages.tsx b/src/components/AppDataLoader/stages.tsx index 9fdd11c9..a371d749 100644 --- a/src/components/AppDataLoader/stages.tsx +++ b/src/components/AppDataLoader/stages.tsx @@ -4,7 +4,7 @@ import { Immutable, Draft, castDraft } from 'immer'; import { Oscar } from '../../data/beans'; import useDownloadOscarData from '../../data/hooks/useDownloadOscarData'; import useDownloadTerms from '../../data/hooks/useDownloadTerms'; -import { NonEmptyArray } from '../../types'; +import { NonEmptyArray, Term } from '../../types'; import LoadingDisplay from '../LoadingDisplay'; import { SkeletonContent, AppSkeleton, AppSkeletonProps } from '../App/content'; import { @@ -78,7 +78,7 @@ export function StageLoadUIState({ export type StageEnsureValidTermProps = { skeletonProps?: StageSkeletonProps; - terms: NonEmptyArray; + terms: NonEmptyArray; currentTermRaw: string; setTerm: (next: string) => void; children: (props: { currentTerm: string }) => React.ReactNode; @@ -343,7 +343,7 @@ export function StageCreateScheduleDataProducer({ export type StageLoadTermsProps = { skeletonProps?: StageSkeletonProps; - children: (props: { terms: NonEmptyArray }) => React.ReactNode; + children: (props: { terms: NonEmptyArray }) => React.ReactNode; }; /** diff --git a/src/components/HeaderDisplay/index.tsx b/src/components/HeaderDisplay/index.tsx index 39d2eec5..9578374e 100644 --- a/src/components/HeaderDisplay/index.tsx +++ b/src/components/HeaderDisplay/index.tsx @@ -19,6 +19,7 @@ import Modal from '../Modal'; import { AccountContextValue } from '../../contexts/account'; import './stylesheet.scss'; +import { Term } from '../../types'; type VersionState = | { type: 'loading' } @@ -49,7 +50,7 @@ export type HeaderDisplayProps = { | { type: 'loading' } | { type: 'loaded'; - terms: readonly string[]; + terms: Term[]; currentTerm: string; onChangeTerm: (next: string) => void; }; @@ -108,8 +109,8 @@ export default function HeaderDisplay({ onChange={termsState.onChangeTerm} current={termsState.currentTerm} options={termsState.terms.map((currentTerm) => ({ - id: currentTerm, - label: getSemesterName(currentTerm), + id: currentTerm.term, + label: getSemesterName(currentTerm.term), }))} className="semester" /> diff --git a/src/data/hooks/useDownloadTerms.ts b/src/data/hooks/useDownloadTerms.ts index 78cf7f70..a5bbcfae 100644 --- a/src/data/hooks/useDownloadTerms.ts +++ b/src/data/hooks/useDownloadTerms.ts @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import axios from 'axios'; import { softError, ErrorWithFields } from '../../log'; -import { LoadingState, NonEmptyArray } from '../../types'; +import { LoadingState, NonEmptyArray, Term } from '../../types'; import { exponentialBackoff, isAxiosNetworkError } from '../../utils/misc'; import Cancellable from '../../utils/cancellable'; @@ -14,10 +14,8 @@ const CRAWLER_INDEX_URL = * Repeatedly attempts to load in the case of errors, * and cancels itself once the parent context is unmounted. */ -export default function useDownloadTerms(): LoadingState< - NonEmptyArray -> { - const [state, setState] = useState>>({ +export default function useDownloadTerms(): LoadingState> { + const [state, setState] = useState>>({ type: 'loading', }); @@ -28,7 +26,7 @@ export default function useDownloadTerms(): LoadingState< let attemptNumber = 1; while (!loadOperation.isCancelled) { try { - const promise = axios.get<{ terms: string[] }>(CRAWLER_INDEX_URL); + const promise = axios.get<{ terms: Term[] }>(CRAWLER_INDEX_URL); const result = await loadOperation.perform(promise); if (result.cancelled) { return; @@ -45,7 +43,7 @@ export default function useDownloadTerms(): LoadingState< setState({ type: 'loaded', - result: newTerms as NonEmptyArray, + result: newTerms as NonEmptyArray, }); return; diff --git a/src/data/hooks/useEnsureValidTerm.ts b/src/data/hooks/useEnsureValidTerm.ts index 73006056..74e0502f 100644 --- a/src/data/hooks/useEnsureValidTerm.ts +++ b/src/data/hooks/useEnsureValidTerm.ts @@ -1,6 +1,6 @@ import { useEffect } from 'react'; -import { NonEmptyArray, LoadingState } from '../../types'; +import { NonEmptyArray, LoadingState, Term } from '../../types'; type HookResult = { currentTerm: string; @@ -16,13 +16,13 @@ export default function useEnsureValidTerm({ setTerm, currentTermRaw, }: { - terms: NonEmptyArray; + terms: NonEmptyArray; setTerm: (next: string) => void; currentTermRaw: string; }): LoadingState { // Set the term to be the first one if it is unset or no longer valid. useEffect(() => { - const mostRecentTerm = terms[0]; + const mostRecentTerm = terms[0].term; const correctedTerm = !isValidTerm(currentTermRaw, terms) ? mostRecentTerm : currentTermRaw; @@ -48,6 +48,10 @@ export default function useEnsureValidTerm({ * Determines if the given term is considered "valid"; * helps to recover from invalid cookie values if possible. */ -export function isValidTerm(term: string, terms: string[]): boolean { - return term !== '' && term !== 'undefined' && terms.includes(term); +export function isValidTerm(currTerm: string, terms: Term[]): boolean { + return ( + currTerm !== '' && + currTerm !== 'undefined' && + terms.map((term) => term.term).includes(currTerm) + ); } diff --git a/src/types.ts b/src/types.ts index e3c8e4ed..2302a8bb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -84,6 +84,11 @@ export interface Event { days: string[]; } +export interface Term { + term: string; + finalized: boolean; +} + // Note: if this type ever changes, // the course gpa cache needs to be invalidated // (by changing the local storage key).