diff --git a/src/IsaacAppTypes.tsx b/src/IsaacAppTypes.tsx index 8224f625bf..cffc80e11a 100644 --- a/src/IsaacAppTypes.tsx +++ b/src/IsaacAppTypes.tsx @@ -483,6 +483,7 @@ export const AssignmentScheduleContext = React.createContext<{ setCollapsed: (b: boolean) => void; viewBy: "startDate" | "dueDate"; }>({boardsById: {}, groupsById: {}, groupFilter: {}, boardIdsByGroupId: {}, groups: [], gameboards: [], openAssignmentModal: () => {}, collapsed: false, setCollapsed: () => {}, viewBy: "startDate"}); +export const SidebarContext = React.createContext<{sidebarPresent: boolean} | undefined>(undefined); export const ContentSidebarContext = React.createContext<{ toggle: () => void; close: () => void; } | undefined>(undefined); export interface AuthorisedAssignmentProgress extends ApiTypes.AssignmentProgressDTO { diff --git a/src/app/components/elements/Book.tsx b/src/app/components/elements/Book.tsx index eb28de4c5c..b13e1069d5 100644 --- a/src/app/components/elements/Book.tsx +++ b/src/app/components/elements/Book.tsx @@ -1,9 +1,7 @@ import React, {useEffect, useState} from "react"; -import {Container} from "reactstrap"; -import {MainContent, SidebarLayout} from "./layout/SidebarLayout"; import {Markup} from "./markup"; import {TitleAndBreadcrumb} from "./TitleAndBreadcrumb"; -import {BOOK_DETAIL_ID_SEPARATOR, BOOKS_CRUMB, useContextFromContentObjectTags} from "../../services"; +import {BOOK_DETAIL_ID_SEPARATOR, BOOKS_CRUMB, siteSpecific, useContextFromContentObjectTags} from "../../services"; import {useLocation, useParams} from "react-router"; import {useGetBookDetailPageQuery, useGetBookIndexPageQuery} from "../../state/slices/api/booksApi"; import {BookPage} from "./BookPage"; @@ -14,6 +12,7 @@ import {ContentDTO} from "../../../IsaacApiTypes"; import { PageMetadata } from "./PageMetadata"; import { WithFigureNumbering } from "./WithFigureNumbering"; import { ContentControlledSidebar } from "./sidebar/ContentControlledSidebar"; +import { PageContainer } from "./layout/PageContainer"; export const Book = () => { @@ -41,59 +40,58 @@ export const Book = () => { setPageId(fragmentId); }, [book?.id, location.pathname]); - return - - - { - return <> - - - {pageId - ? { - return - - ; - }} - /> - : <> - - {definedBookIndexPage.value &&
-
- {definedBookIndexPage.title} -
- {definedBookIndexPage.value} -
} - {!!definedBookIndexPage.children?.length && <> -
-
- - - -
-
- {definedBookIndexPage.title} -
-
- - {definedBookIndexPage.children.slice(1)} - - } - - } -
- ; - }} + return -
-
; + } + sidebar={siteSpecific( + , + undefined + )} + > + { + return pageId + ? { + return + + ; + }} + /> + : <> + + {definedBookIndexPage.value &&
+
+ {definedBookIndexPage.title} +
+ {definedBookIndexPage.value} +
} + {!!definedBookIndexPage.children?.length && <> +
+
+ + + +
+
+ {definedBookIndexPage.title} +
+
+ + {definedBookIndexPage.children.slice(1)} + + } + ; + }} + /> + ; }; diff --git a/src/app/components/elements/layout/PageContainer.tsx b/src/app/components/elements/layout/PageContainer.tsx new file mode 100644 index 0000000000..a0676994d1 --- /dev/null +++ b/src/app/components/elements/layout/PageContainer.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { Container, ContainerProps } from "reactstrap"; +import { siteSpecific } from "../../../services"; +import { MainContent, SidebarLayout } from "./SidebarLayout"; +import classNames from "classnames"; + +interface PageContainerProps extends Omit { + pageTitle?: React.ReactNode; + sidebar?: React.ReactNode; +} + +/** + * A component to manage the main layout of a page, including the page title and sidebar if required. + * @param props The props for the PageContainer component. + * @param props.pageTitle The title of the page, displayed above both the main content and the sidebar. + * @param props.sidebar The content of the sidebar. Displayed according to @see SidebarLayout. If not provided, the page will be displayed without a sidebar. + * @param props.children The main content of the page. + * @returns A React component that renders the page layout. + */ +export const PageContainer = (props: PageContainerProps) => { + const { children, sidebar, pageTitle, id, ...rest } = props; + // TODO increase mb-2 to ~mb-7, but carefully consider mobile layouts and remove inconsistent additional spacing below individual pages. + if (!sidebar) { + return + {pageTitle} + {children} + ; + } + + return siteSpecific( + // Sci + + {pageTitle} + + {sidebar} + + {children} + + + , + + // Ada + // The ID is applied to the top-level component here to ensure #id:before / :after background elements cover the entire page. + // Slightly annoying since the className feels like it should be on the Container, leaving this awkward split. + // Maybe revisit this when we have more use cases? + + {sidebar} + + + {pageTitle} + {children} + + + + ); +}; diff --git a/src/app/components/elements/layout/SidebarLayout.tsx b/src/app/components/elements/layout/SidebarLayout.tsx index d520f089cb..2be0771784 100644 --- a/src/app/components/elements/layout/SidebarLayout.tsx +++ b/src/app/components/elements/layout/SidebarLayout.tsx @@ -1,15 +1,23 @@ -import React, { useEffect } from "react"; -import { Col, ColProps, RowProps, Offcanvas, OffcanvasBody, OffcanvasHeader, Row } from "reactstrap"; +import React, { useContext, useEffect } from "react"; +import { Col, ColProps, RowProps, Offcanvas, OffcanvasBody, OffcanvasHeader } from "reactstrap"; import classNames from "classnames"; -import { above, isAda, siteSpecific, useDeviceSize } from "../../../services"; +import { above, isPhy, siteSpecific, useDeviceSize } from "../../../services"; import { mainContentIdSlice, selectors, sidebarSlice, useAppDispatch, useAppSelector } from "../../../state"; -import { ContentSidebarContext } from "../../../../IsaacAppTypes"; +import { ContentSidebarContext, SidebarContext } from "../../../../IsaacAppTypes"; import { AffixButton } from "../AffixButton"; import { SidebarButton } from "../SidebarButton"; -export const SidebarLayout = (props: RowProps) => { - const { className, ...rest } = props; - return siteSpecific(, props.children); +interface SidebarLayoutProps extends RowProps { + show?: boolean; +} + +export const SidebarLayout = (props: SidebarLayoutProps) => { + const { className, show=true, ...rest } = props; + return show + ? +
+ + : props.children; }; export const MainContent = (props: ColProps) => { @@ -29,10 +37,11 @@ export type SidebarProps = ColProps export const NavigationSidebar = (props: SidebarProps) => { // A navigation sidebar is used for external links that are supplementary to the main content (e.g. related content); // the content in such a sidebar will collapse underneath the main content on smaller screens - if (isAda) return <>; + const sidebarContext = useContext(SidebarContext); + if (!sidebarContext?.sidebarPresent) return <>; const { className, ...rest } = props; - return ; + return ; }; export interface ContentSidebarProps extends SidebarProps { @@ -52,12 +61,13 @@ export const ContentSidebar = (props: ContentSidebarProps) => { const pageTheme = useAppSelector(selectors.pageContext.subject); - if (isAda) return <>; + const sidebarContext = useContext(SidebarContext); + if (!sidebarContext?.sidebarPresent) return <>; const { className, buttonTitle, hideButton, optionBar, ...rest } = props; return <> {above['lg'](deviceSize) - ? + ? : <> {optionBar &&
{optionBar}
diff --git a/src/app/components/elements/quiz/QuizContentsComponent.tsx b/src/app/components/elements/quiz/QuizContentsComponent.tsx index 8099848fd8..fa8e0abc18 100644 --- a/src/app/components/elements/quiz/QuizContentsComponent.tsx +++ b/src/app/components/elements/quiz/QuizContentsComponent.tsx @@ -346,7 +346,7 @@ export function QuizContentsComponent(props: QuizAttemptProps | QuizViewProps) { return <> - + {props.page === null || props.page == undefined ? QuizOverview({...{viewingAsSomeoneElse, ...props}}): } diff --git a/src/app/components/elements/quiz/QuizSidebarLayout.tsx b/src/app/components/elements/quiz/QuizSidebarLayout.tsx index e5bdf81f25..f1b6739945 100644 --- a/src/app/components/elements/quiz/QuizSidebarLayout.tsx +++ b/src/app/components/elements/quiz/QuizSidebarLayout.tsx @@ -1,11 +1,12 @@ import React, { ReactNode } from "react"; import { SidebarLayout, MainContent } from "../layout/SidebarLayout"; +import { isPhy } from "../../../services"; export const QuizSidebarLayout = ({ children } : { children: ReactNode }) => - +
{children}
-
; \ No newline at end of file +
; diff --git a/src/app/components/handlers/ShowLoadingQuery.tsx b/src/app/components/handlers/ShowLoadingQuery.tsx index 34a498703e..ee4eeba522 100644 --- a/src/app/components/handlers/ShowLoadingQuery.tsx +++ b/src/app/components/handlers/ShowLoadingQuery.tsx @@ -69,7 +69,7 @@ type ShowLoadingQueryProps = ShowLoadingQueryErrorProps & ({ children?: undefined; } | { thenRender?: undefined; - children: JSX.Element | JSX.Element[]; + children: React.ReactNode; }); // A flexible way of displaying whether a RTKQ query is loading or errored. You can give as props: // - Either: `children` or `thenRender` (a function that takes the query data and returns a React element) diff --git a/src/app/components/pages/AnvilAppsListing.tsx b/src/app/components/pages/AnvilAppsListing.tsx index 6abd88b954..1a2e4ca8b7 100644 --- a/src/app/components/pages/AnvilAppsListing.tsx +++ b/src/app/components/pages/AnvilAppsListing.tsx @@ -1,11 +1,11 @@ import React from "react"; import { Container } from "reactstrap"; import { generateSubjectLandingPageCrumbFromContext, TitleAndBreadcrumb } from "../elements/TitleAndBreadcrumb"; -import { getHumanContext, isFullyDefinedContext, isSingleStageContext, useUrlPageTheme, VALID_APPS_CONTEXTS } from "../../services"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; +import { getHumanContext, isFullyDefinedContext, isSingleStageContext, siteSpecific, useUrlPageTheme, VALID_APPS_CONTEXTS } from "../../services"; import { PageMetadata } from "../elements/PageMetadata"; import { PageFragment } from "../elements/PageFragment"; import { AnvilAppsListingSidebar } from "../elements/sidebar/AnvilAppsListingSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; export const AnvilAppsListing = () => { const pageContext = useUrlPageTheme(); @@ -26,18 +26,20 @@ export const AnvilAppsListing = () => { ; } - return - - - - - - - - - ; + return + } + sidebar={siteSpecific( + , + undefined + )} + > + + + ; }; diff --git a/src/app/components/pages/BooksOverview.tsx b/src/app/components/pages/BooksOverview.tsx index e7bd1acea8..ee61508961 100644 --- a/src/app/components/pages/BooksOverview.tsx +++ b/src/app/components/pages/BooksOverview.tsx @@ -1,12 +1,11 @@ import React from "react"; -import { Container } from "reactstrap"; import { TitleAndBreadcrumb } from "../elements/TitleAndBreadcrumb"; import { PageFragment } from "../elements/PageFragment"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; -import { BookHiddenState, BookInfo, ISAAC_BOOKS } from "../../services"; +import { BookHiddenState, BookInfo, ISAAC_BOOKS, siteSpecific } from "../../services"; import { Link } from "react-router-dom"; import { PageMetadata } from "../elements/PageMetadata"; import { BooksOverviewSidebar } from "../elements/sidebar/BooksOverviewSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; export const BookCard = (book: BookInfo) => { return @@ -24,25 +23,27 @@ export const BookCard = (book: BookInfo) => { }; export const BooksOverview = () => { - return - - - - - - + return + } + sidebar={siteSpecific( + , + undefined + )} + > + + -

Explore our books online

- Click on a book image below to go to the homepage of each book and explore further. -
- {ISAAC_BOOKS.filter(b => b.hidden !== BookHiddenState.HIDDEN).map((book, index) => { - return ; - })} -
-
-
-
; +

Explore our books online

+ Click on a book image below to go to the homepage of each book and explore further. +
+ {ISAAC_BOOKS.filter(b => b.hidden !== BookHiddenState.HIDDEN).map((book, index) => { + return ; + })} +
+ ; }; diff --git a/src/app/components/pages/Concept.tsx b/src/app/components/pages/Concept.tsx index b9e58045d8..bfa653a105 100644 --- a/src/app/components/pages/Concept.tsx +++ b/src/app/components/pages/Concept.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from "react"; import {useLocation, useParams} from "react-router-dom"; import {selectors, useAppSelector, useGetGameboardByIdQuery} from "../../state"; -import {Col, Container, Row} from "reactstrap"; +import {Col, Row} from "reactstrap"; import {IsaacContent} from "../content/IsaacContent"; import {IsaacConceptPageDTO} from "../../../IsaacApiTypes"; import {Subject, usePreviousPageContext, isAda, useNavigation, siteSpecific, useUserViewingContext, isFullyDefinedContext, isSingleStageContext, LEARNING_STAGE_TO_STAGES, isDefined} from "../../services"; @@ -17,7 +17,6 @@ import {CanonicalHrefElement} from "../navigation/CanonicalHrefElement"; import {MetaDescription} from "../elements/MetaDescription"; import classNames from "classnames"; import queryString from "query-string"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import { useGetConceptQuery } from "../../state/slices/api/conceptsApi"; import { ShowLoadingQuery } from "../handlers/ShowLoadingQuery"; import { NotFound } from "./NotFound"; @@ -27,6 +26,7 @@ import { InaccessibleContentWarningBanner } from "../navigation/InaccessibleCont import { skipToken } from "@reduxjs/toolkit/query"; import { GameboardContentSidebar } from "../elements/sidebar/GameboardContentSidebar"; import { ConceptSidebar } from "../elements/sidebar/RelatedContentSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; interface ConceptPageProps { conceptIdOverride?: string; @@ -71,55 +71,56 @@ export const Concept = ({conceptIdOverride, preview}: ConceptPageProps) => { thenRender={supertypedDoc => { const doc = supertypedDoc as IsaacConceptPageDTO & DocumentSubject; return - - - {!preview && <> - - + + + {!preview && <> + + + } } - - {isDefined(gameboardId) + sidebar={siteSpecific( + isDefined(gameboardId) ? - : - } - - + : , + undefined + )} + > + - {accessibilitySettings?.SHOW_INACCESSIBLE_WARNING && getAccessibilityTags(doc.tags).map(tag => )} + {accessibilitySettings?.SHOW_INACCESSIBLE_WARNING && getAccessibilityTags(doc.tags).map(tag => )} - - + + - + - {isAda && } + {isAda && } - - - + + + - {doc.attribution &&

- - {doc.attribution} - -

} + {doc.attribution &&

+ + {doc.attribution} + +

} - {isAda && doc.relatedContent && } + {isAda && doc.relatedContent && } - - -
-
-
-
+ + + +
; }} />; diff --git a/src/app/components/pages/Concepts.tsx b/src/app/components/pages/Concepts.tsx index a9800d2dd6..f51113fa16 100644 --- a/src/app/components/pages/Concepts.tsx +++ b/src/app/components/pages/Concepts.tsx @@ -1,13 +1,11 @@ import React, {FormEvent, MutableRefObject, useEffect, useMemo, useRef, useState} from "react"; import {Link, useLocation, useNavigate} from "react-router-dom"; import {selectors, useAppSelector} from "../../state"; -import {Container} from "reactstrap"; import queryString from "query-string"; -import {getFilteredStageOptions, isPhy, isRelevantToPageContext, matchesAllWordsInAnyOrder, pushConceptsToHistory, searchResultIsPublic, shortcuts, stageLabelMap, SUBJECT_SPECIFIC_CHILDREN_MAP, TAG_ID, tags} from "../../services"; +import {getFilteredStageOptions, isPhy, isRelevantToPageContext, matchesAllWordsInAnyOrder, pushConceptsToHistory, searchResultIsPublic, shortcuts, siteSpecific, stageLabelMap, SUBJECT_SPECIFIC_CHILDREN_MAP, TAG_ID, tags} from "../../services"; import {generateSubjectLandingPageCrumbFromContext, TitleAndBreadcrumb} from "../elements/TitleAndBreadcrumb"; import {ShortcutResponse, Tag} from "../../../IsaacAppTypes"; import { ListView } from "../elements/list-groups/ListView"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import { getHumanContext, isFullyDefinedContext, useUrlPageTheme } from "../../services/pageContext"; import { useListConceptsQuery } from "../../state/slices/api/conceptsApi"; import { ShowLoadingQuery } from "../handlers/ShowLoadingQuery"; @@ -17,6 +15,7 @@ import { PageMetadata } from "../elements/PageMetadata"; import { ResultsListContainer, ResultsListHeader } from "../elements/ListResultsContainer"; import { FilterSummary } from "./QuestionFinder"; import { GenericConceptsListingSidebar, SubjectSpecificConceptsListingSidebar } from "../elements/sidebar/ConceptsListingSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; const subjectToTagMap = { physics: TAG_ID.physics, @@ -143,54 +142,53 @@ export const Concepts = () => { const sidebarProps = {searchText, setSearchText, conceptFilters, setConceptFilters, applicableTags, tagCounts}; return ( - - - - {pageContext?.subject + />} + sidebar={siteSpecific( + pageContext?.subject ? - : + : , + undefined + )} + > + + {pageContext?.subject + ?
+

+ The concepts shown on this page have been filtered to only show those that are relevant to {getHumanContext(pageContext)}. + You can browse all concepts here. +

+
+ :

Use our concept finder to explore all concepts on the Isaac platform.

} - - - {pageContext?.subject - ?
-

- The concepts shown on this page have been filtered to only show those that are relevant to {getHumanContext(pageContext)}. - You can browse all concepts here. -

-
- :

Use our concept finder to explore all concepts on the Isaac platform.

- } -
- {isPhy && !pageContext?.subject && (!pageContext?.stage || pageContext.stage.length === 0) && } - - - { - - const shortcutAndFilteredSearchResults = shortcutAndFilter(concepts); - - return <> - {!!shortcutAndFilteredSearchResults.length && - Showing {shortcutAndFilteredSearchResults.length} results - } - - {shortcutAndFilteredSearchResults.length - ? - : No results found - } - ; - }} - defaultErrorTitle="Error fetching concepts" - /> - -
-
-
+ + {isPhy && !pageContext?.subject && (!pageContext?.stage || pageContext.stage.length === 0) && } + + + { + + const shortcutAndFilteredSearchResults = shortcutAndFilter(concepts); + + return <> + {!!shortcutAndFilteredSearchResults.length && + Showing {shortcutAndFilteredSearchResults.length} results + } + + {shortcutAndFilteredSearchResults.length + ? + : No results found + } + ; + }} + defaultErrorTitle="Error fetching concepts" + /> + + ); }; diff --git a/src/app/components/pages/Events.tsx b/src/app/components/pages/Events.tsx index dbff5baeaf..67af9da660 100644 --- a/src/app/components/pages/Events.tsx +++ b/src/app/components/pages/Events.tsx @@ -17,9 +17,9 @@ import { } from "../../services"; import {RenderNothing} from "../elements/RenderNothing"; import {ShowLoadingQuery} from "../handlers/ShowLoadingQuery"; -import { Container, Row, Button, Form, Input, Label, Col } from "reactstrap"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; +import { Row, Button, Form, Input, Label, Col } from "reactstrap"; import { EventsSidebar } from "../elements/sidebar/EventsSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; export interface EventsPageQueryParams { show_booked_only?: boolean; @@ -114,55 +114,58 @@ export const Events = () => { }; return
- - - - - - {isAda && } - { - const numberOfLoadedEvents = events.length; + + } + sidebar={siteSpecific( + , + undefined + )} + > + {isAda && } + { + const numberOfLoadedEvents = events.length; - return
-
- Showing {numberOfLoadedEvents} of {total} -
+ return
+
+ Showing {numberOfLoadedEvents} of {total} +
- - {events.map(event => - {deviceSize==="md" &&
} - - )} - + + {events.map(event => + {deviceSize==="md" &&
} + + )} + - {/* Load More Button */} - {numberOfLoadedEvents < total &&
- -
} + {/* Load More Button */} + {numberOfLoadedEvents < total &&
+ +
} - {/* No Results */} - {total === 0 &&
-

Sorry, we cannot find any events that match your filter settings.

-
} -
; - }} /> -
- -
- - - + {/* No Results */} + {total === 0 &&
+

Sorry, we cannot find any events that match your filter settings.

+
} +
; + }} + /> +
+ +
+
; }; diff --git a/src/app/components/pages/Gameboard.tsx b/src/app/components/pages/Gameboard.tsx index 7862ee84ed..754d5b8078 100644 --- a/src/app/components/pages/Gameboard.tsx +++ b/src/app/components/pages/Gameboard.tsx @@ -29,12 +29,12 @@ import { import {Navigate, useLocation} from "react-router"; import classNames from "classnames"; import {skipToken} from "@reduxjs/toolkit/query"; -import {ShowLoadingQuery} from "../handlers/ShowLoadingQuery"; -import {MainContent, SidebarLayout} from "../elements/layout/SidebarLayout"; +import {LoadingPlaceholder, ShowLoadingQuery} from "../handlers/ShowLoadingQuery"; import {PageMetadata} from "../elements/PageMetadata"; import {ListView} from "../elements/list-groups/ListView"; import { GameboardSidebar } from "../elements/sidebar/GameboardSidebar"; import { SupersededDeprecatedBoardContentWarning } from "../navigation/SupersededDeprecatedWarning"; +import { PageContainer } from "../elements/layout/PageContainer"; export const Gameboard = () => { const dispatch = useAppDispatch(); @@ -71,51 +71,52 @@ export const Gameboard = () => { ; return !gameboardId ? - : - { - return <> + : } + thenRender={(gameboard) => { + return - - - - - - - {user && isTutorOrAbove(user) - ? - - - - - - - - : gameboard && !gameboard.savedToCurrentUser && - - - - - } - - - ; - }} - /> - ; + } + sidebar={siteSpecific( + , + undefined + )} + > + + + + {user && isTutorOrAbove(user) + ? + + + + + + + + : gameboard && !gameboard.savedToCurrentUser && + + + + + } + ; + }} + />; }; diff --git a/src/app/components/pages/Generic.tsx b/src/app/components/pages/Generic.tsx index a3a61a8243..ea70107e0b 100644 --- a/src/app/components/pages/Generic.tsx +++ b/src/app/components/pages/Generic.tsx @@ -1,8 +1,8 @@ import React, {useEffect} from "react"; -import {Col, Container, Row} from "reactstrap"; +import {Col, Row} from "reactstrap"; import {ContentSummaryDTO, GameboardDTO, SeguePageDTO} from "../../../IsaacApiTypes"; import {IsaacContent} from "../content/IsaacContent"; -import {isAda, isPhy, useUrlHashValue} from "../../services"; +import {isAda, isPhy, siteSpecific, useUrlHashValue} from "../../services"; import {useParams} from "react-router-dom"; import {RelatedContent} from "../elements/RelatedContent"; import {DocumentSubject} from "../../../IsaacAppTypes"; @@ -12,7 +12,6 @@ import {MetaDescription} from "../elements/MetaDescription"; import classNames from "classnames"; import queryString from "query-string"; import { useUntilFound } from "./Glossary"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import { useGetGenericPageQuery } from "../../state/slices/api/genericApi"; import { ShowLoadingQuery } from "../handlers/ShowLoadingQuery"; import { NotFound } from "./NotFound"; @@ -25,6 +24,7 @@ import { GameboardContentSidebar } from "../elements/sidebar/GameboardContentSid import { GenericPageSidebar } from "../elements/sidebar/GenericPageSidebar"; import { PolicyPageSidebar } from "../elements/sidebar/PolicyPageSidebar"; import { GenericSidebarWithRelatedContent } from "../elements/sidebar/RelatedContentSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; interface GenericPageComponentProps { pageIdOverride?: string; @@ -88,38 +88,40 @@ export const Generic = ({pageIdOverride}: GenericPageComponentProps) => { const sidebar = doc.sidebar ? - : ; + : siteSpecific( + , + undefined + ); - return - + return + } + sidebar={sidebar} + > - - {sidebar} - - {/* on non-news generic pages, the actual doc.title is used as the super-title, unlike e.g. questions which use "Question". - as such, we promote a generic page's subtitle to be the regular title. */} - {isNews - ? - : - } + {/* on non-news generic pages, the actual doc.title is used as the super-title, unlike e.g. questions which use "Question". + as such, we promote a generic page's subtitle to be the regular title. */} + {isNews + ? + : + } - - - - - - - - - + + + + + + + {isAda && doc.relatedContent && } - ; + ; }} />; }; diff --git a/src/app/components/pages/Glossary.tsx b/src/app/components/pages/Glossary.tsx index 025387bedc..765cec6f2d 100644 --- a/src/app/components/pages/Glossary.tsx +++ b/src/app/components/pages/Glossary.tsx @@ -1,5 +1,5 @@ import React, {ChangeEvent, useEffect, useMemo, useRef, useState} from "react"; -import {Col, Container, Input, Label, Row} from "reactstrap"; +import {Col, Input, Label, Row} from "reactstrap"; import queryString from "query-string"; import {AppState, logAction, selectors, useAppDispatch, useAppSelector} from "../../state"; import {ShowLoading} from "../handlers/ShowLoading"; @@ -32,12 +32,12 @@ import {NOT_FOUND_TYPE, PageContextState, Tag} from '../../../IsaacAppTypes'; import {MetaDescription} from "../elements/MetaDescription"; import {StyledSelect} from "../elements/inputs/StyledSelect"; import {useLocation, useNavigate} from "react-router"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import classNames from "classnames"; import debounce from "lodash/debounce"; import { PageMetadata } from "../elements/PageMetadata"; import { PageFragment } from "../elements/PageFragment"; import { GlossarySidebar } from "../elements/sidebar/GlossarySidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; type FilterParams = "subjects" | "stages" | "query"; @@ -358,90 +358,91 @@ export const Glossary = () => { } return
- - - - - + + + + } + sidebar={siteSpecific( - - - - - - - - {isAda && <> - - - - - - - ({ value: t.id, label: t.title}))} - name="topic-select" - placeholder="All topics" - onChange={e => setFilterTopic(topics.find(v => v.id === (e as Item | undefined)?.value)) } - isClearable - /> - - } - - - - {searchText !== "" && Search: {searchText}} - {isDefined(filterTopic) && Topic: {filterTopic.title}} - - - - - {(!glossaryTerms || Object.entries(glossaryTerms).length === 0) && -
- {/* Let users know that they need to select a subject */} - {isPhy && !isDefined(filterSubject) &&

Please select a subject.

} - {(isAda || isDefined(filterSubject)) && searchText === "" &&

There are no glossary terms in the glossary yet! Please try again later.

} - {(isAda || isDefined(filterSubject)) && searchText !== "" &&

We could not find glossary terms to match your search criteria.

} -
-
} - {glossaryTerms && Object.keys(glossaryTerms).length > 0 && -
-
 
-
- {alphabetList} -
-
- {alphabetList} -
-
- {Object.entries(glossaryTerms).map(([letter, terms]) =>
alphabetHeaderRefs.current.set(letter, el)}> - -

- {letter} -

+ />, + undefined + )} + > + + + + + + + {isAda && <> + + + - - {terms.map(term => { - glossaryTermRefs.current.set((term.id && formatGlossaryTermId(term.id)) ?? "", el); - }} - doc={term} - linkToGlossary={true} - />)} + + + ({ value: t.id, label: t.title}))} + name="topic-select" + placeholder="All topics" + onChange={e => setFilterTopic(topics.find(v => v.id === (e as Item | undefined)?.value)) } + isClearable + /> -
)} - } -
-
-
+ } + + + + {searchText !== "" && Search: {searchText}} + {isDefined(filterTopic) && Topic: {filterTopic.title}} + + + + + {(!glossaryTerms || Object.entries(glossaryTerms).length === 0) && +
+ {/* Let users know that they need to select a subject */} + {isPhy && !isDefined(filterSubject) &&

Please select a subject.

} + {(isAda || isDefined(filterSubject)) && searchText === "" &&

There are no glossary terms in the glossary yet! Please try again later.

} + {(isAda || isDefined(filterSubject)) && searchText !== "" &&

We could not find glossary terms to match your search criteria.

} +
+
} + {glossaryTerms && Object.keys(glossaryTerms).length > 0 && +
+
 
+
+ {alphabetList} +
+
+ {alphabetList} +
+
+ {Object.entries(glossaryTerms).map(([letter, terms]) =>
alphabetHeaderRefs.current.set(letter, el)}> + +

+ {letter} +

+ + + {terms.map(term => { + glossaryTermRefs.current.set((term.id && formatGlossaryTermId(term.id)) ?? "", el); + }} + doc={term} + linkToGlossary={true} + />)} + +
)} + } +
; }; diff --git a/src/app/components/pages/Groups.tsx b/src/app/components/pages/Groups.tsx index 03045d722c..0ce0e824ef 100644 --- a/src/app/components/pages/Groups.tsx +++ b/src/app/components/pages/Groups.tsx @@ -62,11 +62,11 @@ import classNames from "classnames"; import {PageFragment} from "../elements/PageFragment"; import {RenderNothing} from "../elements/RenderNothing"; import {StyledCheckbox} from "../elements/inputs/StyledCheckbox"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import { StyledTabPicker } from "../elements/inputs/StyledTabPicker"; import { PageMetadata } from "../elements/PageMetadata"; import { GroupsSidebar } from "../elements/sidebar/GroupsSidebar"; import { IconButton } from "../elements/AffixButton"; +import { PageContainer } from "../elements/layout/PageContainer"; enum SortOrder { Alphabetical = "Alphabetical", @@ -594,28 +594,30 @@ export const Groups = ({user}: {user: RegisteredUserDTO}) => { You can find the code for an existing group by selecting the group and clicking Invite Users. ; - const GroupsPhy = - + const GroupsPhy = + } + sidebar={siteSpecific( + , + undefined + )} + > - - - - - - - {selectedGroup && - - } - {/* On small screens, the groups list should initially be accessible without needing to open the sidebar drawer */} - {below["md"](deviceSize) && !isDefined(selectedGroup) && } - - + + + + {selectedGroup && + + } + {/* On small screens, the groups list should initially be accessible without needing to open the sidebar drawer */} + {below["md"](deviceSize) && !isDefined(selectedGroup) && } - ; + ; const GroupsAda = diff --git a/src/app/components/pages/MyAccount.tsx b/src/app/components/pages/MyAccount.tsx index f05091bef4..d0666d1dd5 100644 --- a/src/app/components/pages/MyAccount.tsx +++ b/src/app/components/pages/MyAccount.tsx @@ -1,7 +1,7 @@ import React, {lazy, Suspense, useCallback, useEffect, useMemo, useState} from 'react'; import classnames from "classnames"; import classNames from "classnames"; -import {Button, Container, Form, Input, Nav, NavItem, NavLink, TabContent, TabPane,} from "reactstrap"; +import {Button, Form, Input, Nav, NavItem, NavLink, TabContent, TabPane,} from "reactstrap"; import {UserContext} from "../../../IsaacApiTypes"; import { AppDispatch, @@ -40,6 +40,7 @@ import { isPhy, isStaff, isTeacherOrAbove, + siteSpecific, validateEmail, validateEmailPreferences, validatePassword @@ -56,11 +57,11 @@ import {useEmailPreferenceState} from "../elements/inputs/UserEmailPreferencesIn import {UserProfile} from '../elements/panels/UserProfile'; import {UserContent} from '../elements/panels/UserContent'; import {ExigentAlert} from "../elements/ExigentAlert"; -import {MainContent, SidebarLayout} from '../elements/layout/SidebarLayout'; import {Loading} from '../handlers/IsaacSpinner'; import {UserAccessibilitySettings} from '../elements/panels/UserAccessibilitySettings'; import {showEmailChangeModal} from "../elements/modals/EmailChangeModal"; import { MyAccountSidebar } from '../elements/sidebar/MyAccountSidebar'; +import { PageContainer } from '../elements/layout/PageContainer'; // Avoid loading the (large) QRCode library unless necessary: const UserMFA = lazy(() => import("../elements/panels/UserMFA")); @@ -316,8 +317,15 @@ export const MyAccount = ({user}: AccountPageProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeTab]); - return - + return + } + sidebar={siteSpecific( + , + undefined + )} + > {isAda &&

{`Update your Ada Computer Science account, or `} @@ -326,112 +334,107 @@ export const MyAccount = ({user}: AccountPageProps) => {

} {user.loggedIn && userToUpdate.loggedIn && // We can guarantee user and myUser are logged in from the route requirements - - - -
- {isAda && } -
- {updateCurrentUserError && - -

Unable to update your account

-

{getRTKQueryErrorMessage(updateCurrentUserError).message}

-
- } - - - - - {isAda && - - } - - - - {!editingOtherUser && - - } - {!editingOtherUser && - - } - {!editingOtherUser && - - } - -
- {/* Tabs containing forms (which cannot be nested inside another form) */} - {formSpecificTabs.includes(activeTab) && - {isStaff(userToUpdate) && !editingOtherUser && - }> - - - } - - - - } -
- {isPhy &&
} -
- -
-
+
+ {isAda && } +
+ {updateCurrentUserError && + +

Unable to update your account

+

{getRTKQueryErrorMessage(updateCurrentUserError).message}

+
+ } + + + + + {isAda && + + } + + + + {!editingOtherUser && + + } + {!editingOtherUser && + + } + {!editingOtherUser && + + } + +
+ {/* Tabs containing forms (which cannot be nested inside another form) */} + {formSpecificTabs.includes(activeTab) && + {isStaff(userToUpdate) && !editingOtherUser && + }> + + + } + + + + } +
+ {isPhy &&
} +
+
- - +
+
} - ; + ; }; diff --git a/src/app/components/pages/MyAssignments.tsx b/src/app/components/pages/MyAssignments.tsx index c4a55583ca..7f6a152f12 100644 --- a/src/app/components/pages/MyAssignments.tsx +++ b/src/app/components/pages/MyAssignments.tsx @@ -1,18 +1,18 @@ import React, {useEffect, useState} from "react"; import {logAction, useAppDispatch, useGetMyAssignmentsQuery} from "../../state"; import {AssignmentDTO, RegisteredUserDTO} from "../../../IsaacApiTypes"; -import {Button, Col, Container, Input, Label, Row} from 'reactstrap'; +import {Button, Col, Input, Label, Row} from 'reactstrap'; import {TitleAndBreadcrumb} from "../elements/TitleAndBreadcrumb"; import {filterAssignmentsByProperties, filterAssignmentsByStatus, getDistinctAssignmentGroups, getDistinctAssignmentSetters, isAda, isPhy, isTutorOrAbove, siteSpecific} from "../../services"; import {Assignments} from "../elements/Assignments"; import {ShowLoadingQuery} from "../handlers/ShowLoadingQuery"; import {PageFragment} from "../elements/PageFragment"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import { MyAssignmentsOrder } from "../../../IsaacAppTypes"; import sortBy from "lodash/sortBy"; import { PageMetadata } from "../elements/PageMetadata"; import classNames from "classnames"; import { MyAssignmentsSidebar } from "../elements/sidebar/MyAssignmentsSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; const INITIAL_NO_ASSIGNMENTS = 10; const NO_ASSIGNMENTS_INCREMENT = 10; @@ -107,87 +107,89 @@ export const MyAssignments = ({user}: {user: RegisteredUserDTO}) => { setStatusFilter, setTitleFilter, setGroupFilter, setSetByFilter }; - return - - + return + } + sidebar={siteSpecific( - - - } /> - -
-
- - { - const myAssignments = filterAssignmentsByStatus(assignments); - - const SORT_FUNCTIONS = { - [MyAssignmentsOrder.startDate]: (a: AssignmentDTO) => a.scheduledStartDate ? a.scheduledStartDate : a.creationDate, - [MyAssignmentsOrder.dueDate]: (a: AssignmentDTO) => a.dueDate, - [MyAssignmentsOrder.attempted]: (a: AssignmentDTO) => a.gameboard?.percentageAttempted ?? 0, - [MyAssignmentsOrder.correct]: (a: AssignmentDTO) => a.gameboard?.percentageCorrect ?? 0, - }; - - const assignmentByStates: Record = { - [AssignmentState.ALL]: [...myAssignments.inProgress, ...myAssignments.overDue, ...myAssignments.allAttempted, ...myAssignments.allCorrect], - [AssignmentState.TODO]: myAssignments.inProgress, - [AssignmentState.OVERDUE]: myAssignments.overDue, - [AssignmentState.ALL_ATTEMPTED]: myAssignments.allAttempted, - [AssignmentState.ALL_CORRECT]: myAssignments.allCorrect - }; - - const filteredAssignments = filterAssignmentsByProperties( - statusFilter.includes(AssignmentState.ALL) ? assignmentByStates[AssignmentState.ALL] : statusFilter.flatMap(f => assignmentByStates[f]), - titleFilter, groupFilter, setByFilter - ); - - if (isPhy && isFirstLoad && filteredAssignments.length === 0) { - setStatusFilter([AssignmentState.ALL]); - } - setIsFirstLoad(false); - - const orderNegative = sortOrder.at(0) == "-"; - const orderKind = (orderNegative ? sortOrder.slice(1) : sortOrder) as "startDate" | "dueDate" | "attempted" | "correct"; - const orderedAssignments = sortBy(filteredAssignments, SORT_FUNCTIONS[orderKind]); - if (orderNegative) orderedAssignments.reverse(); - - return <> - {siteSpecific( -
- -
, - - )} - {limit < filteredAssignments.length &&
-
-

- Showing {limit} of {filteredAssignments.length} filtered quizzes. -

- -
} - ; - }} - /> -
-
-
-
-
; + />, + undefined + )} + > + + } /> + +
+
+ + { + const myAssignments = filterAssignmentsByStatus(assignments); + + const SORT_FUNCTIONS = { + [MyAssignmentsOrder.startDate]: (a: AssignmentDTO) => a.scheduledStartDate ? a.scheduledStartDate : a.creationDate, + [MyAssignmentsOrder.dueDate]: (a: AssignmentDTO) => a.dueDate, + [MyAssignmentsOrder.attempted]: (a: AssignmentDTO) => a.gameboard?.percentageAttempted ?? 0, + [MyAssignmentsOrder.correct]: (a: AssignmentDTO) => a.gameboard?.percentageCorrect ?? 0, + }; + + const assignmentByStates: Record = { + [AssignmentState.ALL]: [...myAssignments.inProgress, ...myAssignments.overDue, ...myAssignments.allAttempted, ...myAssignments.allCorrect], + [AssignmentState.TODO]: myAssignments.inProgress, + [AssignmentState.OVERDUE]: myAssignments.overDue, + [AssignmentState.ALL_ATTEMPTED]: myAssignments.allAttempted, + [AssignmentState.ALL_CORRECT]: myAssignments.allCorrect + }; + + const filteredAssignments = filterAssignmentsByProperties( + statusFilter.includes(AssignmentState.ALL) ? assignmentByStates[AssignmentState.ALL] : statusFilter.flatMap(f => assignmentByStates[f]), + titleFilter, groupFilter, setByFilter + ); + + if (isPhy && isFirstLoad && filteredAssignments.length === 0) { + setStatusFilter([AssignmentState.ALL]); + } + setIsFirstLoad(false); + + const orderNegative = sortOrder.at(0) == "-"; + const orderKind = (orderNegative ? sortOrder.slice(1) : sortOrder) as "startDate" | "dueDate" | "attempted" | "correct"; + const orderedAssignments = sortBy(filteredAssignments, SORT_FUNCTIONS[orderKind]); + if (orderNegative) orderedAssignments.reverse(); + + return <> + {siteSpecific( +
+ +
, + + )} + {limit < filteredAssignments.length &&
+
+

+ Showing {limit} of {filteredAssignments.length} filtered quizzes. +

+ +
} + ; + }} + /> +
+
+ ; }; diff --git a/src/app/components/pages/MyGameboards.tsx b/src/app/components/pages/MyGameboards.tsx index cb993a5a42..e94f0d340a 100644 --- a/src/app/components/pages/MyGameboards.tsx +++ b/src/app/components/pages/MyGameboards.tsx @@ -4,7 +4,6 @@ import {ShowLoading} from "../handlers/ShowLoading"; import { Button, Col, - Container, Input, Label, Row} from 'reactstrap'; @@ -26,10 +25,10 @@ import {PageFragment} from "../elements/PageFragment"; import {RenderNothing} from "../elements/RenderNothing"; import { GameboardsCards, GameboardsCardsProps, GameboardsTable, GameboardsTableProps } from "../elements/Gameboards"; import classNames from "classnames"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import { PageMetadata } from "../elements/PageMetadata"; import { useHistoryState } from "../../state/actions/history"; import { MyGameboardsSidebar } from "../elements/sidebar/MyGameboardsSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; export interface GameboardsDisplaySettingsProps { boardView: BoardViews, @@ -188,9 +187,11 @@ export const MyGameboards = ({user}: {user: RegisteredUserDTO}) => { user, boards, selectedBoards, setSelectedBoards, boardView, boardTitleFilter, boardCreator, boardCompletion, loading, viewMore }; - return - - + return + } + sidebar={siteSpecific( { boardCompletionFilter={boardCompletion} setBoardCompletionFilter={setBoardCompletion} forceAllBoards={forceAllBoards} hideButton - /> - - - - - {boards && boards.totalResults == 0 ? - <> -

You have no {siteSpecific("question decks", "quizzes")} to view.

- - : - <> -
- {boards - ? Showing {inProgress + notStarted} {siteSpecific("question decks", "quizzes")}, with {inProgress} on the go and {notStarted} not started. - : - } -
- {isAda && <> - {/* this is in the sidebar on phy */} - - - } - - {boards && boards.boards && <> - {(boardView === BoardViews.card - ? - : - )} - } - + />, + undefined + )} + > + + + + {boards && boards.totalResults == 0 ? + <> +

You have no {siteSpecific("question decks", "quizzes")} to view.

+ + : + <> +
+ {boards + ? Showing {inProgress + notStarted} {siteSpecific("question decks", "quizzes")}, with {inProgress} on the go and {notStarted} not started. + : + } +
+ {isAda && <> + {/* this is in the sidebar on phy */} + + + } + + {boards && boards.boards && <> + {(boardView === BoardViews.card + ? + : + )} } -
-
-
; + + } + ; }; diff --git a/src/app/components/pages/News.tsx b/src/app/components/pages/News.tsx index fdb70f9a39..9d952338cd 100644 --- a/src/app/components/pages/News.tsx +++ b/src/app/components/pages/News.tsx @@ -1,4 +1,4 @@ -import {Button, Col, Container, Row} from "reactstrap"; +import {Button, Col, Row} from "reactstrap"; import {NewsCard} from "../elements/cards/NewsCard"; import React, { useEffect } from "react"; import {TitleAndBreadcrumb} from "../elements/TitleAndBreadcrumb"; @@ -7,8 +7,8 @@ import {useGetNewsPodListQuery} from "../../state"; import {ShowLoadingQuery} from "../handlers/ShowLoadingQuery"; import {NEWS_PODS_PER_PAGE, siteSpecific} from "../../services"; import { IsaacPodDTO } from "../../../IsaacApiTypes"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import { GenericPageSidebar } from "../elements/sidebar/GenericPageSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; export const News = () => { const [page, setPage] = React.useState(0); @@ -32,30 +32,32 @@ export const News = () => { "Get all the latest news about Isaac Science.", "Get all the latest news about Ada Computer Science, and read the stories of recent graduates who now have exciting careers in computer science."); - return - - - - - - {allNews.length === 0 ? -

No news to display...

} - defaultErrorTitle={"Error fetching news stories"} - /> : - <> - - {allNews.map((n, i) => - - )} - -
- -
- - } -
-
-
; + return + + + } + sidebar={siteSpecific( + , + undefined + )} + > + {allNews.length === 0 ? +

No news to display...

} + defaultErrorTitle={"Error fetching news stories"} + /> : + <> + + {allNews.map((n, i) => + + )} + +
+ +
+ + } +
; }; diff --git a/src/app/components/pages/Programmes.tsx b/src/app/components/pages/Programmes.tsx index 8437ed7dfe..1c322d695f 100644 --- a/src/app/components/pages/Programmes.tsx +++ b/src/app/components/pages/Programmes.tsx @@ -1,11 +1,11 @@ import React, { useEffect, useState } from "react"; -import { Container } from "reactstrap"; import { TitleAndBreadcrumb } from "../elements/TitleAndBreadcrumb"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import { ShowLoading } from "../handlers/ShowLoading"; import { IsaacProgrammeDTO, ProgrammeCard } from "../elements/cards/ProgrammeCard"; import { ContentDTO } from "../../../IsaacApiTypes"; import { ProgrammesSidebar } from "../elements/sidebar/ProgrammesSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; +import { siteSpecific } from "../../services"; const mockFetchProgrammes = (): Promise => new Promise((resolve) => @@ -88,19 +88,21 @@ export const Programmes = () => { } }, [programmes]); - return - - - - - { - return
    - {programmes.map((programme) => ( - - ))} -
; - }} /> -
-
-
; + return + } + sidebar={siteSpecific( + , + undefined + )} + > + { + return
    + {programmes.map((programme) => ( + + ))} +
; + }} /> +
; }; diff --git a/src/app/components/pages/Question.tsx b/src/app/components/pages/Question.tsx index 43b0a63d64..791149f8d5 100644 --- a/src/app/components/pages/Question.tsx +++ b/src/app/components/pages/Question.tsx @@ -1,5 +1,5 @@ import React from "react"; -import {Button, Col, Container, Row} from "reactstrap"; +import {Button, Col, Row} from "reactstrap"; import {useLocation, useParams} from "react-router-dom"; import {goToSupersededByQuestion, selectors, useAppDispatch, useAppSelector, useGetGameboardByIdQuery, useGetQuestionQuery} from "../../state"; import {IsaacQuestionPageDTO} from "../../../IsaacApiTypes"; @@ -31,7 +31,6 @@ import {CanonicalHrefElement} from "../navigation/CanonicalHrefElement"; import classNames from "classnames"; import { RevisionWarningBanner } from "../navigation/RevisionWarningBanner"; import { LLMFreeTextQuestionInfoBanner } from "../navigation/LLMFreeTextQuestionInfoBanner"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import { skipToken } from "@reduxjs/toolkit/query"; import { ShowLoadingQuery } from "../handlers/ShowLoadingQuery"; import { NotFound } from "./NotFound"; @@ -41,6 +40,7 @@ import { QuestionMetaData } from "../elements/QuestionMetadata"; import { getAccessibilityTags, useAccessibilitySettings } from "../../services/accessibility"; import { GameboardContentSidebar } from "../elements/sidebar/GameboardContentSidebar"; import { QuestionSidebar } from "../elements/sidebar/RelatedContentSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; interface QuestionPageProps{ questionIdOverride?: string; preview?: boolean; @@ -77,72 +77,73 @@ export const Question = ({questionIdOverride, preview}: QuestionPageProps) => { const audienceViews = determineAudienceViews(doc.audience, navigation.creationContext); return - - - {isFastTrack && fastTrackProgressEnabledBoards.includes(gameboardId || "") && } - - {isDefined(gameboardId) + + } + sidebar={siteSpecific( + isDefined(gameboardId) ? - : - } - - {!preview && } - - - {isPhy && } - - {accessibilitySettings?.SHOW_INACCESSIBLE_WARNING && getAccessibilityTags(doc.tags).map(tag => )} - - - - - - - {isAda && } - - {pageContainsLLMFreeTextQuestion && } - - - - - - - - {doc.supersededBy && isStudent(user) &&
- This question {" "} - .
- However, if you were assigned this version, you should complete it. -
} - - {doc.attribution &&

- - {doc.attribution} - -

} - - - - {isAda && doc.relatedContent && !isFastTrack && } - -
-
-
-
+ : , + undefined + )} + > + {isFastTrack && fastTrackProgressEnabledBoards.includes(gameboardId || "") && } + {!preview && } + + + {isPhy && } + + {accessibilitySettings?.SHOW_INACCESSIBLE_WARNING && getAccessibilityTags(doc.tags).map(tag => )} + + + + + + + {isAda && } + + {pageContainsLLMFreeTextQuestion && } + + + + + + + + {doc.supersededBy && isStudent(user) &&
+ This question {" "} + .
+ However, if you were assigned this version, you should complete it. +
} + + {doc.attribution &&

+ + {doc.attribution} + +

} + + + + {isAda && doc.relatedContent && !isFastTrack && } + +
+
;} } />; diff --git a/src/app/components/pages/QuestionDecks.tsx b/src/app/components/pages/QuestionDecks.tsx index b2bdd6b882..e6950b64c9 100644 --- a/src/app/components/pages/QuestionDecks.tsx +++ b/src/app/components/pages/QuestionDecks.tsx @@ -1,14 +1,14 @@ import React from "react"; -import { Container } from "reactstrap"; import { TitleAndBreadcrumb } from "../elements/TitleAndBreadcrumb"; import { isFullyDefinedContext, isSingleStageContext, useUrlPageTheme } from "../../services/pageContext"; import { PageFragment } from "../elements/PageFragment"; import { Loading } from "../handlers/IsaacSpinner"; import { PageContextState } from "../../../IsaacAppTypes"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import { PageMetadata } from "../elements/PageMetadata"; import { QuestionDecksSidebar } from "../elements/sidebar/QuestionDecksSidebar"; import { validQuestionDeckStageSubjectPairs } from "../../services/constants"; +import { PageContainer } from "../elements/layout/PageContainer"; +import { siteSpecific } from "../../services"; export const QuestionDecks = () => { const pageContext = useUrlPageTheme(); @@ -21,21 +21,23 @@ export const QuestionDecks = () => { return ; } - return - - - - - - - - - ; + return + } + sidebar={siteSpecific( + , + undefined + )} + > + + + ; }; diff --git a/src/app/components/pages/QuestionFinder.tsx b/src/app/components/pages/QuestionFinder.tsx index cd2927e565..12471cf1f3 100644 --- a/src/app/components/pages/QuestionFinder.tsx +++ b/src/app/components/pages/QuestionFinder.tsx @@ -44,10 +44,9 @@ import {MetaDescription} from "../elements/MetaDescription"; import {CanonicalHrefElement} from "../navigation/CanonicalHrefElement"; import classNames from "classnames"; import queryString from "query-string"; -import {Button, CardBody, Col, Container, Label, Row} from "reactstrap"; +import {Button, CardBody, Col, Label, Row} from "reactstrap"; import {ChoiceTree, getChoiceTreeLeaves, QuestionFinderFilterPanel} from "../elements/panels/QuestionFinderFilterPanel"; import {TierID} from "../elements/svg/HierarchyFilter"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import { ListView } from "../elements/list-groups/ListView"; import { PageFragment } from "../elements/PageFragment"; import { RenderNothing } from "../elements/RenderNothing"; @@ -62,6 +61,7 @@ import { skipToken } from "@reduxjs/toolkit/query"; import { ShowLoadingQuery } from "../handlers/ShowLoadingQuery"; import { QuestionFinderSidebar } from "../elements/sidebar/QuestionFinderSidebar"; import {Immutable} from "immer"; +import { PageContainer } from "../elements/layout/PageContainer"; // Type is used to ensure that we check all query params if a new one is added in the future const FILTER_PARAMS = ["query", "topics", "fields", "subjects", "stages", "difficulties", "examBoards", "book", "excludeBooks", "statuses", "randomSeed"] as const; @@ -462,15 +462,16 @@ export const QuestionFinder = () => { const crumb = isPhy && isFullyDefinedContext(pageContext) && generateSubjectLandingPageCrumbFromContext(pageContext); - return - - - + return + } + sidebar={siteSpecific( { selections, setSelections, applyFilters: searchAndUpdateURL, clearFilters, validFiltersSelected, searchDisabled, setSearchDisabled - }} hideButton/> - - - - - - {siteSpecific( -
- {(pageContext?.subject && pageContext.stage) - ?
-

- The questions shown on this page have been filtered to only show those that are relevant to {getHumanContext(pageContext)}. - You can browse all questions here. -

-
- : <>Use our question finder to find questions to try on topics in Physics, Maths, Chemistry and Biology. - Use our practice questions to become fluent in topics and then take your understanding and problem solving skills to the next level with our challenge questions.} -
, - - )} -
- - - {isAda && - - - debouncedSearchHandler(e.target.value)} - onSearch={searchAndUpdateURL} - /> - - } - - {isPhy && } - - - {isAda && - { - if (isDefined(randomSeed)) { - // on Ada, if randomSeed is defined, we need to unset it before running the search. - // since it is state, running this first won't necessarily have it updated when searchAndUpdateURL is called. - // as such, a useEffect above with randomSeed as a dep will run searchAndUpdateURL for us, instead of here. - setRandomSeed(undefined); - } else { - searchAndUpdateURL(); - } - }, - clearFilters, - validFiltersSelected, searchDisabled, setSearchDisabled - }} /> - } - - - - {siteSpecific( - <>Select {filteringByStatus ? "more" : "some"} filters to start searching., - Please select and apply filters. - )} - - - } - maintainOnRefetch={pageCount > 1} - thenRender={({ results: questions, totalResults: totalQuestions, nextSearchOffset, moreResultsAvailable }, isStale) => { - return <> - - -
- {questions && questions.length > 0 - ? <> - Showing {questions.length} - {(totalQuestions ?? 0) > 0 && !filteringByStatus && <> of {totalQuestions}} - . - - : isPhy && <>No results. - } -
- -
- - {questions?.length - ? - : isAda && (filteringByStatus - ? Could not load any results matching the requested filters. - : No results match the requested filters. - ) - } - -
- {(questions?.length ?? 0) > 0 && - - - - - + }} hideButton/>, + undefined + )} + > + + + + + {siteSpecific( +
+ {(pageContext?.subject && pageContext.stage) + ?
+

+ The questions shown on this page have been filtered to only show those that are relevant to {getHumanContext(pageContext)}. + You can browse all questions here. +

+
+ : <>Use our question finder to find questions to try on topics in Physics, Maths, Chemistry and Biology. + Use our practice questions to become fluent in topics and then take your understanding and problem solving skills to the next level with our challenge questions.} +
, + + )} +
+ + + {isAda && + + + debouncedSearchHandler(e.target.value)} + onSearch={searchAndUpdateURL} + /> + + } + + {isPhy && } + + + {isAda && + { + if (isDefined(randomSeed)) { + // on Ada, if randomSeed is defined, we need to unset it before running the search. + // since it is state, running this first won't necessarily have it updated when searchAndUpdateURL is called. + // as such, a useEffect above with randomSeed as a dep will run searchAndUpdateURL for us, instead of here. + setRandomSeed(undefined); + } else { + searchAndUpdateURL(); + } + }, + clearFilters, + validFiltersSelected, searchDisabled, setSearchDisabled + }} /> + } + + + + {siteSpecific( + <>Select {filteringByStatus ? "more" : "some"} filters to start searching., + Please select and apply filters. + )} + + + } + maintainOnRefetch={pageCount > 1} + thenRender={({ results: questions, totalResults: totalQuestions, nextSearchOffset, moreResultsAvailable }, isStale) => { + return <> + + +
+ {questions && questions.length > 0 + ? <> + Showing {questions.length} + {(totalQuestions ?? 0) > 0 && !filteringByStatus && <> of {totalQuestions}} + . + + : isPhy && <>No results. + } +
+ +
+ + {questions?.length + ? + : isAda && (filteringByStatus + ? Could not load any results matching the requested filters. + : No results match the requested filters. + ) } - ; - }} - /> - -
-
-
-
; + + + {(questions?.length ?? 0) > 0 && + + + + + + } + ; + }} + /> + + + ; }; diff --git a/src/app/components/pages/RegistrationAgeCheck.tsx b/src/app/components/pages/RegistrationAgeCheck.tsx index 0037d43969..96420287ee 100644 --- a/src/app/components/pages/RegistrationAgeCheck.tsx +++ b/src/app/components/pages/RegistrationAgeCheck.tsx @@ -4,7 +4,6 @@ import { Card, CardBody, Col, - Container, Form, FormFeedback, FormGroup, @@ -14,10 +13,10 @@ import { } from "reactstrap"; import {confirmThen, isAda, isPhy, SITE_TITLE, siteSpecific} from "../../services"; import {TitleAndBreadcrumb} from "../elements/TitleAndBreadcrumb"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import classNames from "classnames"; import { SignupSidebar } from "../elements/sidebar/SignupSidebar"; import { useNavigate } from "react-router"; +import { PageContainer } from "../elements/layout/PageContainer"; type AgePermission = "denied" | "additional_info" | "allowed"; @@ -51,71 +50,73 @@ export const RegistrationAgeCheck = () => { () => navigate("/register")); }; - return - - - - - - -
How old are you?
-

{siteSpecific( - "We can only create accounts for users 10 years old or over.", - "We can only create accounts for people over 13 years old." - )}

-
- - {setAgePermission("allowed");}} - color="primary" - invalid={submissionAttempted && agePermission === undefined} - /> - - - {isPhy && - {setAgePermission("additional_info");}} - color="primary" - invalid={submissionAttempted && agePermission === undefined} - /> - - } - - {setAgePermission("denied");}} - color="primary" - invalid={submissionAttempted && agePermission === undefined} - /> - - - Please make a selection. - - - {isAda &&
} - - - - - - - - -
-
-
-
-
-
; + return + } + sidebar={siteSpecific( + , + undefined + )} + > + + +
How old are you?
+

{siteSpecific( + "We can only create accounts for users 10 years old or over.", + "We can only create accounts for people over 13 years old." + )}

+
+ + {setAgePermission("allowed");}} + color="primary" + invalid={submissionAttempted && agePermission === undefined} + /> + + + {isPhy && + {setAgePermission("additional_info");}} + color="primary" + invalid={submissionAttempted && agePermission === undefined} + /> + + } + + {setAgePermission("denied");}} + color="primary" + invalid={submissionAttempted && agePermission === undefined} + /> + + + Please make a selection. + + + {isAda &&
} + + + + + + + + +
+
+
+
; }; diff --git a/src/app/components/pages/RegistrationAgeCheckFailed.tsx b/src/app/components/pages/RegistrationAgeCheckFailed.tsx index 6b3dc5cfc2..06409b4366 100644 --- a/src/app/components/pages/RegistrationAgeCheckFailed.tsx +++ b/src/app/components/pages/RegistrationAgeCheckFailed.tsx @@ -1,10 +1,10 @@ import React from "react"; -import {Button, Card, CardBody, Col, Container, Row} from "reactstrap"; +import {Button, Card, CardBody, Col, Row} from "reactstrap"; import {isAda, SITE_TITLE, siteSpecific} from "../../services"; import {TitleAndBreadcrumb} from "../elements/TitleAndBreadcrumb"; -import { SidebarLayout, MainContent } from "../elements/layout/SidebarLayout"; import { SignupSidebar } from "../elements/sidebar/SignupSidebar"; import { useNavigate } from "react-router"; +import { PageContainer } from "../elements/layout/PageContainer"; export const RegistrationAgeCheckFailed = () => { const navigate = useNavigate(); @@ -14,31 +14,33 @@ export const RegistrationAgeCheckFailed = () => { void navigate("/"); }; - return - - - - - - -

Unable to create account

-

Unfortunately, we aren't able to offer accounts to students under {siteSpecific("10", "13")} years old.

-

{siteSpecific( - <>But you can still access the whole site for free! You just won't be able to track your progress., - <>However, you can still access the whole site for free! However you will not be able to track your progress. - )}

- {isAda &&
} - - {isAda && - - } - - - - -
-
-
-
-
; + return + } + sidebar={siteSpecific( + , + undefined + )} + > + + +

Unable to create account

+

Unfortunately, we aren't able to offer accounts to students under {siteSpecific("10", "13")} years old.

+

{siteSpecific( + <>But you can still access the whole site for free! You just won't be able to track your progress., + <>However, you can still access the whole site for free! However you will not be able to track your progress. + )}

+ {isAda &&
} + + {isAda && + + } + + + + +
+
+
; }; diff --git a/src/app/components/pages/RegistrationAgeCheckParentalConsent.tsx b/src/app/components/pages/RegistrationAgeCheckParentalConsent.tsx index 9cbdb2b133..ddc0aaf0ee 100644 --- a/src/app/components/pages/RegistrationAgeCheckParentalConsent.tsx +++ b/src/app/components/pages/RegistrationAgeCheckParentalConsent.tsx @@ -1,11 +1,11 @@ import React from "react"; -import {Button, Card, CardBody, Col, Container, Input, Label, Row} from "reactstrap"; +import {Button, Card, CardBody, Col, Input, Label, Row} from "reactstrap"; import {siteSpecific} from "../../services"; import {TitleAndBreadcrumb} from "../elements/TitleAndBreadcrumb"; import { Link, useNavigate } from "react-router-dom"; -import { SidebarLayout, MainContent } from "../elements/layout/SidebarLayout"; import classNames from "classnames"; import { SignupSidebar } from "../elements/sidebar/SignupSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; export const RegistrationAgeCheckParentalConsent = () => { @@ -18,36 +18,38 @@ export const RegistrationAgeCheckParentalConsent = () => { void navigate("/register/student/details"); }; - return - - - - - - -

Before signing up to any online programme or website you should ask for permission from a parent or carer so they may check that it is appropriate for you to use.

-

Often websites store some information about you to give you the best possible experience on the site but you should always check what data is being kept to do this – you can read how we use your data to provide our service here.

-
- setParentalConsentCheckboxChecked(e?.target.checked)} - /> - -
- {siteSpecific(
,
)} - - - - - - - - -
-
-
-
-
; + return + } + sidebar={siteSpecific( + , + undefined + )} + > + + +

Before signing up to any online programme or website you should ask for permission from a parent or carer so they may check that it is appropriate for you to use.

+

Often websites store some information about you to give you the best possible experience on the site but you should always check what data is being kept to do this – you can read how we use your data to provide our service here.

+
+ setParentalConsentCheckboxChecked(e?.target.checked)} + /> + +
+ {siteSpecific(
,
)} + + + + + + + + +
+
+
; }; diff --git a/src/app/components/pages/RegistrationSetDetails.tsx b/src/app/components/pages/RegistrationSetDetails.tsx index fb0bab316b..24f1866ac9 100644 --- a/src/app/components/pages/RegistrationSetDetails.tsx +++ b/src/app/components/pages/RegistrationSetDetails.tsx @@ -1,5 +1,5 @@ import React, {useState} from "react"; -import {Button, Card, CardBody, Col, Container, Form, FormFeedback, FormGroup, Row,} from "reactstrap"; +import {Button, Card, CardBody, Col, Form, FormFeedback, FormGroup, Row,} from "reactstrap"; import {TitleAndBreadcrumb} from "../elements/TitleAndBreadcrumb"; import { confirmThen, @@ -33,11 +33,11 @@ import {ExigentAlert} from "../elements/ExigentAlert"; import classNames from "classnames"; import {StyledCheckbox} from "../elements/inputs/StyledCheckbox"; import {DobInput} from "../elements/inputs/DobInput"; -import {MainContent, SidebarLayout} from "../elements/layout/SidebarLayout"; import {SignupTab} from "../elements/panels/SignupTab"; import {scheduleTeacherOnboardingModalForNextOverviewVisit} from "../elements/modals/AdaTeacherOnboardingModal"; import { SignupSidebar } from "../elements/sidebar/SignupSidebar"; import { useNavigate } from "react-router"; +import { PageContainer } from "../elements/layout/PageContainer"; interface RegistrationSetDetailsProps { userRole: UserRole @@ -122,116 +122,118 @@ export const RegistrationSetDetails = ({userRole}: RegistrationSetDetailsProps) } }; - return - - - - - - - {createNewUserError && - -

Unable to create your account

-

{getRTKQueryErrorMessage(createNewUserError).message}

-
+ return + } + sidebar={siteSpecific( + , + undefined + )} + > + + + {createNewUserError && + +

Unable to create your account

+

{getRTKQueryErrorMessage(createNewUserError).message}

+
+ } + Create your{siteSpecific("", ` ${userRole.toLowerCase()}`)} account
} + rightColumn = {
+
+ + +
+ + setRegistrationUser(Object.assign({}, registrationUser, {password: password}))} + onValidityChange={setPasswordValid} + submissionAttempted={attemptedSignUp} + required={true} + /> + +
+ + {isPhy && + } - Create your{siteSpecific("", ` ${userRole.toLowerCase()}`)} account
} - rightColumn = { -
- - -
- - setRegistrationUser(Object.assign({}, registrationUser, {password: password}))} - onValidityChange={setPasswordValid} - submissionAttempted={attemptedSignUp} - required={true} - /> - -
- - {isPhy && - - } -
- -
- - setTosAccepted(e?.target.checked)} - invalid={attemptedSignUp && !tosAccepted} - label={I accept the terms of use.} - /> - - You must accept the terms to continue. - - - {isAda &&
} - - - - - - - - - } +
+ - - - - - ; +
+ + setTosAccepted(e?.target.checked)} + invalid={attemptedSignUp && !tosAccepted} + label={I accept the terms of use.} + /> + + You must accept the terms to continue. + + + {isAda &&
} + + + + + + + + + } + /> + + + ; }; diff --git a/src/app/components/pages/RegistrationSetPreferences.tsx b/src/app/components/pages/RegistrationSetPreferences.tsx index 3730b76085..2884fd91b7 100644 --- a/src/app/components/pages/RegistrationSetPreferences.tsx +++ b/src/app/components/pages/RegistrationSetPreferences.tsx @@ -1,5 +1,5 @@ import React, {useState} from "react"; -import {Button, Card, CardBody, Col, Container, Form, Label, Row,} from "reactstrap"; +import {Button, Card, CardBody, Col, Form, Label, Row,} from "reactstrap"; import {TitleAndBreadcrumb} from "../elements/TitleAndBreadcrumb"; import {Immutable} from "immer"; import {BooleanNotation, DisplaySettings, ProgrammingLanguage, ValidationUser} from "../../../IsaacAppTypes"; @@ -26,9 +26,9 @@ import {ProgrammingLanguageInput} from "../elements/inputs/ProgrammingLanguageIn import {useEmailPreferenceState, UserEmailPreferencesInput} from "../elements/inputs/UserEmailPreferencesInput"; import {ExigentAlert} from "../elements/ExigentAlert"; import classNames from "classnames"; -import {MainContent, SidebarLayout} from "../elements/layout/SidebarLayout"; import {SignupTab} from "../elements/panels/SignupTab"; import { SignupSidebar } from "../elements/sidebar/SignupSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; export const RegistrationSetPreferences = () => { @@ -75,62 +75,64 @@ export const RegistrationSetPreferences = () => { const canSavePreferences = !isPhy || allRequiredInformationIsPresent(userToUpdate, userPreferencesToUpdate, userContexts); - return - - - - - - - {updateCurrentUserError && - -

Unable to update your account

-

{getRTKQueryErrorMessage(updateCurrentUserError).message}

-
- } - -
Set your preferences
-

- Answering these questions will help us personalise the platform for you. You can skip this - or change your answers at any time under My Account. -

} - rightColumn = {
- - {siteSpecific(
,
)} + return + } + sidebar={siteSpecific( + , + undefined + )} + > + + + {updateCurrentUserError && + +

Unable to update your account

+

{getRTKQueryErrorMessage(updateCurrentUserError).message}

+
+ } + +
Set your preferences
+

+ Answering these questions will help us personalise the platform for you. You can skip this + or change your answers at any time under My Account. +

} + rightColumn = { + + {siteSpecific(
,
)} - {isAda && <> - - -
- } + {isAda && <> + + +
+ } - -

Get important information about the {SITE_TITLE} programme delivered to your inbox. These settings can be changed at any time.

- Frequency: expect one email per term for News{siteSpecific(" and a monthly bulletin for Events", "")}. Assignment notifications will be sent as needed by your teacher. -
- - {siteSpecific(
,
)} - - - - - - - - - } + +

Get important information about the {SITE_TITLE} programme delivered to your inbox. These settings can be changed at any time.

+ Frequency: expect one email per term for News{siteSpecific(" and a monthly bulletin for Events", "")}. Assignment notifications will be sent as needed by your teacher. +
+ - - - - - ; + {siteSpecific(
,
)} + + + + + + + + + } + /> + + + ; }; diff --git a/src/app/components/pages/RegistrationStart.tsx b/src/app/components/pages/RegistrationStart.tsx index bb71279f0a..ebf5332660 100644 --- a/src/app/components/pages/RegistrationStart.tsx +++ b/src/app/components/pages/RegistrationStart.tsx @@ -1,14 +1,14 @@ import React from "react"; -import {Button, Card, CardBody, Col, Container, Row} from "reactstrap"; +import {Button, Card, CardBody, Col, Row} from "reactstrap"; import {TitleAndBreadcrumb} from "../elements/TitleAndBreadcrumb"; import {RaspberryPiSignInButton} from "../elements/RaspberryPiSignInButton"; import {GoogleSignInButton} from "../elements/GoogleSignInButton"; import {isAda, isPhy, SITE_TITLE, siteSpecific} from "../../services"; -import { SidebarLayout, MainContent } from "../elements/layout/SidebarLayout"; import { MicrosoftSignInButton } from "../elements/MicrosoftSignInButton"; import { SsoHelpLink } from "./LogIn"; import { SignupSidebar } from "../elements/sidebar/SignupSidebar"; import { useNavigate } from "react-router"; +import { PageContainer } from "../elements/layout/PageContainer"; export const RegistrationStart = () => { const navigate = useNavigate(); @@ -24,50 +24,52 @@ export const RegistrationStart = () => { void navigate("/login"); }; - return - - - - - - - - -
-

{siteSpecific("Hello!", "How would you like to sign up?")}

-

Here, you can create an {SITE_TITLE} account, or log in to an existing one.

-
-
-
Create a new account with your email:
- -
-
-
Or log in with:
- {isAda &&
- -
} - - {isPhy &&
- -
} - {isPhy && } -
- {siteSpecific(
,
)} -
-
Already have an account?
- -
- - - {siteSpecific( - , - - )} - - - - - - - ; + return + } + sidebar={siteSpecific( + , + undefined + )} + > + + + + +
+

{siteSpecific("Hello!", "How would you like to sign up?")}

+

Here, you can create an {SITE_TITLE} account, or log in to an existing one.

+
+
+
Create a new account with your email:
+ +
+
+
Or log in with:
+ {isAda &&
+ +
} + + {isPhy &&
+ +
} + {isPhy && } +
+ {siteSpecific(
,
)} +
+
Already have an account?
+ +
+ + + {siteSpecific( + , + + )} + + + + + ; }; diff --git a/src/app/components/pages/RegistrationTeacherConnect.tsx b/src/app/components/pages/RegistrationTeacherConnect.tsx index 8e3641b81e..45e4392704 100644 --- a/src/app/components/pages/RegistrationTeacherConnect.tsx +++ b/src/app/components/pages/RegistrationTeacherConnect.tsx @@ -4,7 +4,6 @@ import { Card, CardBody, Col, - Container, Form, FormFeedback, FormGroup, @@ -25,7 +24,7 @@ import { import { authenticateWithTokenAfterPrompt } from "../elements/panels/TeacherConnections"; import { useNavigate } from "react-router"; import { SignupSidebar } from "../elements/sidebar/SignupSidebar"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; +import { PageContainer } from "../elements/layout/PageContainer"; export const RegistrationTeacherConnect = () => { const dispatch = useAppDispatch(); @@ -63,82 +62,84 @@ export const RegistrationTeacherConnect = () => { }, []); - return - - - - - - -
-

Connect your account to your teacher

- {siteSpecific( - <> -

If you've been given a group code by your teachers, enter it below. This lets your teachers set you work and see your progress. Learn more.

-

You can skip this. You don't need to join a group to use Isaac, and you can always do this later from the My Account page.

- , - <> -

This lets you see the work your teacher sets, and lets your teacher see your progress. You can join more than one group and you always have control over which groups you are in. Learn more

-

You can always skip this now and connect to your teacher later.

- - )} - - - - {isAda &&

Enter the code given by your teacher to join a group

} - - ) => setAuthenticationToken(e.target.value)} - invalid={submissionAttempted && !codeIsValid} - aria-describedby="codeValidationMessage" - value={authenticationToken} - /> -
- -
-
- - Please enter a valid code. - -
- {activeAuthorisations && activeAuthorisations.length > 0 && -
-
Connected teachers:
-
    - {activeAuthorisations.map((auth) => ( -
  • {extractTeacherName(auth)} - ({auth.email})
  • - ))} -
-
- } - -
- - {siteSpecific( - <> - - - - - - - , - <> - - - - - )} - -
-
-
-
-
-
; + return + } + sidebar={siteSpecific( + , + undefined + )} + > + + +
+

Connect your account to your teacher

+ {siteSpecific( + <> +

If you've been given a group code by your teachers, enter it below. This lets your teachers set you work and see your progress. Learn more.

+

You can skip this. You don't need to join a group to use Isaac, and you can always do this later from the My Account page.

+ , + <> +

This lets you see the work your teacher sets, and lets your teacher see your progress. You can join more than one group and you always have control over which groups you are in. Learn more

+

You can always skip this now and connect to your teacher later.

+ + )} + + + + {isAda &&

Enter the code given by your teacher to join a group

} + + ) => setAuthenticationToken(e.target.value)} + invalid={submissionAttempted && !codeIsValid} + aria-describedby="codeValidationMessage" + value={authenticationToken} + /> +
+ +
+
+ + Please enter a valid code. + +
+ {activeAuthorisations && activeAuthorisations.length > 0 && +
+
Connected teachers:
+
    + {activeAuthorisations.map((auth) => ( +
  • {extractTeacherName(auth)} - ({auth.email})
  • + ))} +
+
+ } + +
+ + {siteSpecific( + <> + + + + + + + , + <> + + + + + )} + +
+
+
+
; }; diff --git a/src/app/components/pages/RevisionDetailPage.tsx b/src/app/components/pages/RevisionDetailPage.tsx index 210697bdcd..6fd16044ee 100644 --- a/src/app/components/pages/RevisionDetailPage.tsx +++ b/src/app/components/pages/RevisionDetailPage.tsx @@ -1,9 +1,7 @@ import React from "react"; -import { Container } from "reactstrap"; import { TitleAndBreadcrumb } from "../elements/TitleAndBreadcrumb"; import { getThemeFromTags } from "../../services/pageContext"; import { useGetRevisionPageQuery } from "../../state/slices/api/revisionApi"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import { LoadingPlaceholder, ShowLoadingQuery } from "../handlers/ShowLoadingQuery"; import { IsaacRevisionDetailPageDTO, QuizSummaryDTO } from "../../../IsaacApiTypes"; import { convertToALVIGameboards, ListView } from "../elements/list-groups/ListView"; @@ -12,33 +10,37 @@ import { MetadataContainer, MetadataContainerLink } from "../elements/panels/Met import { PageMetadata } from "../elements/PageMetadata"; import { ContentControlledSidebar } from "../elements/sidebar/ContentControlledSidebar"; import { useParams } from "react-router"; +import { PageContainer } from "../elements/layout/PageContainer"; +import { siteSpecific } from "../../services"; export const RevisionPage = () => { const { pageId } = useParams(); const revisionPageQuery = useGetRevisionPageQuery({id: pageId ?? ""}); - return - + return + } + sidebar={siteSpecific( + , + undefined + )} + > { - return - - - {isStale ? : } - - ; + return isStale ? : ; }} /> - ; + ; }; const RevisionPageInternal = ({page}: {page: IsaacRevisionDetailPageDTO}) => { diff --git a/src/app/components/pages/SetAssignments.tsx b/src/app/components/pages/SetAssignments.tsx index 3fd9094cea..62ded8c631 100644 --- a/src/app/components/pages/SetAssignments.tsx +++ b/src/app/components/pages/SetAssignments.tsx @@ -5,7 +5,6 @@ import { Card, CardBody, Col, - Container, Input, Label, Row, @@ -53,7 +52,6 @@ import {BoardAssignee, AssignmentBoardOrder, Boards} from "../../../IsaacAppType import {BoardCard} from "../elements/cards/BoardCard"; import {RenderNothing} from "../elements/RenderNothing"; import {SortItemHeader} from "../elements/SortableItemHeader"; -import {MainContent, SidebarLayout} from "../elements/layout/SidebarLayout"; import {HorizontalScroller} from "../elements/inputs/HorizontalScroller"; import classNames from "classnames"; import {PromptBanner} from "../elements/cards/PromptBanner"; @@ -62,6 +60,7 @@ import { SetAssignmentsModal } from "../elements/modals/SetAssignmentsModal"; import { PageFragment } from "../elements/PageFragment"; import { useHistoryState } from "../../state/actions/history"; import { SetAssignmentsSidebar } from "../elements/sidebar/SetAssignmentsSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; interface SetAssignmentsTableProps { user: RegisteredUserDTO; @@ -377,11 +376,13 @@ export const SetAssignments = () => { const sortDisabled = !haveAllBoards; - return - - + return + } + sidebar={siteSpecific( { boardCreator={boardCreator} setBoardCreator={setBoardCreator} sortDisabled={sortDisabled} forceAllBoards={forceAllBoards} hideButton - /> - - - - - {isPhy && + />, + undefined + )} + > + + + + {isPhy && + <> + + {isGroupsEmptyState && + You have not created any groups to assign work to. + Please create a group here first. + } + {isBoardsEmptyState ? + + You have no {siteSpecific("question decks", "quizzes")} to assign. Use one of the + options above to find one. + + : <> - - {isGroupsEmptyState && - You have not created any groups to assign work to. - Please create a group here first. - } - {isBoardsEmptyState ? - - You have no {siteSpecific("question decks", "quizzes")} to assign. Use one of the - options above to find one. - - : - <> -
- Use the assignment schedule page to view - assignments by start date and due date. -
-
- - } - +
+ Use the assignment schedule page to view + assignments by start date and due date. +
+
} - {isAda && <> - {isGroupsEmptyState && - - } -

Your quizzes

-
- {boards && boards.totalResults > 0 && -
-

{`You have ${boards.boards.length} created quiz${boards.boards.length > 1 ? "zes" : ""}.`}

-
+ + + } + {isAda && <> + {isGroupsEmptyState && + - - -
+ }} + /> + } +

Your quizzes

+
+ {boards && boards.totalResults > 0 && +
+

{`You have ${boards.boards.length} created quiz${boards.boards.length > 1 ? "zes" : ""}.`}

- } - {!isBoardsEmptyState && <> - {isAda && <> - - {boardView === BoardViews.card && -
+ } + {!isBoardsEmptyState && <> + {isAda && <> + + {boardView === BoardViews.card && + + } + {boardView === BoardViews.card && <> + + + + + + } - - {boards && boards.boards &&
- {boardView == BoardViews.card ? - // Card view - <> - - {filteredBoards?.map(board => - - openAssignModal(board)} - /> - )} - -
-

Showing {filteredBoards?.length} of {boards.totalResults} -

- {boards.boards.length < boards.totalResults && - } -
- - : - // Table view - } -
} -
- - } - - - ; +
+ } + + {boards && boards.boards &&
+ {boardView == BoardViews.card ? + // Card view + <> + + {filteredBoards?.map(board => + + openAssignModal(board)} + /> + )} + +
+

Showing {filteredBoards?.length} of {boards.totalResults} +

+ {boards.boards.length < boards.totalResults && + } +
+ + : + // Table view + } +
} +
+ + } + ; }; diff --git a/src/app/components/pages/Support.tsx b/src/app/components/pages/Support.tsx index 3a44796d3c..29b84d2adc 100644 --- a/src/app/components/pages/Support.tsx +++ b/src/app/components/pages/Support.tsx @@ -1,5 +1,5 @@ import React from "react"; -import {Container, TabContent, TabPane} from "reactstrap"; +import {TabContent, TabPane} from "reactstrap"; import {Route} from "react-router-dom"; import {TitleAndBreadcrumb} from "../elements/TitleAndBreadcrumb"; import {Navigate, useNavigate, useParams} from "react-router"; @@ -9,10 +9,10 @@ import fromPairs from "lodash/fromPairs"; import {PageFragment} from "../elements/PageFragment"; import {NotFound} from "./NotFound"; import {MetaDescription} from "../elements/MetaDescription"; -import { MainContent, SidebarLayout } from "../elements/layout/SidebarLayout"; import { StyledTabPicker } from "../elements/inputs/StyledTabPicker"; import { PageMetadata } from "../elements/PageMetadata"; import { FAQSidebar } from "../elements/sidebar/FAQSidebar"; +import { PageContainer } from "../elements/layout/PageContainer"; type SupportType = "student" | "teacher" | "tutor"; @@ -135,13 +135,15 @@ export const Support = () => { "teacher": "Got a question about Ada Computer Science? Read our teacher FAQs. Get GCSE and A level support today!", }); - return - {/* TODO replace this icon */} - {isAda && isDefined(type) && type !== "tutor" && } - + return + } + sidebar={siteSpecific( {Object.values(section.categories).map((category, index) => { onClick={() => activeTabChanged(index)} onKeyDown={ifKeyIsEnter(() => activeTabChanged(index))} /> )} - - - {siteSpecific( - <> - - - {Object.values(section.categories).map((category, index) => - - - - )} - - , - - {fromPairs(Object.values(section.categories).map((category, index) => { - return [category.title, ]; - }))} - - )} - - - ; + , + undefined + )} + > + {isAda && isDefined(type) && type !== "tutor" && } + {siteSpecific( + <> + + + {Object.values(section.categories).map((category, index) => + + + + )} + + , + + {fromPairs(Object.values(section.categories).map((category, index) => { + return [category.title, ]; + }))} + + )} + ; }; diff --git a/src/app/components/pages/quizzes/MyQuizzes.tsx b/src/app/components/pages/quizzes/MyQuizzes.tsx index e83e0be1af..2ae3c0d053 100644 --- a/src/app/components/pages/quizzes/MyQuizzes.tsx +++ b/src/app/components/pages/quizzes/MyQuizzes.tsx @@ -32,7 +32,7 @@ import {Spacer} from "../../elements/Spacer"; import {Tabs} from "../../elements/Tabs"; import {PageFragment} from "../../elements/PageFragment"; import { SortItemHeader } from "../../elements/SortableItemHeader"; -import { Card, CardBody, Button, Table, Container, Alert, Row, Col, Label, Input } from "reactstrap"; +import { Card, CardBody, Button, Table, Alert, Row, Col, Label, Input } from "reactstrap"; import orderBy from "lodash/orderBy"; import classNames from "classnames"; import StyledToggle from "../../elements/inputs/StyledToggle"; @@ -40,12 +40,12 @@ import { TrLink } from "../../elements/tables/TableLinks"; import { StyledSelect } from "../../elements/inputs/StyledSelect"; import { CollapsibleContainer } from "../../elements/CollapsibleContainer"; import { FilterCount } from "../../elements/svg/FilterCount"; -import { MainContent, SidebarLayout } from "../../elements/layout/SidebarLayout"; import { HexIcon } from "../../elements/svg/HexIcon"; import { CardGrid } from "../../elements/CardGrid"; import { HorizontalScroller } from "../../elements/inputs/HorizontalScroller"; import { PageMetadata } from "../../elements/PageMetadata"; import { MyQuizzesSidebar } from "../../elements/sidebar/MyQuizzesSidebar"; +import { PageContainer } from "../../elements/layout/PageContainer"; export interface QuizzesPageProps { user: RegisteredUserDTO; @@ -457,59 +457,61 @@ export const MyQuizzes = ({user}: QuizzesPageProps) => { : "No practice tests match your filters." }; - return - - + return + } + sidebar={siteSpecific( - - - } /> - - { - void navigate({...location, hash: tabAnchors[index - 1]}, {replace: true}); - setBoardOrder(index === 1 ? QuizzesBoardOrder.dueDate : QuizzesBoardOrder.title); - }}> - {{ - ["Assigned tests"]: - Your test assignments failed to load, please try refreshing the page.} - > -
- {tabTopContent} - {displayMode === "table" ? - - - - : } -
-
, - ["My practice tests"]: - Your practice test attempts failed to load, please try refreshing the page.} - > -
- {tabTopContent} - {displayMode === "table" ? - - - - : } -
-
, - }} -
-
-
-
; + />, + undefined + )} + > + + } /> + + { + void navigate({...location, hash: tabAnchors[index - 1]}, {replace: true}); + setBoardOrder(index === 1 ? QuizzesBoardOrder.dueDate : QuizzesBoardOrder.title); + }}> + {{ + ["Assigned tests"]: + Your test assignments failed to load, please try refreshing the page.} + > +
+ {tabTopContent} + {displayMode === "table" ? + + + + : } +
+
, + ["My practice tests"]: + Your practice test attempts failed to load, please try refreshing the page.} + > +
+ {tabTopContent} + {displayMode === "table" ? + + + + : } +
+
, + }} +
+ ; }; diff --git a/src/app/components/pages/quizzes/PracticeQuizzes.tsx b/src/app/components/pages/quizzes/PracticeQuizzes.tsx index 9ffd04b2d4..db167c050d 100644 --- a/src/app/components/pages/quizzes/PracticeQuizzes.tsx +++ b/src/app/components/pages/quizzes/PracticeQuizzes.tsx @@ -1,19 +1,19 @@ import React, { useEffect, useState } from "react"; -import { Input, Col, Container } from "reactstrap"; +import { Input, Col } from "reactstrap"; import { generateSubjectLandingPageCrumbFromContext, TitleAndBreadcrumb } from "../../elements/TitleAndBreadcrumb"; -import { getFilteredStageOptions, isAda, isDefined, isLoggedIn, isPhy, LearningStage, sortByStringValue, STAGE_TO_LEARNING_STAGE, Subjects, TAG_ID, tags } from "../../../services"; +import { getFilteredStageOptions, isAda, isDefined, isLoggedIn, isPhy, LearningStage, siteSpecific, sortByStringValue, STAGE_TO_LEARNING_STAGE, Subjects, TAG_ID, tags } from "../../../services"; import { AudienceContext, QuizSummaryDTO, Stage } from "../../../../IsaacApiTypes"; import { Tag} from "../../../../IsaacAppTypes"; import { ShowLoading } from "../../handlers/ShowLoading"; import { useGetAvailableQuizzesQuery } from "../../../state/slices/api/quizApi"; import { PageFragment } from "../../elements/PageFragment"; -import { MainContent, SidebarLayout } from "../../elements/layout/SidebarLayout"; import { isFullyDefinedContext, useUrlPageTheme } from "../../../services/pageContext"; import { selectors, useAppSelector } from "../../../state"; import { ListView } from "../../elements/list-groups/ListView"; import classNames from "classnames"; import { PageMetadata } from "../../elements/PageMetadata"; import { PracticeQuizzesSidebar } from "../../elements/sidebar/PracticeQuizzesSidebar"; +import { PageContainer } from "../../elements/layout/PageContainer"; export const PracticeQuizzes = () => { const pageContext = useUrlPageTheme(); @@ -88,42 +88,44 @@ export const PracticeQuizzes = () => { const sidebarProps = {filterText, setFilterText, filterTags, setFilterTags, tagCounts: tagCounts(), filterStages, setFilterStages, stageCounts: stageCounts()}; - return - - - - - - - - {!user - ? You must be logged in to view practice tests. - : - {quizzes && <> - {quizzes.length === 0 &&

There are no practice tests currently available.

} - - {isAda && setFilterText(e.target.value)} />} -
; - return - - - {activeTab === MANAGE_QUIZ_TAB.set + return + } + sidebar={siteSpecific( + activeTab === MANAGE_QUIZ_TAB.set ? : - } - - - - - - {{ - [siteSpecific("Set tests", "Available tests")]: - - {undeprecatedQuizzes && <> -

The following tests are available to set to your groups.

- - {undeprecatedQuizzes.length === 0 &&

There are no tests you can set which match your search term.

} - - {siteSpecific( - , - - {undeprecatedQuizzes.map(quiz => - - -
- {quiz.title} - {roleVisibilitySummary(quiz)} -
- - - + + + + Preview + + + + + + Actions + + + dispatch(openActiveModal(SetQuizzesModal({quiz: quiz})))} style={{zIndex: '1'}}> Set test - - - - - Preview + + + + + Preview + - - - - - Actions - - - dispatch(openActiveModal(SetQuizzesModal({quiz: quiz})))} style={{zIndex: '1'}}> - Set test - - - - - Preview - - - - - -
-
)} -
)} - } -
, - - [siteSpecific("Manage tests", "Previously set tests")]: - <> - {isAda &&
- -
} - - {/* Ada filters */} - {showFilters && (rowFiltersView - ? - - {titleFilterInput} - {setDateFilterInput} - - - {groupFilterInput} - {dueDateFilterInput} - - - : - {titleFilterInput} - {groupFilterInput} - {setDateFilterInput} - {dueDateFilterInput} - ) + + + + + )} + )} + } + , + + [siteSpecific("Manage tests", "Previously set tests")]: + <> + {isAda &&
+ +
} + + {/* Ada filters */} + {showFilters && (rowFiltersView + ? + + {titleFilterInput} + {setDateFilterInput} + + + {groupFilterInput} + {dueDateFilterInput} + + + : + {titleFilterInput} + {groupFilterInput} + {setDateFilterInput} + {dueDateFilterInput} + ) + } + + Tests you have assigned have failed to load, please try refreshing the page.} + thenRender={quizAssignments => { + let quizAssignmentsWithGroupNames: AppQuizAssignment[] = quizAssignments.map(assignment => { + const groupName = persistence.load(KEY.ANONYMISE_GROUPS) === "YES" + ? `Demo Group ${assignment.groupId}` + : groupIdToName[assignment.groupId as number] ?? "Unknown Group"; + return {...assignment, groupName}; + }).reverse(); + + if (showFilters || isPhy) { + const filters = []; + if (manageQuizzesTitleFilter !== "") { + filters.push((assignment : AppQuizAssignment) => assignment.quizSummary?.title?.toLowerCase().includes(manageQuizzesTitleFilter)); + } + if (manageQuizzesGroupNameFilter !== "") { + filters.push((assignment : AppQuizAssignment) => assignment.groupName?.toLowerCase().includes(manageQuizzesGroupNameFilter.toLowerCase())); + } + if (quizStartDate && !isNaN(quizStartDate.valueOf())) { + filters.push((assignment : AppQuizAssignment) => { + return filterByDate(quizSetDateFilterType, assignment.scheduledStartDate ?? assignment.creationDate, quizStartDate); + }); + } + if (quizDueDate && !isNaN(quizDueDate.valueOf())) { + filters.push((assignment : AppQuizAssignment) => { + return filterByDate(quizDueDateFilterType, assignment.dueDate, quizDueDate); + }); + } + quizAssignmentsWithGroupNames = quizAssignmentsWithGroupNames.filter(filters.reduce((acc, filter) => (assignment) => acc(assignment) && filter(assignment), () => true)); } - Tests you have assigned have failed to load, please try refreshing the page.} - thenRender={quizAssignments => { - let quizAssignmentsWithGroupNames: AppQuizAssignment[] = quizAssignments.map(assignment => { - const groupName = persistence.load(KEY.ANONYMISE_GROUPS) === "YES" - ? `Demo Group ${assignment.groupId}` - : groupIdToName[assignment.groupId as number] ?? "Unknown Group"; - return {...assignment, groupName}; - }).reverse(); - - if (showFilters || isPhy) { - const filters = []; - if (manageQuizzesTitleFilter !== "") { - filters.push((assignment : AppQuizAssignment) => assignment.quizSummary?.title?.toLowerCase().includes(manageQuizzesTitleFilter)); - } - if (manageQuizzesGroupNameFilter !== "") { - filters.push((assignment : AppQuizAssignment) => assignment.groupName?.toLowerCase().includes(manageQuizzesGroupNameFilter.toLowerCase())); - } - if (quizStartDate && !isNaN(quizStartDate.valueOf())) { - filters.push((assignment : AppQuizAssignment) => { - return filterByDate(quizSetDateFilterType, assignment.scheduledStartDate ?? assignment.creationDate, quizStartDate); - }); - } - if (quizDueDate && !isNaN(quizDueDate.valueOf())) { - filters.push((assignment : AppQuizAssignment) => { - return filterByDate(quizDueDateFilterType, assignment.dueDate, quizDueDate); - }); - } - quizAssignmentsWithGroupNames = quizAssignmentsWithGroupNames.filter(filters.reduce((acc, filter) => (assignment) => acc(assignment) && filter(assignment), () => true)); - } - - // an array of objects, each representing one test and the groups it is assigned to - const quizAssignment: QuizAssignmentProps[] = quizAssignmentsWithGroupNames.reduce((acc, assignment) => { - const existing = acc.find(q => q.assignedGroups.map(a => a.assignment.quizId).includes(assignment.quizId)); - if (existing) { - existing.assignedGroups.push({group: assignment.groupName, assignment: assignment}); - } else { - acc.push({user: user, assignedGroups: [{group: assignment.groupName, assignment: assignment}], index: 0}); - } - return acc; - }, [] as QuizAssignmentProps[]); - - // sort the outermost table by quiz title - quizAssignment.sort((a, b) => a.assignedGroups[0].assignment.quizSummary?.title?.localeCompare(b.assignedGroups[0].assignment.quizSummary?.title ?? "") ?? 0); - - return <> - {quizAssignments.length === 0 &&

You have not set any tests to your groups yet.

} - {quizAssignments.length > 0 && - {isAda && - - - {below["xs"](deviceSize) ? <> : below["lg"](deviceSize) ? : } - - } - - {quizAssignment.map((g, i) => )} - -
} - ; - }} - /> - - }} -
-
-
-
; + // an array of objects, each representing one test and the groups it is assigned to + const quizAssignment: QuizAssignmentProps[] = quizAssignmentsWithGroupNames.reduce((acc, assignment) => { + const existing = acc.find(q => q.assignedGroups.map(a => a.assignment.quizId).includes(assignment.quizId)); + if (existing) { + existing.assignedGroups.push({group: assignment.groupName, assignment: assignment}); + } else { + acc.push({user: user, assignedGroups: [{group: assignment.groupName, assignment: assignment}], index: 0}); + } + return acc; + }, [] as QuizAssignmentProps[]); + + // sort the outermost table by quiz title + quizAssignment.sort((a, b) => a.assignedGroups[0].assignment.quizSummary?.title?.localeCompare(b.assignedGroups[0].assignment.quizSummary?.title ?? "") ?? 0); + + return <> + {quizAssignments.length === 0 &&

You have not set any tests to your groups yet.

} + {quizAssignments.length > 0 && + {isAda && + + + {below["xs"](deviceSize) ? <> : below["lg"](deviceSize) ? : } + + } + + {quizAssignment.map((g, i) => )} + +
} + ; + }} + /> + + }} + + ; }; diff --git a/src/test/pages/__image_snapshots__/ada/Concept pages should have no visual regressions #0.png b/src/test/pages/__image_snapshots__/ada/Concept pages should have no visual regressions #0.png index 8b5a3ff621..92c7e87e4a 100644 Binary files a/src/test/pages/__image_snapshots__/ada/Concept pages should have no visual regressions #0.png and b/src/test/pages/__image_snapshots__/ada/Concept pages should have no visual regressions #0.png differ diff --git a/src/test/pages/__image_snapshots__/ada/LLM-marked question page should have no visual regressions #0.png b/src/test/pages/__image_snapshots__/ada/LLM-marked question page should have no visual regressions #0.png index 09e2cf5d9e..a1ab03cfbc 100644 Binary files a/src/test/pages/__image_snapshots__/ada/LLM-marked question page should have no visual regressions #0.png and b/src/test/pages/__image_snapshots__/ada/LLM-marked question page should have no visual regressions #0.png differ diff --git a/src/test/pages/__image_snapshots__/ada/Question types' regression test page should have no visual regressions #0.png b/src/test/pages/__image_snapshots__/ada/Question types' regression test page should have no visual regressions #0.png index 59cfb093b6..42ac588893 100644 Binary files a/src/test/pages/__image_snapshots__/ada/Question types' regression test page should have no visual regressions #0.png and b/src/test/pages/__image_snapshots__/ada/Question types' regression test page should have no visual regressions #0.png differ diff --git a/src/test/pages/__image_snapshots__/ada/Set Assignments should have no visual regressions #0.png b/src/test/pages/__image_snapshots__/ada/Set Assignments should have no visual regressions #0.png index 7dac765369..a9e3ad9efe 100644 Binary files a/src/test/pages/__image_snapshots__/ada/Set Assignments should have no visual regressions #0.png and b/src/test/pages/__image_snapshots__/ada/Set Assignments should have no visual regressions #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/Concept pages should have no visual regressions #0.png b/src/test/pages/__image_snapshots__/sci/Concept pages should have no visual regressions #0.png index 01d0a77e69..6bf2b796d8 100644 Binary files a/src/test/pages/__image_snapshots__/sci/Concept pages should have no visual regressions #0.png and b/src/test/pages/__image_snapshots__/sci/Concept pages should have no visual regressions #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/Groups page should have no visual regressions on group editor #0.png b/src/test/pages/__image_snapshots__/sci/Groups page should have no visual regressions on group editor #0.png index 47559c2547..4c34b3f0b6 100644 Binary files a/src/test/pages/__image_snapshots__/sci/Groups page should have no visual regressions on group editor #0.png and b/src/test/pages/__image_snapshots__/sci/Groups page should have no visual regressions on group editor #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/Groups page should have no visual regressions on group selector #0.png b/src/test/pages/__image_snapshots__/sci/Groups page should have no visual regressions on group selector #0.png index c182a41f88..6251196b41 100644 Binary files a/src/test/pages/__image_snapshots__/sci/Groups page should have no visual regressions on group selector #0.png and b/src/test/pages/__image_snapshots__/sci/Groups page should have no visual regressions on group selector #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/LLM-marked question page should have no visual regressions #0.png b/src/test/pages/__image_snapshots__/sci/LLM-marked question page should have no visual regressions #0.png index ca868f6892..c28dca4cda 100644 Binary files a/src/test/pages/__image_snapshots__/sci/LLM-marked question page should have no visual regressions #0.png and b/src/test/pages/__image_snapshots__/sci/LLM-marked question page should have no visual regressions #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Accessibility page #0.png b/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Accessibility page #0.png index f7da9554f1..12e3ac8522 100644 Binary files a/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Accessibility page #0.png and b/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Accessibility page #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Beta page #0.png b/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Beta page #0.png index 47cd29ba66..ae08a31b2f 100644 Binary files a/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Beta page #0.png and b/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Beta page #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Notifications page #0.png b/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Notifications page #0.png index e5d2e1c070..a8e5df3e50 100644 Binary files a/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Notifications page #0.png and b/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Notifications page #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Profile page #0.png b/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Profile page #0.png index 587c6fc7ff..741d46b908 100644 Binary files a/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Profile page #0.png and b/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Profile page #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Security page #0.png b/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Security page #0.png index 7c8c49b8fa..7c720e9039 100644 Binary files a/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Security page #0.png and b/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Security page #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Teacher connections page #0.png b/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Teacher connections page #0.png index f5f2ae628b..33076283fb 100644 Binary files a/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Teacher connections page #0.png and b/src/test/pages/__image_snapshots__/sci/My Account should have no visual regressions on Teacher connections page #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/My Assignments should have no visual regressions #0.png b/src/test/pages/__image_snapshots__/sci/My Assignments should have no visual regressions #0.png index 691d3c91a9..85750c0541 100644 Binary files a/src/test/pages/__image_snapshots__/sci/My Assignments should have no visual regressions #0.png and b/src/test/pages/__image_snapshots__/sci/My Assignments should have no visual regressions #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/My Gameboards should have no visual regressions in card view #0.png b/src/test/pages/__image_snapshots__/sci/My Gameboards should have no visual regressions in card view #0.png index 3eb3629710..cc1c97c7b8 100644 Binary files a/src/test/pages/__image_snapshots__/sci/My Gameboards should have no visual regressions in card view #0.png and b/src/test/pages/__image_snapshots__/sci/My Gameboards should have no visual regressions in card view #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/My Gameboards should have no visual regressions in table view #0.png b/src/test/pages/__image_snapshots__/sci/My Gameboards should have no visual regressions in table view #0.png index abe3c460c5..8f0cacaaab 100644 Binary files a/src/test/pages/__image_snapshots__/sci/My Gameboards should have no visual regressions in table view #0.png and b/src/test/pages/__image_snapshots__/sci/My Gameboards should have no visual regressions in table view #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/News page should have no visual regressions #0.png b/src/test/pages/__image_snapshots__/sci/News page should have no visual regressions #0.png index a3307d971d..ae43fc11eb 100644 Binary files a/src/test/pages/__image_snapshots__/sci/News page should have no visual regressions #0.png and b/src/test/pages/__image_snapshots__/sci/News page should have no visual regressions #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/Programmes page should have no visual regressions #0.png b/src/test/pages/__image_snapshots__/sci/Programmes page should have no visual regressions #0.png index 89fa25a3be..d57fd656a3 100644 Binary files a/src/test/pages/__image_snapshots__/sci/Programmes page should have no visual regressions #0.png and b/src/test/pages/__image_snapshots__/sci/Programmes page should have no visual regressions #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/Question types' regression test page should have no visual regressions #0.png b/src/test/pages/__image_snapshots__/sci/Question types' regression test page should have no visual regressions #0.png index f6c8092a79..5648ff7982 100644 Binary files a/src/test/pages/__image_snapshots__/sci/Question types' regression test page should have no visual regressions #0.png and b/src/test/pages/__image_snapshots__/sci/Question types' regression test page should have no visual regressions #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/Registration page should have no visual regressions #0.png b/src/test/pages/__image_snapshots__/sci/Registration page should have no visual regressions #0.png index b21091efcf..5b2ac1e24e 100644 Binary files a/src/test/pages/__image_snapshots__/sci/Registration page should have no visual regressions #0.png and b/src/test/pages/__image_snapshots__/sci/Registration page should have no visual regressions #0.png differ diff --git a/src/test/pages/__image_snapshots__/sci/Set Assignments should have no visual regressions #0.png b/src/test/pages/__image_snapshots__/sci/Set Assignments should have no visual regressions #0.png index 55d99ae2af..afb5bbfabe 100644 Binary files a/src/test/pages/__image_snapshots__/sci/Set Assignments should have no visual regressions #0.png and b/src/test/pages/__image_snapshots__/sci/Set Assignments should have no visual regressions #0.png differ