Skip to content

Commit 9e5bf0b

Browse files
authored
Merge pull request #1340 from isaacphysics/redesign/subject-overview-pages
Subject Overview Pages
2 parents 8970276 + 34aa4f2 commit 9e5bf0b

17 files changed

+259
-217
lines changed

src/app/components/elements/list-groups/AbstractListViewItem.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ export interface ListViewTagProps {
6262
}
6363

6464
export interface AbstractListViewItemProps extends ListGroupItemProps {
65-
icon: TitleIconProps;
6665
title: string;
66+
icon?: TitleIconProps;
6767
subject?: Subject;
6868
subtitle?: string;
6969
breadcrumb?: string[];
7070
status?: CompletionState;
71-
tags?: string[]
71+
tags?: string[];
7272
supersededBy?: string;
7373
linkTags?: ListViewTagProps[];
7474
quizTag?: string;
@@ -87,8 +87,8 @@ export const AbstractListViewItem = ({icon, title, subject, subtitle, breadcrumb
8787
fullWidth = fullWidth || below["sm"](deviceSize) || ((status || audienceViews || previewQuizUrl || quizButton) ? false : true);
8888
const colWidths = fullWidth ? [12,12,12,12,12] : isQuiz ? [12,6,6,6,6] : [12,8,7,6,7];
8989
const cardBody =
90-
<Row className="w-100 flex-row">
91-
<Col xs={colWidths[0]} md={colWidths[1]} lg={colWidths[2]} xl={colWidths[3]} xxl={colWidths[4]} className={classNames("d-flex", {"mt-3": isCard})}>
90+
<Row className="flex-row">
91+
<Col xs={colWidths[0]} md={colWidths[1]} lg={colWidths[2]} xl={colWidths[3]} xxl={colWidths[4]} className={classNames("d-flex", {"mt-3": isCard && linkTags?.length, "mb-3": isCard && !linkTags?.length})}>
9292
<div>
9393
{icon && (
9494
icon.type === "img" ? <img src={icon.icon} alt="" className="me-3"/>
@@ -145,8 +145,9 @@ export const AbstractListViewItem = ({icon, title, subject, subtitle, breadcrumb
145145
</Row>;
146146

147147
return <ListGroupItem {...rest} className={classNames("content-summary-item", rest.className)} data-bs-theme={subject}>
148-
{url ?
149-
<Link to={{pathname: url}} className="w-100"> {cardBody} </Link> :
150-
<div> {cardBody} </div>}
148+
{url
149+
? <Link to={{pathname: url}} className="w-100 h-100 align-items-start"> {cardBody} </Link>
150+
: cardBody
151+
}
151152
</ListGroupItem>;
152153
};

src/app/components/elements/list-groups/ListView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import classNames from "classnames";
1313

1414
export interface ListViewCardProps extends ListGroupItemProps {
1515
item: ShortcutResponse;
16-
icon: TitleIconProps;
16+
icon?: TitleIconProps;
1717
subject?: Subject;
1818
linkTags?: ListViewTagProps[];
1919
url?: string;

src/app/components/pages/Concepts.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {IsaacSpinner} from "../handlers/IsaacSpinner";
1111
import { ListView } from "../elements/list-groups/ListView";
1212
import { ContentTypeVisibility, LinkToContentSummaryList } from "../elements/list-groups/ContentSummaryListGroupItem";
1313
import { SubjectSpecificConceptListSidebar, MainContent, SidebarLayout, GenericConceptsSidebar } from "../elements/layout/SidebarLayout";
14-
import { isDefinedContext, useUrlPageTheme } from "../../services/pageContext";
14+
import { isFullyDefinedContext, useUrlPageTheme } from "../../services/pageContext";
1515

1616
// This component is Isaac Physics only (currently)
1717
export const Concepts = withRouter((props: RouteComponentProps) => {
@@ -85,7 +85,7 @@ export const Concepts = withRouter((props: RouteComponentProps) => {
8585

8686
const shortcutAndFilteredSearchResults = (shortcutResponse || []).concat(filteredSearchResults || []);
8787

88-
const crumb = isPhy && isDefinedContext(pageContext) && generateSubjectLandingPageCrumbFromContext(pageContext);
88+
const crumb = isPhy && isFullyDefinedContext(pageContext) && generateSubjectLandingPageCrumbFromContext(pageContext);
8989

9090
return (
9191
<Container id="search-page" { ...(pageContext?.subject && { "data-bs-theme" : pageContext.subject })}>

src/app/components/pages/QuestionFinder.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
HUMAN_STAGES,
1313
ISAAC_BOOKS,
1414
isAda,
15-
isDefinedContext,
15+
isFullyDefinedContext,
1616
isLoggedIn,
1717
isPhy,
1818
Item,
@@ -510,7 +510,7 @@ export const QuestionFinder = withRouter(({location}: RouteComponentProps) => {
510510
</div>;
511511
};
512512

513-
const crumb = isPhy && isDefinedContext(pageContext) && generateSubjectLandingPageCrumbFromContext(pageContext);
513+
const crumb = isPhy && isFullyDefinedContext(pageContext) && generateSubjectLandingPageCrumbFromContext(pageContext);
514514

515515
return <Container id="finder-page" className={classNames("mb-5")} { ...(pageContext?.subject && { "data-bs-theme" : pageContext.subject })}>
516516
<TitleAndBreadcrumb

src/app/components/pages/QuickQuizzes.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import React from "react";
22
import { RouteComponentProps, withRouter } from "react-router";
33
import { Container } from "reactstrap";
44
import { generateSubjectLandingPageCrumbFromContext, TitleAndBreadcrumb } from "../elements/TitleAndBreadcrumb";
5-
import { getHumanContext, isDefinedContext, useUrlPageTheme } from "../../services/pageContext";
5+
import { getHumanContext, isFullyDefinedContext, useUrlPageTheme } from "../../services/pageContext";
66
import { isPhy } from "../../services";
77

88
export const QuickQuizzes = withRouter((props: RouteComponentProps) => {
99
const pageContext = useUrlPageTheme();
1010

11-
const crumb = isPhy && isDefinedContext(pageContext) && generateSubjectLandingPageCrumbFromContext(pageContext);
11+
const crumb = isPhy && isFullyDefinedContext(pageContext) && generateSubjectLandingPageCrumbFromContext(pageContext);
1212

1313
return <Container data-bs-theme={pageContext?.subject}>
1414
<TitleAndBreadcrumb

src/app/components/pages/SubjectLandingPage.tsx

Lines changed: 79 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React, { useCallback, useEffect } from "react";
22
import { RouteComponentProps, withRouter } from "react-router";
3-
import { Card, Col, Container, Row } from "reactstrap";
3+
import { Button, Card, Col, Container, Row } from "reactstrap";
44
import { TitleAndBreadcrumb } from "../elements/TitleAndBreadcrumb";
5-
import { getHumanContext, isDefinedContext, isSingleStageContext, useUrlPageTheme } from "../../services/pageContext";
5+
import { getHumanContext, isFullyDefinedContext, isSingleStageContext, useUrlPageTheme } from "../../services/pageContext";
66
import { ListView, ListViewCards } from "../elements/list-groups/ListView";
77
import { getBooksForContext, getLandingPageCardsForContext } from "./subjectLandingPageComponents";
88
import { above, below, DOCUMENT_TYPE, EventStatusFilter, EventTypeFilter, STAGE, useDeviceSize } from "../../services";
@@ -50,7 +50,7 @@ const RandomQuestionBanner = ({context}: {context?: PageContextState}) => {
5050
searchDebounce();
5151
}, [searchDebounce]);
5252

53-
if (!context || !isDefinedContext(context) || !isSingleStageContext(context)) {
53+
if (!context || !isFullyDefinedContext(context) || !isSingleStageContext(context)) {
5454
return null;
5555
}
5656

@@ -106,18 +106,86 @@ const RandomQuestionBanner = ({context}: {context?: PageContextState}) => {
106106
</div>;
107107
};
108108

109-
export const SubjectLandingPage = withRouter((props: RouteComponentProps) => {
110-
const pageContext = useUrlPageTheme();
111-
const deviceSize = useDeviceSize();
112-
109+
export const LandingPageFooter = ({context}: {context: PageContextState}) => {
113110
const [getEventsList, eventsQuery] = useLazyGetEventsQuery();
114111
useEffect(() => {
115112
getEventsList({startIndex: 0, limit: 10, typeFilter: EventTypeFilter["All events"], statusFilter: EventStatusFilter["Upcoming events"], stageFilter: [STAGE.ALL]});
116113
}, []);
117114

118-
const books = getBooksForContext(pageContext);
115+
const books = getBooksForContext(context);
119116
// TODO: are we going to make subject-specific news?
120117
const {data: news} = useGetNewsPodListQuery({subject: "physics"});
118+
119+
return <Row className={classNames("mt-5 py-4 row-cols-1 row-cols-md-2")}>
120+
<div className="d-flex flex-column mt-3">
121+
{/* if there are books, display books. otherwise, display news */}
122+
{books.length > 0
123+
? <>
124+
<div className="d-flex mb-3 align-items-center gap-4 white-space-pre">
125+
<h4 className="m-0">{getHumanContext(context)} books</h4>
126+
<div className="section-divider-bold"/>
127+
</div>
128+
<Col className="d-flex flex-column">
129+
{books.slice(0, 2).map((book, index) => <Link key={index} to={book.path} className="book-container d-flex p-2 gap-3">
130+
<div className="book-image-container">
131+
<img src={book.image} alt={book.title} className="h-100"/>
132+
</div>
133+
<div className="d-flex flex-column">
134+
<h5 className="pt-2 pt-2 pb-1 m-0">{book.title}</h5>
135+
<div className="section-divider"/>
136+
<span className="text-decoration-none">
137+
This is some explanatory text about the book. It could be a brief description of the book, or a list of topics covered.
138+
</span>
139+
</div>
140+
</Link>)}
141+
{books.length > 2 && <Button tag={Link} color="keyline" to={`/publications`} className="btn mt-4 mx-5">View more books</Button>}
142+
</Col>
143+
</>
144+
: <>
145+
<div className="d-flex flex-column">
146+
<div className="d-flex mb-3 align-items-center gap-4 white-space-pre">
147+
<h4>News & Features</h4>
148+
<div className="section-divider-bold"/>
149+
</div>
150+
{news && <Row className="h-100">
151+
{news.slice(0, 2).map(newsItem => <Col xs={12} key={newsItem.id}>
152+
<NewsCard newsItem={newsItem} className="force-horizontal p-2" />
153+
</Col>)}
154+
</Row>}
155+
</div>
156+
</>
157+
}
158+
</div>
159+
<div className="d-flex flex-column mt-3">
160+
<div className="d-flex mb-3 align-items-center gap-4 white-space-pre">
161+
<h4 className="m-0">Events</h4>
162+
<div className="section-divider-bold"/>
163+
</div>
164+
<ShowLoadingQuery
165+
query={eventsQuery}
166+
defaultErrorTitle={"Error loading events list"}
167+
thenRender={({events}) => {
168+
// TODO: filter by audience, once that data is available
169+
const relevantEvents = events.filter(event => context?.subject && event.tags?.includes(context.subject)).slice(0, 2);
170+
return <Row className="h-100">
171+
{relevantEvents.length
172+
? relevantEvents.map((event, i) =>
173+
<Col xs={12} key={i}>
174+
{event && <EventCard event={event} className="force-horizontal p-2" />}
175+
</Col>
176+
)
177+
: <Col className="pt-3 pb-5">No events found for {getHumanContext(context)}. Check back soon!</Col>
178+
}
179+
</Row>;
180+
}}
181+
/>
182+
</div>
183+
</Row>;
184+
};
185+
186+
export const SubjectLandingPage = withRouter((props: RouteComponentProps) => {
187+
const pageContext = useUrlPageTheme();
188+
const deviceSize = useDeviceSize();
121189

122190
return <Container data-bs-theme={pageContext?.subject}>
123191
<TitleAndBreadcrumb
@@ -133,69 +201,8 @@ export const SubjectLandingPage = withRouter((props: RouteComponentProps) => {
133201

134202
<ListViewCards cards={getLandingPageCardsForContext(pageContext, below['md'](deviceSize))} showBlanks={!below['md'](deviceSize)} className="my-5" />
135203

136-
<Row className={classNames("mt-5 py-4 row-cols-1 row-cols-md-2")}>
137-
<div className="d-flex flex-column mt-3">
138-
{/* if there are books, display books. otherwise, display news */}
139-
{books.length > 0
140-
? <>
141-
<div className="d-flex mb-3 align-items-center gap-4 white-space-pre">
142-
<h4 className="m-0">{getHumanContext(pageContext)} books</h4>
143-
<div className="section-divider-bold"/>
144-
</div>
145-
<Col className="d-flex flex-column">
146-
{books.slice(0, 4).map((book, index) => <Link key={index} to={book.path} className="book-container d-flex p-2 gap-3">
147-
<div className="book-image-container">
148-
<img src={book.image} alt={book.title} className="h-100"/>
149-
</div>
150-
<div className="d-flex flex-column">
151-
<h5 className="pt-2 pt-2 pb-1 m-0">{book.title}</h5>
152-
<div className="section-divider"/>
153-
<span className="text-decoration-none">
154-
This is some explanatory text about the book. It could be a brief description of the book, or a list of topics covered.
155-
</span>
156-
</div>
157-
</Link>)}
158-
</Col>
159-
</>
160-
: <>
161-
<div className="d-flex flex-column">
162-
<div className="d-flex mb-3 align-items-center gap-4 white-space-pre">
163-
<h4>News & Features</h4>
164-
<div className="section-divider-bold"/>
165-
</div>
166-
{news && <Row className="h-100">
167-
{news.slice(0, 2).map(newsItem => <Col xs={12} key={newsItem.id}>
168-
<NewsCard newsItem={newsItem} className="force-horizontal p-2" />
169-
</Col>)}
170-
</Row>}
171-
</div>
172-
</>
173-
}
174-
</div>
175-
<div className="d-flex flex-column mt-3">
176-
<div className="d-flex mb-3 align-items-center gap-4 white-space-pre">
177-
<h4 className="m-0">Events</h4>
178-
<div className="section-divider-bold"/>
179-
</div>
180-
<ShowLoadingQuery
181-
query={eventsQuery}
182-
defaultErrorTitle={"Error loading events list"}
183-
thenRender={({events}) => {
184-
// TODO: filter by audience, once that data is available
185-
const relevantEvents = events.filter(event => pageContext?.subject && event.tags?.includes(pageContext.subject)).slice(0, 2);
186-
return <Row className="h-100">
187-
{relevantEvents.length
188-
? relevantEvents.map((event, i) =>
189-
<Col key={i}>
190-
{event && <EventCard event={event} className="force-horizontal p-2" />}
191-
</Col>
192-
)
193-
: <Col className="pt-3 pb-5">No events found for {getHumanContext(pageContext)}. Check back soon!</Col>
194-
}
195-
</Row>;
196-
}}
197-
/>
198-
</div>
199-
</Row>
204+
<LandingPageFooter context={pageContext} />
205+
206+
200207
</Container>;
201208
});

0 commit comments

Comments
 (0)