From d2749c083e689f6152801ebc3ee7b7c51bd05190 Mon Sep 17 00:00:00 2001 From: Brooke Date: Fri, 22 Jul 2022 12:01:17 -0700 Subject: [PATCH 1/4] PROD-2549 #comment refactor hooks so that we don't need to pass the user id; make function and variable names more explicit; lint fixes; add user's course state to the course cards; refactor progress block to use the same course state the cards use; #time 4h --- src-ts/tools/learn/Learn.tsx | 1 - .../course-completed/CourseCompletedPage.tsx | 1 - .../course-details/CourseDetailsPage.tsx | 1 - .../course-curriculum/CourseCurriculum.tsx | 42 ++++--- .../learn/free-code-camp/FreeCodeCamp.tsx | 108 ++++++++++-------- .../learn-certification.model.ts | 2 +- .../course-outline/CourseOutline.tsx | 19 +-- src-ts/tools/learn/learn-lib/index.ts | 7 +- .../my-certifications-provider/index.ts | 4 +- .../my-certification-completed.model.ts | 5 + .../my-certification-in-progress.model.ts | 6 + .../my-certification-progress.provider.tsx | 50 +++++--- .../my-certifications-functions/index.ts | 7 +- .../my-certification-progress-status.enum.ts | 1 + ...ifications-update-progress-actions.enum.ts | 6 + .../my-certifications.store.ts | 14 +-- .../my-certifications.provider.tsx | 41 ++++--- .../my-course-card/in-progress/InProgress.tsx | 14 +-- src-ts/tools/learn/learn.routes.tsx | 15 ++- .../learn/my-certificate/MyCertificate.tsx | 1 - src-ts/tools/learn/my-learning/MyLearning.tsx | 9 +- src-ts/tools/learn/welcome/WelcomePage.tsx | 47 +++++--- .../welcome/courses-card/CoursesCard.tsx | 92 ++++++++++++--- .../progress-block/ProgressBlock.module.scss | 19 --- .../welcome/progress-block/ProgressBlock.tsx | 92 +++------------ .../progress-block/init-state/index.ts | 1 - .../NoProgress.module.scss} | 0 .../NoProgress.tsx} | 6 +- .../progress-block/no-progress/index.ts | 1 + .../ProgressAction.module.scss | 20 ++++ .../progress-action/ProgressAction.tsx | 93 +++++++++++++++ .../progress-block/progress-action/index.ts | 1 + 32 files changed, 458 insertions(+), 268 deletions(-) create mode 100644 src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-completed.model.ts create mode 100644 src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-in-progress.model.ts create mode 100644 src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/my-certifications-update-progress-actions.enum.ts delete mode 100644 src-ts/tools/learn/welcome/progress-block/init-state/index.ts rename src-ts/tools/learn/welcome/progress-block/{init-state/InitState.module.scss => no-progress/NoProgress.module.scss} (100%) rename src-ts/tools/learn/welcome/progress-block/{init-state/InitState.tsx => no-progress/NoProgress.tsx} (81%) create mode 100644 src-ts/tools/learn/welcome/progress-block/no-progress/index.ts create mode 100644 src-ts/tools/learn/welcome/progress-block/progress-action/ProgressAction.module.scss create mode 100644 src-ts/tools/learn/welcome/progress-block/progress-action/ProgressAction.tsx create mode 100644 src-ts/tools/learn/welcome/progress-block/progress-action/index.ts diff --git a/src-ts/tools/learn/Learn.tsx b/src-ts/tools/learn/Learn.tsx index aba5d5288..44b6a5825 100644 --- a/src-ts/tools/learn/Learn.tsx +++ b/src-ts/tools/learn/Learn.tsx @@ -2,7 +2,6 @@ import { FC, useContext } from 'react' import { Outlet, Routes } from 'react-router-dom' import { - ContentLayout, routeContext, RouteContextData, } from '../../lib' diff --git a/src-ts/tools/learn/course-completed/CourseCompletedPage.tsx b/src-ts/tools/learn/course-completed/CourseCompletedPage.tsx index da2f33abe..de9cbdb4e 100755 --- a/src-ts/tools/learn/course-completed/CourseCompletedPage.tsx +++ b/src-ts/tools/learn/course-completed/CourseCompletedPage.tsx @@ -45,7 +45,6 @@ const CourseCompletedPage: FC<{}> = () => { certificateProgress: progress, ready: progressReady, }: MyCertificationProgressProviderData = useMyCertificationProgress( - profile?.userId, routeParams.provider, routeParams.certification ) diff --git a/src-ts/tools/learn/course-details/CourseDetailsPage.tsx b/src-ts/tools/learn/course-details/CourseDetailsPage.tsx index 0dc41b16d..d7ac14bb3 100644 --- a/src-ts/tools/learn/course-details/CourseDetailsPage.tsx +++ b/src-ts/tools/learn/course-details/CourseDetailsPage.tsx @@ -43,7 +43,6 @@ const CourseDetailsPage: FC<{}> = () => { certificateProgress: progress, ready: progressReady, }: MyCertificationProgressProviderData = useMyCertificationProgress( - profile?.userId, routeParams.provider, routeParams.certification, ) diff --git a/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx b/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx index 415015ac9..fc5069608 100644 --- a/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx +++ b/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx @@ -11,11 +11,16 @@ import { LearnModule, LearnMyCertificationProgress, MyCertificationProgressStatus, - startMyCertificationsProgressAsync, - UpdateMyCertificateProgressActions, - updateMyCertificationsProgressAsync + myCertificationsStartProgress, + myCertificationsUpdateProgress, + UpdateMyCertificateProgressActions } from '../../learn-lib' -import { authenticateAndStartCourseRoute, getCertificatePath, getFccLessonPath, LEARN_PATHS } from '../../learn.routes' +import { + authenticateAndStartCourseRoute, + getCertificatePath, + getLessonPathFromCurrentLesson, + LEARN_PATHS, +} from '../../learn.routes' import styles from './CourseCurriculum.module.scss' import { CurriculumSummary } from './curriculum-summary' @@ -29,6 +34,7 @@ interface CourseCurriculumProps { } const CourseCurriculum: FC = (props: CourseCurriculumProps) => { + const navigate: NavigateFunction = useNavigate() const [searchParams]: any = useSearchParams() @@ -36,7 +42,7 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp const [isTcAcademyPolicyModal, setIsTcAcademyPolicyModal]: [boolean, Dispatch>] = useState(false) - const status: string = props.progress?.status ?? 'init' + const status: string = props.progress?.status ?? MyCertificationProgressStatus.inititialized const completedPercentage: number = (props.progress?.courseProgressPercentage ?? 0) / 100 const inProgress: boolean = status === MyCertificationProgressStatus.inProgress || !!props.progress?.currentLesson const isCompleted: boolean = status === MyCertificationProgressStatus.completed @@ -46,19 +52,25 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp * otherwise redirect to first module > first lesson */ const handleStartCourse: () => void = useCallback(() => { - const current: Array = (props.progress?.currentLesson ?? '').split('/') + const course: LearnCourse = props.course const module: LearnModule = course.modules[0] const lesson: LearnLesson = module.lessons[0] - const lessonPath: string = getFccLessonPath( + const lessonPath: string = getLessonPathFromCurrentLesson( course.provider, course.certification, - current[0] || module.meta.dashedName, - current[1] || lesson.dashedName, + props.progress?.currentLesson, + module.meta.dashedName, + lesson.dashedName, ) navigate(lessonPath) - }, [props.course, props.progress, navigate]) + }, [ + getLessonPathFromCurrentLesson, + navigate, + props.course, + props.progress, + ]) /** * Handle user click on start course/resume/login button @@ -97,7 +109,7 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp } if (!props.progress?.id) { - await startMyCertificationsProgressAsync( + await myCertificationsStartProgress( props.profile.userId, props.course.certificationId, props.course.id, @@ -107,7 +119,7 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp } ) } else { - await updateMyCertificationsProgressAsync( + await myCertificationsUpdateProgress( props.progress.id, UpdateMyCertificateProgressActions.acceptHonestyPolicy, {} @@ -134,9 +146,9 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp * proceed as if the user just clicked "Start course" button */ useEffect(() => { - if (props.progressReady && isLoggedIn && searchParams.get('start-course') !== null) { - handleStartCourseClick() - } + if (props.progressReady && isLoggedIn && searchParams.get('start-course') !== null) { + handleStartCourseClick() + } }, [handleStartCourseClick, isLoggedIn, props.progressReady, searchParams]) return ( diff --git a/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx b/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx index 351ab9d26..e9240cac3 100644 --- a/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx +++ b/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx @@ -1,4 +1,14 @@ -import { Dispatch, FC, SetStateAction, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useState } from 'react' +import { + Dispatch, + FC, + SetStateAction, + useCallback, + useContext, + useEffect, + useLayoutEffect, + useMemo, + useState, +} from 'react' import { NavigateFunction, Params, useNavigate, useParams } from 'react-router-dom' import { @@ -17,14 +27,14 @@ import { LessonProviderData, MyCertificationProgressProviderData, MyCertificationProgressStatus, - startMyCertificationsProgressAsync, + myCertificationsStartProgress, + myCertificationsUpdateProgress, UpdateMyCertificateProgressActions, - updateMyCertificationsProgressAsync, useCoursesProvider, useLessonProvider, useMyCertificationProgress, } from '../learn-lib' -import { getCertificationCompletedPath, getCoursePath, getFccLessonPath } from '../learn.routes' +import { getCertificationCompletedPath, getCoursePath, getLessonPathFromModule } from '../learn.routes' import { FccFrame } from './fcc-frame' import styles from './FreeCodeCamp.module.scss' @@ -49,7 +59,7 @@ const FreeCodeCamp: FC<{}> = () => { certificateProgress, setCertificateProgress, ready: progressReady, - }: MyCertificationProgressProviderData = useMyCertificationProgress(profile?.userId, routeParams.provider, certificationParam) + }: MyCertificationProgressProviderData = useMyCertificationProgress(routeParams.provider, certificationParam) const { course: courseData, @@ -71,27 +81,27 @@ const FreeCodeCamp: FC<{}> = () => { { url: '/learn/fcc', name: lesson?.module.title ?? '' }, ], [providerParam, lesson]) - const currentModuleData: LearnModule|undefined = useMemo(() => { + const currentModuleData: LearnModule | undefined = useMemo(() => { return courseData?.modules.find(d => d.key === moduleParam) }, [courseData, moduleParam]) const currentStepIndex: number = useMemo(() => { - if (!currentModuleData) { - return 0 - } + if (!currentModuleData) { + return 0 + } - const lessonIndex: number = currentModuleData.lessons.findIndex(l => l.dashedName === lessonParam) - return lessonIndex + 1 + const lessonIndex: number = currentModuleData.lessons.findIndex(l => l.dashedName === lessonParam) + return lessonIndex + 1 }, [currentModuleData, lessonParam]) const handleNavigate: (direction: number) => void = useCallback((direction = 1) => { - const nextStep: LearnLesson|undefined = currentModuleData?.lessons[(currentStepIndex - 1) + direction] + const nextStep: LearnLesson | undefined = currentModuleData?.lessons[(currentStepIndex - 1) + direction] if (!nextStep) { return } - const lessonPath: string = getFccLessonPath( + const lessonPath: string = getLessonPathFromModule( providerParam, certificationParam, moduleParam, @@ -108,12 +118,19 @@ const FreeCodeCamp: FC<{}> = () => { ]) function updatePath(lessonPath: string, modulePath: string, coursePath: string): void { - if (coursePath !== certificationParam) { setCourseParam(coursePath) } - if (modulePath !== moduleParam) { setModuleParam(modulePath) } - if (lessonPath !== lessonParam) { setLessonParam(lessonPath) } + + if (coursePath !== certificationParam) { + setCourseParam(coursePath) + } + if (modulePath !== moduleParam) { + setModuleParam(modulePath) + } + if (lessonPath !== lessonParam) { + setLessonParam(lessonPath) + } if (lessonPath !== lessonParam || modulePath !== moduleParam || coursePath !== certificationParam) { - const nextLessonPath: string = getFccLessonPath( + const nextLessonPath: string = getLessonPathFromModule( providerParam, coursePath, modulePath, @@ -124,10 +141,11 @@ const FreeCodeCamp: FC<{}> = () => { } function handleFccLessonReady(lessonPath: string): void { + const [nLessonPath, modulePath, coursePath]: Array = lessonPath.replace(/\/$/, '').split('/').reverse() updatePath(nLessonPath, modulePath, coursePath) - const currentLesson: {[key: string]: string} = { + const currentLesson: { [key: string]: string } = { lesson: nLessonPath, module: modulePath, } @@ -141,7 +159,7 @@ const FreeCodeCamp: FC<{}> = () => { } if (!certificateProgress) { - startMyCertificationsProgressAsync( + myCertificationsStartProgress( profile.userId, lesson.course.certificationId, lesson.course.id, @@ -151,23 +169,23 @@ const FreeCodeCamp: FC<{}> = () => { // TODO: remove this delay!! // TEMP_FIX: delay this api call to allow for previous "completeLesson" call to write in the api setTimeout(() => { - updateMyCertificationsProgressAsync( - certificateProgress.id, - UpdateMyCertificateProgressActions.currentLesson, - currentLesson - ) + myCertificationsUpdateProgress( + certificateProgress.id, + UpdateMyCertificateProgressActions.currentLesson, + currentLesson + ) .then(setCertificateProgress) }, 500) } } function handleFccLessonComplete(): void { - const currentLesson: {[key: string]: string} = { + const currentLesson: { [key: string]: string } = { lesson: lessonParam, module: moduleParam, } if (certificateProgress) { - updateMyCertificationsProgressAsync( + myCertificationsUpdateProgress( certificateProgress.id, UpdateMyCertificateProgressActions.completeLesson, currentLesson @@ -176,26 +194,26 @@ const FreeCodeCamp: FC<{}> = () => { } useEffect(() => { - if ( - certificateProgress && - certificateProgress.courseProgressPercentage === 100 && - certificateProgress.status === MyCertificationProgressStatus.inProgress - ) { - updateMyCertificationsProgressAsync( - certificateProgress.id, - UpdateMyCertificateProgressActions.completeCertificate, - {} - ) - .then(setCertificateProgress) - .then(() => { - const completedPath: string = getCertificationCompletedPath( - providerParam, - certificationParam - ) + if ( + certificateProgress && + certificateProgress.courseProgressPercentage === 100 && + certificateProgress.status === MyCertificationProgressStatus.inProgress + ) { + myCertificationsUpdateProgress( + certificateProgress.id, + UpdateMyCertificateProgressActions.completeCertificate, + {} + ) + .then(setCertificateProgress) + .then(() => { + const completedPath: string = getCertificationCompletedPath( + providerParam, + certificationParam + ) - navigate(completedPath) - }) - } + navigate(completedPath) + }) + } }, [ certificateProgress, certificationParam, diff --git a/src-ts/tools/learn/learn-lib/certifications-provider/certifications-functions/learn-certification.model.ts b/src-ts/tools/learn/learn-lib/certifications-provider/certifications-functions/learn-certification.model.ts index 6bd6ca18e..6104d6201 100644 --- a/src-ts/tools/learn/learn-lib/certifications-provider/certifications-functions/learn-certification.model.ts +++ b/src-ts/tools/learn/learn-lib/certifications-provider/certifications-functions/learn-certification.model.ts @@ -5,6 +5,6 @@ export interface LearnCertification { key: string providerId: string providerName: string - state: string + state: 'active' | 'coming-soon' title: string } diff --git a/src-ts/tools/learn/learn-lib/course-outline/CourseOutline.tsx b/src-ts/tools/learn/learn-lib/course-outline/CourseOutline.tsx index 7b74df4a6..57a967e77 100644 --- a/src-ts/tools/learn/learn-lib/course-outline/CourseOutline.tsx +++ b/src-ts/tools/learn/learn-lib/course-outline/CourseOutline.tsx @@ -8,7 +8,7 @@ import { LearnModule, LearnMyCertificationProgress, } from '../../learn-lib' -import { getFccLessonPath } from '../../learn.routes' +import { getLessonPathFromModule } from '../../learn.routes' import { CollapsibleItem } from './collapsible-item' import styles from './CourseOutline.module.scss' @@ -22,14 +22,15 @@ interface CourseOutlineProps { const CourseOutline: FC = (props: CourseOutlineProps) => { - const lessonPath: (course: LearnCourse, module: LearnModule, lesson: LearnLesson) => string = useCallback((course: LearnCourse, module: LearnModule, lesson: LearnLesson) => { - return getFccLessonPath( - course.provider, - course.certification, - module.key, - lesson.dashedName, - ) - }, []) + const lessonPath: (course: LearnCourse, module: LearnModule, lesson: LearnLesson) => string + = useCallback((course: LearnCourse, module: LearnModule, lesson: LearnLesson) => { + return getLessonPathFromModule( + course.provider, + course.certification, + module.key, + lesson.dashedName, + ) + }, []) return (
diff --git a/src-ts/tools/learn/learn-lib/index.ts b/src-ts/tools/learn/learn-lib/index.ts index abd290825..70a6891f5 100755 --- a/src-ts/tools/learn/learn-lib/index.ts +++ b/src-ts/tools/learn/learn-lib/index.ts @@ -1,13 +1,12 @@ export * from './certifications-provider' -export * from './resource-provider-provider' export * from './collapsible-pane' +export * from './course-outline' +export * from './course-title' export * from './courses-provider' export * from './curriculum-summary' export * from './lesson-provider' export * from './my-certifications-provider' export * from './my-course-card' - -export * from './course-outline' -export * from './course-title' +export * from './resource-provider-provider' export * from './svgs' export * from './wave-hero' diff --git a/src-ts/tools/learn/learn-lib/my-certifications-provider/index.ts b/src-ts/tools/learn/learn-lib/my-certifications-provider/index.ts index 9f7a8661c..2e446dd89 100755 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/index.ts +++ b/src-ts/tools/learn/learn-lib/my-certifications-provider/index.ts @@ -1,5 +1,7 @@ +export * from './my-certification-completed.model' +export * from './my-certification-in-progress.model' export * from './my-certification-progress-provider-data.model' export * from './my-certification-progress.provider' +export * from './my-certifications-functions' export * from './my-certifications-provider-data.model' export * from './my-certifications.provider' -export * from './my-certifications-functions' diff --git a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-completed.model.ts b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-completed.model.ts new file mode 100644 index 000000000..66c199201 --- /dev/null +++ b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-completed.model.ts @@ -0,0 +1,5 @@ +import { LearnMyCertificationProgress } from './my-certifications-functions' + +export interface MyCertificationCompleted extends LearnMyCertificationProgress { + completedDate: string +} diff --git a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-in-progress.model.ts b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-in-progress.model.ts new file mode 100644 index 000000000..c21509b21 --- /dev/null +++ b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-in-progress.model.ts @@ -0,0 +1,6 @@ +import { LearnMyCertificationProgress } from './my-certifications-functions' + +export interface MyCertificationInProgress extends LearnMyCertificationProgress { + currentLesson: string + startDate: string +} diff --git a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-progress.provider.tsx b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-progress.provider.tsx index ddc93a577..e238179de 100644 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-progress.provider.tsx +++ b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-progress.provider.tsx @@ -1,19 +1,25 @@ -import { Dispatch, SetStateAction, useEffect, useState } from 'react' +import { Dispatch, SetStateAction, useContext, useEffect, useState } from 'react' + +import { profileContext, ProfileContextData } from '../../../../lib' import { MyCertificationProgressProviderData } from './my-certification-progress-provider-data.model' -import { getMyCertificationsProgressAsync, LearnMyCertificationProgress } from './my-certifications-functions' +import { LearnMyCertificationProgress, myCertificationsGetProgress } from './my-certifications-functions' + +export function useMyCertificationProgress(provider?: string, certification?: string): MyCertificationProgressProviderData { + + const profileContextData: ProfileContextData = useContext(profileContext) -export function useMyCertificationProgress(userId?: number, provider?: string, certification?: string): MyCertificationProgressProviderData { function setCertificateProgress(progress: LearnMyCertificationProgress): void { - setState((prevState) => ({...prevState, certificateProgress: progress})) + setState((prevState) => ({ ...prevState, certificateProgress: progress })) } - const [state, setState]: [MyCertificationProgressProviderData, Dispatch>] = useState({ - certificateProgress: undefined, - loading: false, - ready: false, - setCertificateProgress, - }) + const [state, setState]: [MyCertificationProgressProviderData, Dispatch>] + = useState({ + certificateProgress: undefined, + loading: false, + ready: false, + setCertificateProgress, + }) useEffect(() => { setState((prevState) => ({ @@ -21,19 +27,25 @@ export function useMyCertificationProgress(userId?: number, provider?: string, c loading: true, })) + const userId: number | undefined = profileContextData?.profile?.userId if (!userId) { return } - getMyCertificationsProgressAsync(userId, provider, certification).then((myCertifications) => { - setState((prevState) => ({ - ...prevState, - certificateProgress: myCertifications.find(c => c.certification === certification), - loading: false, - ready: true, - })) - }) - }, [userId, provider, certification]) + myCertificationsGetProgress(userId, provider, certification) + .then((myCertifications) => { + setState((prevState) => ({ + ...prevState, + certificateProgress: myCertifications.find(c => c.certification === certification), + loading: false, + ready: true, + })) + }) + }, [ + certification, + profileContextData?.profile?.userId, + provider, + ]) return state } diff --git a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/index.ts b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/index.ts index c5eebf2e5..fa4db0de9 100755 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/index.ts +++ b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/index.ts @@ -1,4 +1,9 @@ export * from './learn-my-certification-progress.model' export * from './learn-my-module-progress.model' export * from './my-certification-progress-status.enum' -export * from './my-certifications.store' +export * from './my-certifications-update-progress-actions.enum' +export { + getProgressAsync as myCertificationsGetProgress, + startProgressAsync as myCertificationsStartProgress, + updateProgressAsync as myCertificationsUpdateProgress +} from './my-certifications.store' diff --git a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/my-certification-progress-status.enum.ts b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/my-certification-progress-status.enum.ts index b9a3c8e6f..1ee38d1b7 100755 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/my-certification-progress-status.enum.ts +++ b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/my-certification-progress-status.enum.ts @@ -1,4 +1,5 @@ export enum MyCertificationProgressStatus { + inititialized = 'init', inProgress = 'in-progress', completed = 'completed', } diff --git a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/my-certifications-update-progress-actions.enum.ts b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/my-certifications-update-progress-actions.enum.ts new file mode 100644 index 000000000..34cdf4027 --- /dev/null +++ b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/my-certifications-update-progress-actions.enum.ts @@ -0,0 +1,6 @@ +export enum UpdateMyCertificateProgressActions { + acceptHonestyPolicy = 'honesty-policy', + currentLesson = 'current-lesson', + completeLesson = 'complete-lesson', + completeCertificate = 'complete-certification', +} diff --git a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/my-certifications.store.ts b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/my-certifications.store.ts index eb4fbcb1c..7683fd0d5 100755 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/my-certifications.store.ts +++ b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/my-certifications.store.ts @@ -2,15 +2,9 @@ import { xhrGetAsync, xhrPostAsync, xhrPutAsync } from '../../../../../lib/funct import { getPath } from '../../learn-url.config' import { LearnMyCertificationProgress } from './learn-my-certification-progress.model' +import { UpdateMyCertificateProgressActions } from './my-certifications-update-progress-actions.enum' -export enum UpdateMyCertificateProgressActions { - acceptHonestyPolicy = 'honesty-policy', - currentLesson = 'current-lesson', - completeLesson = 'complete-lesson', - completeCertificate = 'complete-certification', -} - -export function getMyCertificationsProgressAsync(userId: number, provider?: string, certification?: string): Promise> { +export function getProgressAsync(userId: number, provider?: string, certification?: string): Promise> { return xhrGetAsync>(getPath( 'certification-progresses', [ @@ -21,7 +15,7 @@ export function getMyCertificationsProgressAsync(userId: number, provider?: stri )) } -export function startMyCertificationsProgressAsync(userId: number, certificationId: string, courseId: string, data: any): Promise { +export function startProgressAsync(userId: number, certificationId: string, courseId: string, data: any): Promise { return xhrPostAsync<{}, LearnMyCertificationProgress>(getPath( 'certification-progresses', `${userId}`, @@ -30,7 +24,7 @@ export function startMyCertificationsProgressAsync(userId: number, certification ), data) } -export function updateMyCertificationsProgressAsync(certificationProgressId: string, action: UpdateMyCertificateProgressActions, data: any): Promise { +export function updateProgressAsync(certificationProgressId: string, action: UpdateMyCertificateProgressActions, data: any): Promise { return xhrPutAsync<{}, LearnMyCertificationProgress>(getPath( 'certification-progresses', certificationProgressId, diff --git a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications.provider.tsx b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications.provider.tsx index 81a21cdf1..f4c9c75f7 100644 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications.provider.tsx +++ b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications.provider.tsx @@ -1,9 +1,15 @@ -import { Dispatch, SetStateAction, useEffect, useState } from 'react' +import { Dispatch, SetStateAction, useContext, useEffect, useState } from 'react' -import { getMyCertificationsProgressAsync, MyCertificationProgressStatus } from './my-certifications-functions' +import { profileContext, ProfileContextData } from '../../../../lib' + +import { MyCertificationCompleted } from './my-certification-completed.model' +import { MyCertificationInProgress } from './my-certification-in-progress.model' +import { MyCertificationProgressStatus, myCertificationsGetProgress } from './my-certifications-functions' import { MyCertificationsProviderData } from './my-certifications-provider-data.model' -export function useMyCertifications(userId?: number): MyCertificationsProviderData { +export function useMyCertifications(): MyCertificationsProviderData { + + const profileContextData: ProfileContextData = useContext(profileContext) const [state, setState]: [MyCertificationsProviderData, Dispatch>] = useState({ completed: [], inProgress: [], @@ -12,25 +18,34 @@ export function useMyCertifications(userId?: number): MyCertificationsProviderDa }) useEffect(() => { + setState((prevState) => ({ ...prevState, loading: true, })) + const userId: number | undefined = profileContextData?.profile?.userId if (!userId) { return } - getMyCertificationsProgressAsync(userId).then((myCertifications) => { - setState((prevState) => ({ - ...prevState, - completed: myCertifications.filter(c => c.status === MyCertificationProgressStatus.completed) as MyCertificationsProviderData['completed'], - inProgress: myCertifications.filter(c => c.status === MyCertificationProgressStatus.inProgress) as MyCertificationsProviderData['inProgress'], - loading: false, - ready: true, - })) - }) - }, [userId]) + myCertificationsGetProgress(userId) + .then((myCertifications) => { + const completed: Array = myCertifications + .filter(c => c.status === MyCertificationProgressStatus.completed) + .map(c => c as MyCertificationCompleted) + const inProgress: Array = myCertifications + .filter(c => c.status === MyCertificationProgressStatus.inProgress) + .map(c => c as MyCertificationInProgress) + setState((prevState) => ({ + ...prevState, + completed, + inProgress, + loading: false, + ready: true, + })) + }) + }, [profileContextData?.profile?.userId]) return state } diff --git a/src-ts/tools/learn/learn-lib/my-course-card/in-progress/InProgress.tsx b/src-ts/tools/learn/learn-lib/my-course-card/in-progress/InProgress.tsx index 0cb6a0780..75a1f409c 100644 --- a/src-ts/tools/learn/learn-lib/my-course-card/in-progress/InProgress.tsx +++ b/src-ts/tools/learn/learn-lib/my-course-card/in-progress/InProgress.tsx @@ -13,7 +13,7 @@ import { LearnCertification, useCoursesProvider, } from '../../../learn-lib' -import { getCoursePath, getFccLessonPath } from '../../../learn.routes' +import { getCoursePath, getLessonPathFromCurrentLesson } from '../../../learn.routes' import { CurriculumSummary } from '../../curriculum-summary' import styles from './InProgress.module.scss' @@ -23,30 +23,28 @@ interface InProgressProps { completedPercentage: number currentLesson?: string startDate?: string - theme: 'detailed'|'minimum' + theme: 'detailed' | 'minimum' } const InProgress: FC = (props: InProgressProps) => { + const navigate: NavigateFunction = useNavigate() const isDetailed: boolean = props.theme === 'detailed' const isMinimum: boolean = props.theme === 'minimum' const certification: string = props.certification?.certification ?? '' const provider: string = props.certification?.providerName ?? '' - const {course}: CoursesProviderData = useCoursesProvider(provider, certification) + const { course }: CoursesProviderData = useCoursesProvider(provider, certification) const resumeCourse: () => void = () => { if (!props.currentLesson) { return } - const [module, lesson]: Array = (props.currentLesson ?? '').split('/') - - const coursePath: string = getFccLessonPath( + const coursePath: string = getLessonPathFromCurrentLesson( provider, certification, - module, - lesson, + props.currentLesson, ) navigate(coursePath) } diff --git a/src-ts/tools/learn/learn.routes.tsx b/src-ts/tools/learn/learn.routes.tsx index 15d1fdec6..20833a468 100644 --- a/src-ts/tools/learn/learn.routes.tsx +++ b/src-ts/tools/learn/learn.routes.tsx @@ -3,7 +3,7 @@ import { authUrlLogin, PlatformRoute } from '../../lib' import { CourseCompletedPage } from './course-completed' import { CourseDetailsPage } from './course-details' import { FreeCodeCamp } from './free-code-camp' -import Learn, { toolTitle } from './Learn' +import { default as Learn, toolTitle } from './Learn' import { MyCertificate } from './my-certificate' import { MyLearning } from './my-learning' import { WelcomePage } from './welcome' @@ -20,7 +20,18 @@ export function getCertificationCompletedPath(provider: string, certification: s return `/learn/${provider}/${certification}/completed` } -export function getFccLessonPath( +export function getLessonPathFromCurrentLesson( + provider: string, + certification: string, + currentLesson: string | undefined, + fallbackModule?: string, + fallbackLesson?: string, +): string { + const [module, lesson]: Array = (currentLesson ?? '').split('/') + return `${getCoursePath(provider, certification)}/${module || fallbackModule}/${lesson || fallbackLesson}` +} + +export function getLessonPathFromModule( provider: string, certification: string, module: string, diff --git a/src-ts/tools/learn/my-certificate/MyCertificate.tsx b/src-ts/tools/learn/my-certificate/MyCertificate.tsx index c9c7c2e43..e1f140ea2 100755 --- a/src-ts/tools/learn/my-certificate/MyCertificate.tsx +++ b/src-ts/tools/learn/my-certificate/MyCertificate.tsx @@ -47,7 +47,6 @@ const MyCertificate: FC<{}> = () => { certificateProgress, ready: progressReady, }: MyCertificationProgressProviderData = useMyCertificationProgress( - profile?.userId, routeParams.provider, routeParams.certification ) diff --git a/src-ts/tools/learn/my-learning/MyLearning.tsx b/src-ts/tools/learn/my-learning/MyLearning.tsx index e7ea6b341..8c3a0282a 100755 --- a/src-ts/tools/learn/my-learning/MyLearning.tsx +++ b/src-ts/tools/learn/my-learning/MyLearning.tsx @@ -24,17 +24,14 @@ interface CertificatesByIdType { const MyLearning: FC<{}> = () => { const { profile }: ProfileContextData = useContext(profileContext) - const { completed, inProgress }: MyCertificationsProviderData = useMyCertifications(profile?.userId) - - const { - certifications, - }: CertificationsProviderData = useCertificationsProvider() + const { completed, inProgress }: MyCertificationsProviderData = useMyCertifications() + const { certifications }: CertificationsProviderData = useCertificationsProvider() const certificatesById: CertificatesByIdType = useMemo(() => ( certifications.reduce((certifs, certificate) => { certifs[certificate.id] = certificate return certifs -}, {} as unknown as CertificatesByIdType) + }, {} as unknown as CertificatesByIdType) ), [certifications]) const breadcrumb: Array = useMemo(() => [ diff --git a/src-ts/tools/learn/welcome/WelcomePage.tsx b/src-ts/tools/learn/welcome/WelcomePage.tsx index 653290552..53f202dcc 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.tsx +++ b/src-ts/tools/learn/welcome/WelcomePage.tsx @@ -1,18 +1,24 @@ import { FC } from 'react' import { ContentLayout, LoadingSpinner, Portal } from '../../../lib' -import { CertificationsProviderData, useCertificationsProvider, WaveHero } from '../learn-lib' -import { getCoursePath } from '../learn.routes' +import { + CertificationsProviderData, + MyCertificationsProviderData, + useCertificationsProvider, + useMyCertifications, + WaveHero, +} from '../learn-lib' import { CoursesCard } from './courses-card' import { ProgressBlock } from './progress-block' import styles from './WelcomePage.module.scss' const WelcomePage: FC<{}> = () => { - const { - certifications, - ready, - }: CertificationsProviderData = useCertificationsProvider() + + const allCertsData: CertificationsProviderData = useCertificationsProvider() + const myCertsData: MyCertificationsProviderData = useMyCertifications() + + const coursesReady: boolean = allCertsData.ready && myCertsData.ready return ( @@ -35,28 +41,33 @@ const WelcomePage: FC<{}> = () => { `} theme='light' > - +

Courses Available

- {!ready && ( + {!coursesReady && ( )} - {ready && ( + {coursesReady && (
- {certifications.map((certification) => ( - - ))} + {allCertsData.certifications + .map((certification) => ( + + ))}
)}
diff --git a/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx b/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx index 587b579a5..c47da48a8 100644 --- a/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx +++ b/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx @@ -1,36 +1,102 @@ import classNames from 'classnames' -import { FC } from 'react' +import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react' import { Button } from '../../../../lib' -import { CourseTitle } from '../../learn-lib' +import { + CourseTitle, + LearnCertification, + MyCertificationCompleted, + MyCertificationInProgress, +} from '../../learn-lib' +import { getCertificatePath, getCoursePath, getLessonPathFromCurrentLesson } from '../../learn.routes' import styles from './CoursesCard.module.scss' interface CoursesCardProps { - credits?: string - link?: string - title: string - type: string + certification: LearnCertification + myCompletedCertifications: Array + myInProgressCertifications: Array } const CoursesCard: FC = (props: CoursesCardProps) => { + const [buttonLabel, setButtonLabel]: [string, Dispatch>] + = useState('') + const [link, setLink]: [string, Dispatch>] + = useState('') + + const courseEnabled: boolean = props.certification.state === 'active' + useEffect(() => { + + // if the course isn't enabled, there's nothing to do + if (!courseEnabled) { + return + } + + // set the button text and link based on the progress of the user for this course + const isCompleted: boolean = props.myCompletedCertifications + .some(comp => comp.certificationId === props.certification.id) + const inProgress: MyCertificationInProgress | undefined + = props.myInProgressCertifications + .find(i => i.certificationId === props.certification.id) + + if (isCompleted) { + // if the course is completed, View the Certificate + setButtonLabel('View Certificate') + setLink(getCertificatePath( + props.certification.providerName, + props.certification.certification + )) + + } else if (!inProgress) { + // if there is no in-progress lesson for the course, Get Started by going to the course details + setButtonLabel('Get Started') + setLink(getCoursePath( + props.certification.providerName, + props.certification.certification + )) + + } else { + // otherwise this course is in-progress, so Resume the course at the next lesson + setButtonLabel('Resume') + setLink(getLessonPathFromCurrentLesson( + props.certification.providerName, + props.certification.certification, + inProgress.currentLesson + )) + } + }, [ + courseEnabled, + getCertificatePath, + getCoursePath, + getLessonPathFromCurrentLesson, + props.certification, + props.myCompletedCertifications, + props.myInProgressCertifications, + setButtonLabel, + setLink, + ]) + return ( -
+
- {props.type} + {props.certification.category}
- +
- {props.link && ( + {!!link && (
diff --git a/src-ts/tools/learn/welcome/progress-block/ProgressBlock.module.scss b/src-ts/tools/learn/welcome/progress-block/ProgressBlock.module.scss index b05aba83c..ad8703417 100644 --- a/src-ts/tools/learn/welcome/progress-block/ProgressBlock.module.scss +++ b/src-ts/tools/learn/welcome/progress-block/ProgressBlock.module.scss @@ -16,22 +16,3 @@ padding: $pad-lg; } } - -.title-line { - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; - gap: $pad-lg; - - svg { - @include icon-mx; - margin-right: $pad-xs; - } -} - -.title { - display: flex; - align-items: center; - gap: $pad-md; -} diff --git a/src-ts/tools/learn/welcome/progress-block/ProgressBlock.tsx b/src-ts/tools/learn/welcome/progress-block/ProgressBlock.tsx index 30442a5ae..bb3ec1898 100644 --- a/src-ts/tools/learn/welcome/progress-block/ProgressBlock.tsx +++ b/src-ts/tools/learn/welcome/progress-block/ProgressBlock.tsx @@ -1,93 +1,33 @@ -import { FC, ReactNode, useContext, useMemo } from 'react' +import { FC } from 'react' -import { Button, profileContext, ProfileContextData } from '../../../../lib' import { LearnCertification, - LearningHat, - MyCertificationsProviderData, - MyCourseCompletedCard, - MyCourseInProgressCard, - useMyCertifications -} from '../../learn-lib' -import { LEARN_PATHS } from '../../learn.routes' + MyCertificationCompleted, + MyCertificationInProgress} from '../../learn-lib' -import InitState from './init-state/InitState' +import { NoProgress } from './no-progress' +import { ProgressAction } from './progress-action' import styles from './ProgressBlock.module.scss' interface ProgressBlockProps { - certificates: Array + allCertifications: Array + myCompletedCertifications: Array + myInProgressCertifications: Array + ready: boolean } const ProgressBlock: FC = (props: ProgressBlockProps) => { - const { profile }: ProfileContextData = useContext(profileContext) - const { completed, inProgress }: MyCertificationsProviderData = useMyCertifications(profile?.userId) - const isInit: boolean = !inProgress.length && !completed.length + if (!props.ready) { + return <> + } - const certificatesById: {[key: string]: LearnCertification} = useMemo(() => ( - props.certificates.reduce((certifs, certificate) => { - certifs[certificate.id] = certificate - return certifs -}, {} as unknown as {[key: string]: LearnCertification}) - ), [props.certificates]) - - const allMyLearningsLink: ReactNode = ( - -
diff --git a/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx b/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx index c47da48a8..a17a20460 100644 --- a/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx +++ b/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx @@ -5,8 +5,8 @@ import { Button } from '../../../../lib' import { CourseTitle, LearnCertification, - MyCertificationCompleted, - MyCertificationInProgress, + UserCertificationCompleted, + UserCertificationInProgress, } from '../../learn-lib' import { getCertificatePath, getCoursePath, getLessonPathFromCurrentLesson } from '../../learn.routes' @@ -14,8 +14,8 @@ import styles from './CoursesCard.module.scss' interface CoursesCardProps { certification: LearnCertification - myCompletedCertifications: Array - myInProgressCertifications: Array + userCompletedCertifications: Array + userInProgressCertifications: Array } const CoursesCard: FC = (props: CoursesCardProps) => { @@ -34,10 +34,10 @@ const CoursesCard: FC = (props: CoursesCardProps) => { } // set the button text and link based on the progress of the user for this course - const isCompleted: boolean = props.myCompletedCertifications + const isCompleted: boolean = props.userCompletedCertifications .some(comp => comp.certificationId === props.certification.id) - const inProgress: MyCertificationInProgress | undefined - = props.myInProgressCertifications + const inProgress: UserCertificationInProgress | undefined + = props.userInProgressCertifications .find(i => i.certificationId === props.certification.id) if (isCompleted) { @@ -71,8 +71,8 @@ const CoursesCard: FC = (props: CoursesCardProps) => { getCoursePath, getLessonPathFromCurrentLesson, props.certification, - props.myCompletedCertifications, - props.myInProgressCertifications, + props.userCompletedCertifications, + props.userInProgressCertifications, setButtonLabel, setLink, ]) diff --git a/src-ts/tools/learn/welcome/progress-block/ProgressBlock.tsx b/src-ts/tools/learn/welcome/progress-block/ProgressBlock.tsx index bb3ec1898..79c3c0667 100644 --- a/src-ts/tools/learn/welcome/progress-block/ProgressBlock.tsx +++ b/src-ts/tools/learn/welcome/progress-block/ProgressBlock.tsx @@ -2,8 +2,9 @@ import { FC } from 'react' import { LearnCertification, - MyCertificationCompleted, - MyCertificationInProgress} from '../../learn-lib' + UserCertificationCompleted, + UserCertificationInProgress +} from '../../learn-lib' import { NoProgress } from './no-progress' import { ProgressAction } from './progress-action' @@ -11,9 +12,9 @@ import styles from './ProgressBlock.module.scss' interface ProgressBlockProps { allCertifications: Array - myCompletedCertifications: Array - myInProgressCertifications: Array ready: boolean + userCompletedCertifications: Array + userInProgressCertifications: Array } const ProgressBlock: FC = (props: ProgressBlockProps) => { @@ -22,7 +23,7 @@ const ProgressBlock: FC = (props: ProgressBlockProps) => { return <> } - const isStarted: boolean = !!props.myInProgressCertifications.length || !!props.myCompletedCertifications.length + const isStarted: boolean = !!props.userInProgressCertifications.length || !!props.userCompletedCertifications.length return (
diff --git a/src-ts/tools/learn/welcome/progress-block/progress-action/ProgressAction.tsx b/src-ts/tools/learn/welcome/progress-block/progress-action/ProgressAction.tsx index 383e3e07f..000e322ab 100644 --- a/src-ts/tools/learn/welcome/progress-block/progress-action/ProgressAction.tsx +++ b/src-ts/tools/learn/welcome/progress-block/progress-action/ProgressAction.tsx @@ -4,10 +4,10 @@ import { Button } from '../../../../../lib' import { LearnCertification, LearningHat, - MyCertificationCompleted, - MyCertificationInProgress, MyCourseCompletedCard, MyCourseInProgressCard, + UserCertificationCompleted, + UserCertificationInProgress, } from '../../../learn-lib' import { LEARN_PATHS } from '../../../learn.routes' @@ -15,16 +15,16 @@ import styles from './ProgressAction.module.scss' interface ProgressActionProps { allCertifications: Array - myCompletedCertifications: Array - myInProgressCertifications: Array + userCompletedCertifications: Array + userInProgressCertifications: Array } const ProgressAction: FC = (props: ProgressActionProps) => { const { allCertifications, - myCompletedCertifications, - myInProgressCertifications, + userCompletedCertifications: myCompletedCertifications, + userInProgressCertifications: myInProgressCertifications, }: ProgressActionProps = props const allMyLearningsLink: ReactNode = ( From 5424707f2fd481cea27fb5c68a664a7e45f7f192 Mon Sep 17 00:00:00 2001 From: Brooke Date: Mon, 25 Jul 2022 11:59:02 -0700 Subject: [PATCH 4/4] PROD-2549 fix lint issues #time 5m --- .../course-outline/collapsible-item/CollapsibleItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx index 485c9538e..36f23db12 100644 --- a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx +++ b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx @@ -3,7 +3,7 @@ import { Dispatch, FC, ReactNode, SetStateAction, useCallback, useMemo, useState import { Link } from 'react-router-dom' import { IconOutline, IconSolid } from '../../../../../lib' -import { LearnModule, LearnUserCertificationProgress, LearnModuleProgress } from '../../../learn-lib' +import { LearnModule, LearnModuleProgress, LearnUserCertificationProgress } from '../../../learn-lib' import { StatusIcon } from '../status-icon' import { StepIcon } from '../step-icon'