1
- import React from "react" ;
1
+ import React , { useCallback , useEffect } from "react" ;
2
2
import { RouteComponentProps , withRouter } from "react-router" ;
3
- import { Container } from "reactstrap" ;
3
+ import { Card , Col , Container , Row } from "reactstrap" ;
4
4
import { TitleAndBreadcrumb } from "../elements/TitleAndBreadcrumb" ;
5
- import { getHumanContext , useUrlPageTheme } from "../../services/pageContext" ;
5
+ import { getHumanContext , isDefinedContext , isSingleStageContext , useUrlPageTheme } from "../../services/pageContext" ;
6
+ import { ListView , ListViewCards } from "../elements/list-groups/ListView" ;
7
+ import { getBooksForContext , getLandingPageCardsForContext } from "./subjectLandingPageComponents" ;
8
+ import { above , below , DOCUMENT_TYPE , EventStatusFilter , EventTypeFilter , STAGE , useDeviceSize } from "../../services" ;
9
+ import { PageContextState } from "../../../IsaacAppTypes" ;
10
+ import { PhyHexIcon } from "../elements/svg/PhyHexIcon" ;
11
+ import { Link } from "react-router-dom" ;
12
+ import { ShowLoadingQuery } from "../handlers/ShowLoadingQuery" ;
13
+ import { searchQuestions , useAppDispatch , useAppSelector , useGetNewsPodListQuery , useLazyGetEventsQuery } from "../../state" ;
14
+ import { EventCard } from "../elements/cards/EventCard" ;
15
+ import { debounce } from "lodash" ;
16
+ import { Loading } from "../handlers/IsaacSpinner" ;
17
+ import classNames from "classnames" ;
18
+ import { NewsCard } from "../elements/cards/NewsCard" ;
19
+
20
+ const handleGetDifferentQuestion = ( ) => {
21
+ // TODO
22
+ } ;
23
+
24
+ const RandomQuestionBanner = ( { context} : { context ?: PageContextState } ) => {
25
+ const deviceSize = useDeviceSize ( ) ;
26
+ const dispatch = useAppDispatch ( ) ;
27
+
28
+ const searchDebounce = useCallback ( debounce ( ( ) => {
29
+ dispatch ( searchQuestions ( {
30
+ searchString : "" ,
31
+ tags : "" ,
32
+ fields : undefined ,
33
+ subjects : context ?. subject ,
34
+ topics : undefined ,
35
+ books : undefined ,
36
+ stages : context ?. stage ?. map ( s => s === "11_14" ? "year_7_and_8,year_9" : s ) . join ( ',' ) ,
37
+ difficulties : undefined ,
38
+ examBoards : undefined ,
39
+ questionCategories : "problem_solving,book" ,
40
+ statuses : undefined ,
41
+ fasttrack : false ,
42
+ startIndex : undefined ,
43
+ limit : 1
44
+ } ) ) ;
45
+ } ) , [ dispatch , context ] ) ;
46
+
47
+ const { results : questions } = useAppSelector ( ( state ) => state && state . questionSearchResult ) || { } ;
48
+
49
+ useEffect ( ( ) => {
50
+ searchDebounce ( ) ;
51
+ } , [ searchDebounce ] ) ;
52
+
53
+ if ( ! context || ! isDefinedContext ( context ) || ! isSingleStageContext ( context ) ) {
54
+ return null ;
55
+ }
56
+
57
+ const question = questions ?. [ 0 ] ;
58
+
59
+ return < div className = "py-4 container-override random-question-panel" >
60
+ < Row className = "my-3" >
61
+ < Col lg = { 7 } >
62
+ < div className = "d-flex justify-content-between align-items-center" >
63
+ < h4 className = "m-0" > Try a random question!</ h4 >
64
+ < button className = "btn btn-link invert-underline d-flex align-items-center gap-2" onClick = { handleGetDifferentQuestion } >
65
+ Get a different question
66
+ < i className = "icon icon-refresh icon-color-black" />
67
+ </ button >
68
+ </ div >
69
+ </ Col >
70
+ </ Row >
71
+ < Row >
72
+ < Col lg = { 7 } >
73
+ < Card >
74
+ { question
75
+ ? < ListView items = { [ {
76
+ type : DOCUMENT_TYPE . QUESTION ,
77
+ title : question . title ,
78
+ tags : question . tags ,
79
+ id : question . id ,
80
+ audience : question . audience ,
81
+ } ] } />
82
+ : < Loading /> }
83
+ </ Card >
84
+ </ Col >
85
+ < Col lg = { 5 } className = "ps-lg-5 m-3 m-lg-0" >
86
+ < div className = "d-flex align-items-center" >
87
+ { above [ 'lg' ] ( deviceSize ) && < PhyHexIcon className = "w-min-content" icon = { "page-icon-concept" } /> }
88
+ < h5 className = "m-0" > Explore related concepts:</ h5 >
89
+ </ div >
90
+ < div className = "d-flex flex-wrap gap-2 mt-3" >
91
+ { /* TODO: replace this with "recommended content" or similar */ }
92
+ { /* {question?.relatedContent.filter(rc => rc.type === "isaacConceptPage").slice(0, 5).map((rc, i) => (
93
+ <Link to={`/concepts/${rc.id}` } key={i}>
94
+ <AffixButton key={i} color="keyline" className="px-3 py-2" affix={{
95
+ affix: "icon-lightbulb",
96
+ position: "prefix",
97
+ type: "icon"
98
+ }}>
99
+ {rc.title}
100
+ </AffixButton>
101
+ </Link>
102
+ ))} */ }
103
+ </ div >
104
+ </ Col >
105
+ </ Row >
106
+ </ div > ;
107
+ } ;
6
108
7
109
export const SubjectLandingPage = withRouter ( ( props : RouteComponentProps ) => {
8
110
const pageContext = useUrlPageTheme ( ) ;
111
+ const deviceSize = useDeviceSize ( ) ;
112
+
113
+ const [ getEventsList , eventsQuery ] = useLazyGetEventsQuery ( ) ;
114
+ useEffect ( ( ) => {
115
+ getEventsList ( { startIndex : 0 , limit : 10 , typeFilter : EventTypeFilter [ "All events" ] , statusFilter : EventStatusFilter [ "Upcoming events" ] , stageFilter : [ STAGE . ALL ] } ) ;
116
+ } , [ ] ) ;
117
+
118
+ const books = getBooksForContext ( pageContext ) ;
119
+ // TODO: are we going to make subject-specific news?
120
+ const { data : news } = useGetNewsPodListQuery ( { subject : "physics" } ) ;
9
121
10
122
return < Container data-bs-theme = { pageContext ?. subject } >
11
123
< TitleAndBreadcrumb
@@ -16,6 +128,74 @@ export const SubjectLandingPage = withRouter((props: RouteComponentProps) => {
16
128
icon : `/assets/phy/icons/redesign/subject-${ pageContext . subject } .svg`
17
129
} : undefined }
18
130
/>
19
- < div className = "mt-5" > This is a subject landing page for { getHumanContext ( pageContext ) } !</ div >
131
+
132
+ < RandomQuestionBanner context = { pageContext } />
133
+
134
+ < ListViewCards cards = { getLandingPageCardsForContext ( pageContext , below [ 'md' ] ( deviceSize ) ) } showBlanks = { ! below [ 'md' ] ( deviceSize ) } className = "my-5" />
135
+
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 >
20
200
</ Container > ;
21
201
} ) ;
0 commit comments