From 072dd406d868b2f2795dac45841fbdfb64010ff8 Mon Sep 17 00:00:00 2001 From: Madeline Hartlage Date: Mon, 26 Feb 2024 02:18:13 -0500 Subject: [PATCH] Finalized Term Alert (#255) ### Summary Resolves #252 Added functionality to alert user when they're viewing a schedule that might not be finalized --------- Co-authored-by: mhartlage3 Co-authored-by: Yatharth Bhargava <52095139+yatharth-b@users.noreply.github.com> Co-authored-by: Nathan Papa <111544731+Nathan-Papa@users.noreply.github.com> Co-authored-by: Yatharth Bhargava --- src/components/App/content.tsx | 4 +- src/components/App/index.tsx | 83 ++++++++++--------------- src/components/AppDataLoader/index.tsx | 3 +- src/components/AppDataLoader/stages.tsx | 6 +- src/components/Header/index.tsx | 1 + src/components/HeaderDisplay/index.tsx | 38 +++++++++-- src/components/Toast/index.tsx | 49 +++++++++------ src/components/Toast/stylesheet.scss | 79 +++++++++++++++-------- src/contexts/terms.ts | 4 +- src/data/hooks/useDownloadTerms.ts | 21 ++++--- src/data/hooks/useEnsureValidTerm.ts | 14 +++-- src/types.ts | 5 ++ 12 files changed, 189 insertions(+), 118 deletions(-) diff --git a/src/components/App/content.tsx b/src/components/App/content.tsx index 1292a3af..eaf410de 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; }; @@ -120,6 +121,7 @@ export function AppSkeleton({ : { type: 'loaded', ...termsState } } versionsState={{ type: 'loading' }} + skeleton /> {children} diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index f9c9aa23..415a9a76 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -16,20 +16,10 @@ import useThemeFromStorage from '../../data/hooks/useThemeFromStorage'; import { DESKTOP_BREAKPOINT } from '../../constants'; import useScreenWidth from '../../hooks/useScreenWidth'; import InformationModal from '../InformationModal'; -import Maintenance from './maintenance'; import 'react-virtualized/styles.css'; import './stylesheet.scss'; -const date = new Date( - new Date().toLocaleString('en-US', { timeZone: 'America/New_York' }) -); - -const websiteDown = - date.getFullYear() === 2024 && - date.getMonth() + 1 === 2 && - date.getDate() === 26; - export default function App(): React.ReactElement { // Grab the current theme (light/dark) from local storage. // This hook returns the memoized context value. @@ -45,52 +35,45 @@ export default function App(): React.ReactElement { {/* To bring the website down for maintenance purposes, insert here and disable everything below. See https://github.com/gt-scheduler/website/pull/194 for reference. */} - {websiteDown ? ( - - ) : ( - ( - - - - - } - > -
- There was en error somewhere in the core application - logic and it can't continue. -
-
- Try refreshing the page to see if it fixes the issue. -
-
-
-
- )} - > - - {/* AppDataLoader is in charge of ensuring that there are valid values + ( + + + + + } + > +
+ There was en error somewhere in the core application logic + and it can't continue. +
+
+ Try refreshing the page to see if it fixes the issue. +
+
+
+
+ )} + > + + {/* AppDataLoader is in charge of ensuring that there are valid values for the Terms and Term contexts before rendering its children. If any data is still loading, then it displays an "app skeleton" with a spinner. If there was an error while loading then it displays an error screen. */} - - - - - + + + +
+ - {/* Display a popup when first visiting the site */} - {/* Include or here */} - -
- )} + {/* Display a popup when first visiting the site */} + {/* Include or here */} + + diff --git a/src/components/AppDataLoader/index.tsx b/src/components/AppDataLoader/index.tsx index 302d9ec1..883546e5 100644 --- a/src/components/AppDataLoader/index.tsx +++ b/src/components/AppDataLoader/index.tsx @@ -30,6 +30,7 @@ import { StageExtractScheduleVersion, StageSkeletonProps, } from './stages'; +import { Term } from '../../types'; export type DataLoaderProps = { children: React.ReactNode; @@ -204,7 +205,7 @@ function GroupLoadScheduleData({ } type ContextProviderProps = { - terms: string[]; + terms: Term[]; currentTerm: string; setTerm: (next: string) => void; currentVersion: string; 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/Header/index.tsx b/src/components/Header/index.tsx index 7485588f..3b3d4a12 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -72,6 +72,7 @@ export default function Header({ renameVersion, cloneVersion, }} + skeleton={false} /> ); } diff --git a/src/components/HeaderDisplay/index.tsx b/src/components/HeaderDisplay/index.tsx index 39d2eec5..6975bbc2 100644 --- a/src/components/HeaderDisplay/index.tsx +++ b/src/components/HeaderDisplay/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faBars, @@ -17,6 +17,8 @@ import useScreenWidth from '../../hooks/useScreenWidth'; import HeaderActionBar from '../HeaderActionBar'; import Modal from '../Modal'; import { AccountContextValue } from '../../contexts/account'; +import { Term } from '../../types'; +import Toast, { notifyToast } from '../Toast'; import './stylesheet.scss'; @@ -49,12 +51,13 @@ export type HeaderDisplayProps = { | { type: 'loading' } | { type: 'loaded'; - terms: readonly string[]; + terms: Term[]; currentTerm: string; onChangeTerm: (next: string) => void; }; versionsState: VersionState; accountState: AccountContextValue | { type: 'loading' }; + skeleton: boolean; }; /** @@ -79,6 +82,7 @@ export default function HeaderDisplay({ termsState, versionsState, accountState, + skeleton = true, }: HeaderDisplayProps): React.ReactElement { // Re-render when the page is re-sized to become mobile/desktop // (desktop is >= 1024 px wide) @@ -87,8 +91,33 @@ export default function HeaderDisplay({ // Re-render when the page is re-sized to be small mobile vs. greater // (small mobile is < 600 px wide) const largeMobile = useScreenWidth(LARGE_MOBILE_BREAKPOINT); + + useEffect(() => { + if (termsState.type === 'loaded' && !skeleton) { + const termObject = termsState.terms.filter( + (term) => term.term === termsState.currentTerm + )[0]; + + if (!termObject?.finalized) { + notifyToast('finalized-term-toast'); + } + } + }); + return (
+ {!skeleton ? ( + + ) : null} {/* Menu button, only displayed on mobile */} {mobile && (