Skip to content

Commit 149de41

Browse files
committed
TCA-1280 - handle breadcrumbs navigation within course pages
1 parent 6e5295e commit 149de41

File tree

11 files changed

+234
-183
lines changed

11 files changed

+234
-183
lines changed

src-ts/lib/breadcrumb/breadcrumb-item/BreadcrumbItem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const BreadcrumbItem: FC<BreadcrumbItemProps> = (props: BreadcrumbItemProps) =>
2626
<Link
2727
className={classNames(props.item.isElipsis && styles.elipsis)}
2828
to={props.item.url}
29+
state={props.item.state}
2930
>
3031
{props.item.name}
3132
</Link>

src-ts/lib/breadcrumb/breadcrumb-item/breadcrumb-item.model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export interface BreadcrumbItemModel {
33
name: string
44
onClick?: (item: BreadcrumbItemModel) => void
55
url: string
6+
state?: any
67
}

src-ts/tools/learn/certification-details/certification-curriculum/curriculum-cards/course-card/CourseCard.tsx

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { FC, ReactNode } from 'react'
22

3-
import { Button, IconSolid, ProgressBar } from '../../../../../../lib'
3+
import { BreadcrumbItemModel, Button, IconSolid, ProgressBar } from '../../../../../../lib'
44
import {
55
clearFCCCertificationTitle,
66
CompletionTimeRange,
@@ -20,6 +20,7 @@ import {
2020
getCertificatePath,
2121
getCoursePath,
2222
getLessonPathFromCurrentLesson,
23+
getTCACertificationPath,
2324
} from '../../../../learn.routes'
2425
import CurriculumCard from '../CurriculumCard'
2526

@@ -36,7 +37,16 @@ interface CourseCardProps {
3637
}
3738

3839
const CourseCard: FC<CourseCardProps> = (props: CourseCardProps) => {
40+
3941
function renderCta(): ReactNode {
42+
43+
const routeState: { tcaCertInfo: BreadcrumbItemModel } = {
44+
tcaCertInfo: {
45+
name: props.tcaCertification.title,
46+
url: getTCACertificationPath(props.tcaCertification.dashedName),
47+
},
48+
}
49+
4050
switch (props.progress?.status) {
4151
case UserCertificationProgressStatus.completed:
4252
return (
@@ -49,12 +59,7 @@ const CourseCard: FC<CourseCardProps> = (props: CourseCardProps) => {
4959
props.provider,
5060
props.certification.certification,
5161
)}
52-
routeState={{
53-
tcaCertInfo: {
54-
dashedName: props.tcaCertification.dashedName,
55-
title: props.tcaCertification.title,
56-
},
57-
}}
62+
routeState={routeState}
5863
/>
5964
<Button
6065
buttonStyle='primary'
@@ -78,12 +83,7 @@ const CourseCard: FC<CourseCardProps> = (props: CourseCardProps) => {
7883
props.certification.certification,
7984
props.progress?.currentLesson,
8085
)}
81-
routeState={{
82-
tcaCertInfo: {
83-
dashedName: props.tcaCertification.dashedName,
84-
title: props.tcaCertification.title,
85-
},
86-
}}
86+
routeState={routeState}
8787
/>
8888
)
8989
default:
@@ -96,12 +96,7 @@ const CourseCard: FC<CourseCardProps> = (props: CourseCardProps) => {
9696
props.provider,
9797
props.certification.certification,
9898
)}
99-
routeState={{
100-
tcaCertInfo: {
101-
dashedName: props.tcaCertification.dashedName,
102-
title: props.tcaCertification.title,
103-
},
104-
}}
99+
routeState={routeState}
105100
/>
106101
)
107102
}

src-ts/tools/learn/course-completed/CourseCompletedPage.tsx

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { FC, useContext, useEffect, useMemo } from 'react'
2-
import { NavigateFunction, Params, useLocation, useNavigate, useParams } from 'react-router-dom'
2+
import { Params, useParams } from 'react-router-dom'
33

44
import {
55
Breadcrumb,
@@ -19,20 +19,20 @@ import {
1919
useGetCourses,
2020
useGetTCACertification,
2121
useGetUserCertificationProgress,
22-
useLearnBreadcrumb,
2322
UserCertificationProgressProviderData,
2423
UserCertificationProgressStatus,
2524
useTCACertificationCheckCompleted,
2625
} from '../learn-lib'
27-
import { getCoursePath, getTCACertificationPath, LEARN_PATHS } from '../learn.routes'
26+
import { getCoursePath, LEARN_PATHS } from '../learn.routes'
27+
import { CoursePageContextValue, useCoursePageContext } from '../course-page-wrapper'
2828

2929
import { CourseView } from './course-view'
3030
import { TCACertificationView } from './tca-certification-view'
3131
import styles from './CourseCompletedPage.module.scss'
3232

3333
const CourseCompletedPage: FC<{}> = () => {
3434

35-
const navigate: NavigateFunction = useNavigate()
35+
const { buildBreadcrumbs, localNavigate }: CoursePageContextValue = useCoursePageContext()
3636
const routeParams: Params<string> = useParams()
3737
const { profile, initialized: profileReady }: ProfileContextData = useContext(profileContext)
3838
const providerParam: string = textFormatGetSafeString(routeParams.provider)
@@ -94,45 +94,24 @@ const CourseCompletedPage: FC<{}> = () => {
9494
tcaCertifCompletedCheckReady,
9595
])
9696

97-
const location: any = useLocation()
98-
99-
const breadcrumbItems: BreadcrumbItemModel[] = useMemo(() => {
100-
const bItems: BreadcrumbItemModel[] = [
101-
{
102-
name: courseData?.title ?? '',
103-
url: coursePath,
104-
},
105-
{
106-
name: 'Congratulations!',
107-
url: LEARN_PATHS.completed,
108-
},
109-
]
110-
111-
// if coming path is from TCA certification details page
112-
// then we need to add the certification to the navi list
113-
if (location.state?.tcaCertInfo) {
114-
bItems.unshift({
115-
name: location.state.tcaCertInfo.title,
116-
url: getTCACertificationPath(location.state.tcaCertInfo.dashedName),
117-
})
118-
}
119-
120-
return bItems
121-
}, [
122-
location.state,
123-
courseData?.title,
124-
coursePath,
125-
])
126-
127-
const breadcrumb: Array<BreadcrumbItemModel> = useLearnBreadcrumb(breadcrumbItems)
97+
const breadcrumbs: Array<BreadcrumbItemModel> = useMemo(() => buildBreadcrumbs([
98+
{
99+
name: courseData?.title ?? '',
100+
url: coursePath,
101+
},
102+
{
103+
name: 'Congratulations!',
104+
url: LEARN_PATHS.completed,
105+
},
106+
]), [buildBreadcrumbs, courseData?.title, coursePath])
128107

129108
useEffect(() => {
130109
if (ready && progress?.status !== UserCertificationProgressStatus.completed) {
131-
navigate(coursePath)
110+
localNavigate(coursePath)
132111
}
133112
}, [
134113
coursePath,
135-
navigate,
114+
localNavigate,
136115
progress,
137116
ready,
138117
])
@@ -147,7 +126,7 @@ const CourseCompletedPage: FC<{}> = () => {
147126

148127
{ready && courseData && (
149128
<>
150-
<Breadcrumb items={breadcrumb} />
129+
<Breadcrumb items={breadcrumbs} />
151130
<div className={styles['main-wrap']}>
152131
<div className={styles['course-frame']}>
153132
{tcaCertificationName && tcaCertification ? (

src-ts/tools/learn/course-details/CourseDetailsPage.tsx

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable react/no-danger */
22
import { FC, ReactNode, useContext, useMemo } from 'react'
3-
import { Params, useLocation, useParams } from 'react-router-dom'
3+
import { Params, useParams } from 'react-router-dom'
44

55
import {
66
Breadcrumb,
@@ -23,17 +23,17 @@ import {
2323
useGetCourses,
2424
useGetResourceProvider,
2525
useGetUserCertificationProgress,
26-
useLearnBreadcrumb,
2726
UserCertificationProgressProviderData,
2827
UserCertificationProgressStatus,
2928
} from '../learn-lib'
30-
import { getCoursePath, getTCACertificationPath } from '../learn.routes'
29+
import { getCoursePath } from '../learn.routes'
30+
import { CoursePageContextValue, useCoursePageContext } from '../course-page-wrapper'
3131

3232
import { CourseCurriculum } from './course-curriculum'
3333
import styles from './CourseDetailsPage.module.scss'
3434

3535
const CourseDetailsPage: FC<{}> = () => {
36-
36+
const { buildBreadcrumbs }: CoursePageContextValue = useCoursePageContext()
3737
const routeParams: Params<string> = useParams()
3838
const { profile, initialized: profileReady }: ProfileContextData = useContext(profileContext)
3939

@@ -69,36 +69,16 @@ const CourseDetailsPage: FC<{}> = () => {
6969

7070
const ready: boolean = profileReady && courseReady && certificateReady && (!profile || progressReady)
7171

72-
const location: any = useLocation()
73-
74-
const breadcrumbItems: BreadcrumbItemModel[] = useMemo(() => {
75-
const bItems: BreadcrumbItemModel[] = [
76-
{
77-
78-
name: textFormatGetSafeString(course?.title),
79-
url: getCoursePath(routeParams.provider as string, textFormatGetSafeString(routeParams.certification)),
80-
},
81-
]
82-
83-
// if coming path is from TCA certification details page
84-
// then we need to add the certification to the navi list
85-
if (location.state?.tcaCertInfo) {
86-
bItems.unshift({
87-
name: location.state.tcaCertInfo.title,
88-
url: getTCACertificationPath(location.state.tcaCertInfo.dashedName),
89-
})
90-
}
91-
92-
return bItems
93-
}, [
72+
const breadcrumbs: Array<BreadcrumbItemModel> = useMemo(() => buildBreadcrumbs([{
73+
name: textFormatGetSafeString(course?.title),
74+
url: getCoursePath(routeParams.provider as string, textFormatGetSafeString(routeParams.certification)),
75+
}]), [
76+
buildBreadcrumbs,
9477
course?.title,
9578
routeParams.certification,
9679
routeParams.provider,
97-
location.state,
9880
])
9981

100-
const breadcrumb: Array<BreadcrumbItemModel> = useLearnBreadcrumb(breadcrumbItems)
101-
10282
function getDescription(): ReactNode {
10383

10484
if (!course) {
@@ -207,7 +187,7 @@ const CourseDetailsPage: FC<{}> = () => {
207187
<LoadingSpinner />
208188
</div>
209189
)}
210-
<Breadcrumb items={breadcrumb} />
190+
<Breadcrumb items={breadcrumbs} />
211191
{ready && course && certificate && (
212192
<>
213193
<div className={styles.wrap}>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Context, createContext, FC, ReactNode, useCallback, useContext, useMemo } from 'react'
2+
import { NavigateFunction, useLocation, useNavigate } from 'react-router-dom'
3+
import { pick } from 'lodash'
4+
5+
import { rootRoute } from '../learn.routes'
6+
import { BreadcrumbItemModel } from '../../../lib'
7+
8+
export interface CoursePageContextProviderProps {
9+
children: ReactNode
10+
}
11+
12+
export interface CoursePageContextValue {
13+
buildBreadcrumbs: (items: BreadcrumbItemModel[]) => BreadcrumbItemModel[]
14+
localNavigate: NavigateFunction
15+
navState: { tcaCertInfo: BreadcrumbItemModel } | undefined
16+
}
17+
18+
const CoursePageContext: Context<CoursePageContextValue> = createContext(
19+
{} as CoursePageContextValue,
20+
)
21+
22+
/**
23+
* Page context provider for Course pages: details, fcc, completed
24+
*
25+
* It keeps into account the navigation path the user took to get here
26+
*
27+
* Eg. if user clicked the course directly, there's nothing to do, but
28+
* if the user clicked the course from inside the TCA certification page
29+
* we need to show an extra breadcrumb specific to TCA certification page
30+
*/
31+
32+
export const CoursePageContextProvider: FC<CoursePageContextProviderProps> = props => {
33+
const location: any = useLocation()
34+
const navigate: NavigateFunction = useNavigate()
35+
36+
const parentTcaCert: BreadcrumbItemModel | undefined = useMemo(() => (
37+
(
38+
location.state
39+
&& Object.prototype.hasOwnProperty.call(location.state, 'tcaCertInfo')
40+
&& location.state.tcaCertInfo && pick(location.state.tcaCertInfo, ['name', 'url'])
41+
) || undefined
42+
), [location.state])
43+
44+
const buildBreadcrumbs: (items: BreadcrumbItemModel[]) => BreadcrumbItemModel[]
45+
= useCallback(items => {
46+
const breadcrumbs: BreadcrumbItemModel[] = [
47+
{
48+
name: 'Topcoder Academy',
49+
url: rootRoute,
50+
},
51+
...(!parentTcaCert ? [] : [parentTcaCert]),
52+
...items,
53+
]
54+
55+
return !parentTcaCert ? breadcrumbs : breadcrumbs.map(item => Object.assign(item, {
56+
state: { tcaCertInfo: pick(parentTcaCert, ['name', 'url']) },
57+
}))
58+
}, [parentTcaCert])
59+
60+
const localNavigate: NavigateFunction = useCallback((to, options) => (
61+
navigate(to, {
62+
...options,
63+
state: {
64+
...options?.state,
65+
tcaCertInfo: parentTcaCert,
66+
},
67+
})
68+
), [navigate, parentTcaCert]) as NavigateFunction
69+
70+
const ctxValue: CoursePageContextValue = useMemo(() => ({
71+
buildBreadcrumbs,
72+
localNavigate,
73+
navState: parentTcaCert ? { tcaCertInfo: parentTcaCert } : undefined,
74+
}), [
75+
buildBreadcrumbs,
76+
localNavigate,
77+
parentTcaCert,
78+
])
79+
80+
return (
81+
<CoursePageContext.Provider
82+
value={ctxValue}
83+
>
84+
{props.children}
85+
</CoursePageContext.Provider>
86+
)
87+
}
88+
89+
export const useCoursePageContext: () => CoursePageContextValue
90+
= () => useContext(CoursePageContext)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { FC, ReactElement, useContext, useMemo } from 'react'
2+
import { Outlet, Routes } from 'react-router-dom'
3+
4+
import { routeContext, RouteContextData } from '../../../lib'
5+
import { learnRoutes } from '../learn.routes'
6+
7+
import { CoursePageContextProvider } from './CoursePage.context'
8+
9+
const CoursePageWrapper: FC<{}> = () => {
10+
const { getRouteElement }: RouteContextData = useContext(routeContext)
11+
12+
// Get all the CoursePage child routes and render them as a route element
13+
const childRoutes: ReactElement[] = useMemo(() => (
14+
learnRoutes[0].children?.find(route => route.id === 'CoursePage')?.children ?? []
15+
).map(getRouteElement), [getRouteElement])
16+
17+
return (
18+
<CoursePageContextProvider>
19+
<Outlet />
20+
<Routes>
21+
{childRoutes}
22+
</Routes>
23+
</CoursePageContextProvider>
24+
)
25+
}
26+
27+
export default CoursePageWrapper
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as CoursePageWrapper } from './CoursePageWrapper'
2+
export * from './CoursePage.context'

0 commit comments

Comments
 (0)