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..b30e16970 100755 --- a/src-ts/tools/learn/course-completed/CourseCompletedPage.tsx +++ b/src-ts/tools/learn/course-completed/CourseCompletedPage.tsx @@ -11,16 +11,16 @@ import { ProfileContextData } from '../../../lib' import { - CertificationsProviderData, + AllCertificationsProviderData, CollapsiblePane, CourseOutline, CoursesProviderData, CourseTitle, - MyCertificationProgressProviderData, - MyCertificationProgressStatus, - useCertificationsProvider, - useCoursesProvider, - useMyCertificationProgress + useAllCertifications, + useCourses, + UserCertificationProgressProviderData, + UserCertificationProgressStatus, + useUserCertificationProgress } from '../learn-lib' import { getCertificatePath, getCoursePath } from '../learn.routes' @@ -39,12 +39,12 @@ const CourseCompletedPage: FC<{}> = () => { const { course: courseData, ready: courseDataReady, - }: CoursesProviderData = useCoursesProvider(providerParam, certificationParam) + }: CoursesProviderData = useCourses(providerParam, certificationParam) const { - certificateProgress: progress, + certificationProgress: progress, ready: progressReady, - }: MyCertificationProgressProviderData = useMyCertificationProgress( + }: UserCertificationProgressProviderData = useUserCertificationProgress( profile?.userId, routeParams.provider, routeParams.certification @@ -53,7 +53,7 @@ const CourseCompletedPage: FC<{}> = () => { const { certification, ready: certifReady, - }: CertificationsProviderData = useCertificationsProvider(providerParam, progress?.certificationId, { + }: AllCertificationsProviderData = useAllCertifications(providerParam, progress?.certificationId, { enabled: progressReady && !!progress, }) @@ -66,9 +66,9 @@ const CourseCompletedPage: FC<{}> = () => { ], [coursePath, courseData]) useEffect(() => { - if (ready && progress?.status !== MyCertificationProgressStatus.completed) { - navigate(coursePath) - } + if (ready && progress?.status !== UserCertificationProgressStatus.completed) { + navigate(coursePath) + } }, [ coursePath, navigate, diff --git a/src-ts/tools/learn/course-details/CourseDetailsPage.tsx b/src-ts/tools/learn/course-details/CourseDetailsPage.tsx index 0dc41b16d..0cee9f3e8 100644 --- a/src-ts/tools/learn/course-details/CourseDetailsPage.tsx +++ b/src-ts/tools/learn/course-details/CourseDetailsPage.tsx @@ -13,12 +13,12 @@ import { import { CoursesProviderData, CourseTitle, - MyCertificationProgressProviderData, - MyCertificationProgressStatus, ResourceProviderData, - useCoursesProvider, - useMyCertificationProgress, - useResourceProvider + useCourses, + UserCertificationProgressProviderData, + UserCertificationProgressStatus, + useResourceProvider, + useUserCertificationProgress } from '../learn-lib' import { CourseCurriculum } from './course-curriculum' @@ -37,12 +37,12 @@ const CourseDetailsPage: FC<{}> = () => { const { course, ready: courseReady, - }: CoursesProviderData = useCoursesProvider(routeParams.provider ?? '', routeParams.certification) + }: CoursesProviderData = useCourses(routeParams.provider ?? '', routeParams.certification) const { - certificateProgress: progress, + certificationProgress: progress, ready: progressReady, - }: MyCertificationProgressProviderData = useMyCertificationProgress( + }: UserCertificationProgressProviderData = useUserCertificationProgress( profile?.userId, routeParams.provider, routeParams.certification, @@ -59,7 +59,7 @@ const CourseDetailsPage: FC<{}> = () => { return } - return progress?.status === MyCertificationProgressStatus.completed ? ( + return progress?.status === UserCertificationProgressStatus.completed ? ( <>

Suggested next steps

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..df4e68edf 100644 --- a/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx +++ b/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx @@ -9,13 +9,18 @@ import { LearningHat, LearnLesson, LearnModule, - LearnMyCertificationProgress, - MyCertificationProgressStatus, - startMyCertificationsProgressAsync, - UpdateMyCertificateProgressActions, - updateMyCertificationsProgressAsync + LearnUserCertificationProgress, + userCertificationProgressStartAsync, + UserCertificationProgressStatus, + userCertificationProgressUpdateAsync, + UserCertificationUpdateProgressActions } 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' @@ -24,11 +29,12 @@ import { TcAcademyPolicyModal } from './tc-academy-policy-modal' interface CourseCurriculumProps { course: LearnCourse profile?: UserProfile - progress?: LearnMyCertificationProgress + progress?: LearnUserCertificationProgress progressReady?: boolean } const CourseCurriculum: FC = (props: CourseCurriculumProps) => { + const navigate: NavigateFunction = useNavigate() const [searchParams]: any = useSearchParams() @@ -36,29 +42,35 @@ 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 ?? UserCertificationProgressStatus.inititialized const completedPercentage: number = (props.progress?.courseProgressPercentage ?? 0) / 100 - const inProgress: boolean = status === MyCertificationProgressStatus.inProgress || !!props.progress?.currentLesson - const isCompleted: boolean = status === MyCertificationProgressStatus.completed + const inProgress: boolean = status === UserCertificationProgressStatus.inProgress || !!props.progress?.currentLesson + const isCompleted: boolean = status === UserCertificationProgressStatus.completed /** * Redirect user to the currentLesson if there's already some progress recorded * 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 userCertificationProgressStartAsync( props.profile.userId, props.course.certificationId, props.course.id, @@ -107,9 +119,9 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp } ) } else { - await updateMyCertificationsProgressAsync( + await userCertificationProgressUpdateAsync( props.progress.id, - UpdateMyCertificateProgressActions.acceptHonestyPolicy, + UserCertificationUpdateProgressActions.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..32d34bd0c 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 { @@ -15,16 +25,16 @@ import { LearnLesson, LearnModule, LessonProviderData, - MyCertificationProgressProviderData, - MyCertificationProgressStatus, - startMyCertificationsProgressAsync, - UpdateMyCertificateProgressActions, - updateMyCertificationsProgressAsync, - useCoursesProvider, + useCourses, useLessonProvider, - useMyCertificationProgress, + UserCertificationProgressProviderData, + userCertificationProgressStartAsync, + UserCertificationProgressStatus, + userCertificationProgressUpdateAsync, + UserCertificationUpdateProgressActions, + useUserCertificationProgress, } 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' @@ -46,15 +56,19 @@ const FreeCodeCamp: FC<{}> = () => { const [lessonParam, setLessonParam]: [string, Dispatch>] = useState(routeParams.lesson ?? '') const { - certificateProgress, + certificationProgress: certificateProgress, setCertificateProgress, ready: progressReady, - }: MyCertificationProgressProviderData = useMyCertificationProgress(profile?.userId, routeParams.provider, certificationParam) + }: UserCertificationProgressProviderData = useUserCertificationProgress( + profile?.userId, + routeParams.provider, + certificationParam + ) const { course: courseData, ready: courseDataReady, - }: CoursesProviderData = useCoursesProvider(providerParam, certificationParam) + }: CoursesProviderData = useCourses(providerParam, certificationParam) const { lesson, ready: lessonReady }: LessonProviderData = useLessonProvider( providerParam, @@ -71,27 +85,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 +122,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 +145,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 +163,7 @@ const FreeCodeCamp: FC<{}> = () => { } if (!certificateProgress) { - startMyCertificationsProgressAsync( + userCertificationProgressStartAsync( profile.userId, lesson.course.certificationId, lesson.course.id, @@ -151,51 +173,51 @@ 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 - ) + userCertificationProgressUpdateAsync( + certificateProgress.id, + UserCertificationUpdateProgressActions.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( + userCertificationProgressUpdateAsync( certificateProgress.id, - UpdateMyCertificateProgressActions.completeLesson, + UserCertificationUpdateProgressActions.completeLesson, currentLesson ).then(setCertificateProgress) } } 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 === UserCertificationProgressStatus.inProgress + ) { + userCertificationProgressUpdateAsync( + certificateProgress.id, + UserCertificationUpdateProgressActions.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/certification.store.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-functions/all-certifications.store.ts similarity index 92% rename from src-ts/tools/learn/learn-lib/certifications-provider/certifications-functions/certification.store.ts rename to src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-functions/all-certifications.store.ts index b0e8f3cbb..e42c28e5f 100755 --- a/src-ts/tools/learn/learn-lib/certifications-provider/certifications-functions/certification.store.ts +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-functions/all-certifications.store.ts @@ -3,7 +3,7 @@ import { getPath } from '../../learn-url.config' import { LearnCertification } from './learn-certification.model' -export function getCertificationsAsync( +export function getAsync( providerName: string = 'freeCodeCamp', certificationId?: string ): Promise> { diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-functions/index.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-functions/index.ts new file mode 100755 index 000000000..4e1ec9d24 --- /dev/null +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-functions/index.ts @@ -0,0 +1,4 @@ +export { + getAsync as allCertificationsGetAsync, +} from './all-certifications.store' +export * from './learn-certification.model' diff --git a/src-ts/tools/learn/learn-lib/certifications-provider/certifications-functions/learn-certification.model.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-functions/learn-certification.model.ts similarity index 83% rename from src-ts/tools/learn/learn-lib/certifications-provider/certifications-functions/learn-certification.model.ts rename to src-ts/tools/learn/learn-lib/all-certifications-provider/all-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/all-certifications-provider/all-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/certifications-provider/certifications-provider-data.model.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts similarity index 57% rename from src-ts/tools/learn/learn-lib/certifications-provider/certifications-provider-data.model.ts rename to src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts index a7851fd88..cc5d28762 100755 --- a/src-ts/tools/learn/learn-lib/certifications-provider/certifications-provider-data.model.ts +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts @@ -1,6 +1,6 @@ -import { LearnCertification } from './certifications-functions' +import { LearnCertification } from './all-certifications-functions' -export interface CertificationsProviderData { +export interface AllCertificationsProviderData { certification?: LearnCertification certifications: Array certificationsCount: number diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx new file mode 100644 index 000000000..c68739ee4 --- /dev/null +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx @@ -0,0 +1,50 @@ +import { Dispatch, SetStateAction, useEffect, useState } from 'react' + +import { allCertificationsGetAsync, LearnCertification } from './all-certifications-functions' +import { AllCertificationsProviderData } from './all-certifications-provider-data.model' + +interface CertificationsAllProviderOptions { + enabled?: boolean +} + +export function useAllCertifications( + provider?: string, + certificationId?: string, + options?: CertificationsAllProviderOptions +): AllCertificationsProviderData { + + const [state, setState]: + [AllCertificationsProviderData, Dispatch>] + = useState({ + certifications: [], + certificationsCount: 0, + loading: false, + ready: false, + }) + + useEffect(() => { + setState((prevState) => ({ + ...prevState, + loading: true, + })) + + if (options?.enabled === false) { + return + } + + allCertificationsGetAsync(provider, certificationId) + .then((certifications) => { + setState((prevState) => ({ + ...prevState, + ...(certificationId ? { certification: certifications as unknown as LearnCertification } : { + certifications: [...certifications], + }), + certificationsCount: certifications.length, + loading: false, + ready: true, + })) + }) + }, [provider, certificationId, options?.enabled]) + + return state +} diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts new file mode 100755 index 000000000..f8e61f839 --- /dev/null +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts @@ -0,0 +1,3 @@ +export * from './all-certifications-functions' +export * from './all-certifications-provider-data.model' +export * from './all-certifications.provider' diff --git a/src-ts/tools/learn/learn-lib/certifications-provider/certifications-functions/index.ts b/src-ts/tools/learn/learn-lib/certifications-provider/certifications-functions/index.ts deleted file mode 100755 index 17186582c..000000000 --- a/src-ts/tools/learn/learn-lib/certifications-provider/certifications-functions/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './certification.store' -export * from './learn-certification.model' diff --git a/src-ts/tools/learn/learn-lib/certifications-provider/certifications.provider.tsx b/src-ts/tools/learn/learn-lib/certifications-provider/certifications.provider.tsx deleted file mode 100644 index 39ab8c339..000000000 --- a/src-ts/tools/learn/learn-lib/certifications-provider/certifications.provider.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Dispatch, SetStateAction, useEffect, useState } from 'react' - -import { getCertificationsAsync, LearnCertification } from './certifications-functions' -import { CertificationsProviderData } from './certifications-provider-data.model' - -interface CertificationsProviderOptions { - enabled?: boolean -} - -export function useCertificationsProvider( - provider?: string, - certificationId?: string, - options?: CertificationsProviderOptions -): CertificationsProviderData { - const [state, setState]: [CertificationsProviderData, Dispatch>] = useState({ - certifications: [], - certificationsCount: 0, - loading: false, - ready: false, - }) - - useEffect(() => { - setState((prevState) => ({ - ...prevState, - loading: true, - })) - - if (options?.enabled === false) { - return - } - - getCertificationsAsync(provider, certificationId).then((certifications) => { - setState((prevState) => ({ - ...prevState, - ...(certificationId ? {certification: certifications as unknown as LearnCertification} : { - certifications: [...certifications], - }), - certificationsCount: certifications.length, - loading: false, - ready: true, - })) - }) - }, [provider, certificationId, options?.enabled]) - - return state -} diff --git a/src-ts/tools/learn/learn-lib/certifications-provider/index.ts b/src-ts/tools/learn/learn-lib/certifications-provider/index.ts deleted file mode 100755 index c4af9fcdf..000000000 --- a/src-ts/tools/learn/learn-lib/certifications-provider/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './certifications-functions' -export * from './certifications-provider-data.model' -export * from './certifications.provider' 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..63015da4e 100644 --- a/src-ts/tools/learn/learn-lib/course-outline/CourseOutline.tsx +++ b/src-ts/tools/learn/learn-lib/course-outline/CourseOutline.tsx @@ -6,9 +6,9 @@ import { LearnCourse, LearnLesson, LearnModule, - LearnMyCertificationProgress, + LearnUserCertificationProgress, } from '../../learn-lib' -import { getFccLessonPath } from '../../learn.routes' +import { getLessonPathFromModule } from '../../learn.routes' import { CollapsibleItem } from './collapsible-item' import styles from './CourseOutline.module.scss' @@ -16,20 +16,21 @@ import styles from './CourseOutline.module.scss' interface CourseOutlineProps { course?: LearnCourse currentStep?: string - progress?: LearnMyCertificationProgress + progress?: LearnUserCertificationProgress ready?: boolean } 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/course-outline/collapsible-item/CollapsibleItem.tsx b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx index e88fe12d9..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, LearnMyCertificationProgress, LearnMyModuleProgress } from '../../../learn-lib' +import { LearnModule, LearnModuleProgress, LearnUserCertificationProgress } from '../../../learn-lib' import { StatusIcon } from '../status-icon' import { StepIcon } from '../step-icon' @@ -22,7 +22,7 @@ interface CollapsibleItemProps { lessonsCount: number moduleKey: string path?: (item: any) => string - progress?: LearnMyCertificationProgress['modules'] + progress?: LearnUserCertificationProgress['modules'] shortDescription: Array title: string } @@ -36,7 +36,7 @@ const CollapsibleItem: FC = (props: CollapsibleItemProps) setIsOpen(open => !open) }, []) - const progress: LearnMyModuleProgress|undefined = useMemo(() => { + const progress: LearnModuleProgress | undefined = useMemo(() => { return props.progress?.find(m => m.module === props.moduleKey) }, [props.progress, props.moduleKey]) diff --git a/src-ts/tools/learn/learn-lib/courses-provider/courses.provider.tsx b/src-ts/tools/learn/learn-lib/courses-provider/courses.provider.tsx index 780cd6781..963e56348 100644 --- a/src-ts/tools/learn/learn-lib/courses-provider/courses.provider.tsx +++ b/src-ts/tools/learn/learn-lib/courses-provider/courses.provider.tsx @@ -3,11 +3,13 @@ import { Dispatch, SetStateAction, useEffect, useState } from 'react' import { getCourseAsync } from './courses-functions' import { CoursesProviderData } from './courses-provider-data.model' -export function useCoursesProvider(provider: string, certification?: string): CoursesProviderData { - const [state, setState]: [CoursesProviderData, Dispatch>] = useState({ - loading: false, - ready: false, - }) +export function useCourses(provider: string, certification?: string): CoursesProviderData { + + const [state, setState]: [CoursesProviderData, Dispatch>] + = useState({ + loading: false, + ready: false, + }) useEffect(() => { if (!certification) { diff --git a/src-ts/tools/learn/learn-lib/index.ts b/src-ts/tools/learn/learn-lib/index.ts index abd290825..af4a0dcb8 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 './all-certifications-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 './user-certifications-provider' 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 deleted file mode 100755 index 9f7a8661c..000000000 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './my-certification-progress-provider-data.model' -export * from './my-certification-progress.provider' -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-progress-provider-data.model.ts b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-progress-provider-data.model.ts deleted file mode 100755 index 8ea447619..000000000 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-progress-provider-data.model.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { LearnMyCertificationProgress } from './my-certifications-functions' - -export interface MyCertificationProgressProviderData { - certificateProgress?: LearnMyCertificationProgress - loading: boolean - ready: boolean - setCertificateProgress: (progess: LearnMyCertificationProgress) => void, -} 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 deleted file mode 100644 index ddc93a577..000000000 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certification-progress.provider.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Dispatch, SetStateAction, useEffect, useState } from 'react' - -import { MyCertificationProgressProviderData } from './my-certification-progress-provider-data.model' -import { getMyCertificationsProgressAsync, LearnMyCertificationProgress } from './my-certifications-functions' - -export function useMyCertificationProgress(userId?: number, provider?: string, certification?: string): MyCertificationProgressProviderData { - function setCertificateProgress(progress: LearnMyCertificationProgress): void { - setState((prevState) => ({...prevState, certificateProgress: progress})) - } - - const [state, setState]: [MyCertificationProgressProviderData, Dispatch>] = useState({ - certificateProgress: undefined, - loading: false, - ready: false, - setCertificateProgress, - }) - - useEffect(() => { - setState((prevState) => ({ - ...prevState, - loading: true, - })) - - 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]) - - return state -} diff --git a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/certificate-progress.decorators.ts b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/certificate-progress.decorators.ts deleted file mode 100755 index 39cca583f..000000000 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/certificate-progress.decorators.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { LearnMyCertificationProgress } from './learn-my-certification-progress.model' - -export function decorateCompletedPercentage(myCertification: LearnMyCertificationProgress): LearnMyCertificationProgress { - const progress: {completedLessons: number, lessonCount: number } = myCertification.modules.reduce((prev, m) => ({ - completedLessons: prev.completedLessons + m.completedLessons.length, - lessonCount: prev.lessonCount + m.lessonCount, - }), {lessonCount: 0, completedLessons: 0}) - - return { - ...myCertification, - completedPercentage: progress.completedLessons / progress.lessonCount, - } -} - -export function mapCompletedPercentage(myCertifications: Array): Array { - return myCertifications.map(decorateCompletedPercentage) -} 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 deleted file mode 100755 index c5eebf2e5..000000000 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -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' 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 deleted file mode 100755 index b9a3c8e6f..000000000 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/my-certification-progress-status.enum.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum MyCertificationProgressStatus { - inProgress = 'in-progress', - completed = 'completed', -} 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 deleted file mode 100755 index eb4fbcb1c..000000000 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/my-certifications.store.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { xhrGetAsync, xhrPostAsync, xhrPutAsync } from '../../../../../lib/functions' -import { getPath } from '../../learn-url.config' - -import { LearnMyCertificationProgress } from './learn-my-certification-progress.model' - -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> { - return xhrGetAsync>(getPath( - 'certification-progresses', - [ - `?userId=${userId}`, - provider && `provider=${provider}`, - certification && `certification=${certification}`, - ].filter(Boolean).join('&'), - )) -} - -export function startMyCertificationsProgressAsync(userId: number, certificationId: string, courseId: string, data: any): Promise { - return xhrPostAsync<{}, LearnMyCertificationProgress>(getPath( - 'certification-progresses', - `${userId}`, - certificationId, - courseId, - ), data) -} - -export function updateMyCertificationsProgressAsync(certificationProgressId: string, action: UpdateMyCertificateProgressActions, data: any): Promise { - return xhrPutAsync<{}, LearnMyCertificationProgress>(getPath( - 'certification-progresses', - certificationProgressId, - action - ), data) -} diff --git a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-provider-data.model.ts b/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-provider-data.model.ts deleted file mode 100755 index 9efa7186c..000000000 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-provider-data.model.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { LearnMyCertificationProgress } from './my-certifications-functions' - -export interface MyCertificationsProviderData { - completed: Array - inProgress: Array - loading: boolean - ready: boolean -} 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 deleted file mode 100644 index 81a21cdf1..000000000 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications.provider.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Dispatch, SetStateAction, useEffect, useState } from 'react' - -import { getMyCertificationsProgressAsync, MyCertificationProgressStatus } from './my-certifications-functions' -import { MyCertificationsProviderData } from './my-certifications-provider-data.model' - -export function useMyCertifications(userId?: number): MyCertificationsProviderData { - const [state, setState]: [MyCertificationsProviderData, Dispatch>] = useState({ - completed: [], - inProgress: [], - loading: false, - ready: false, - }) - - useEffect(() => { - setState((prevState) => ({ - ...prevState, - loading: true, - })) - - 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]) - - 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..50eebb624 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 @@ -11,9 +11,9 @@ import { CoursesProviderData, CourseTitle, LearnCertification, - useCoursesProvider, + useCourses, } 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 = useCourses(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-lib/user-certifications-provider/index.ts b/src-ts/tools/learn/learn-lib/user-certifications-provider/index.ts new file mode 100755 index 000000000..2c2e9770c --- /dev/null +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/index.ts @@ -0,0 +1,7 @@ +export * from './user-certification-completed.model' +export * from './user-certification-in-progress.model' +export * from './user-certification-progress-provider-data.model' +export * from './user-certification-progress.provider' +export * from './user-certifications-functions' +export * from './user-certifications-provider-data.model' +export * from './user-certifications.provider' diff --git a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certification-completed.model.ts b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certification-completed.model.ts new file mode 100644 index 000000000..2f6c4a198 --- /dev/null +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certification-completed.model.ts @@ -0,0 +1,5 @@ +import { LearnUserCertificationProgress } from './user-certifications-functions' + +export interface UserCertificationCompleted extends LearnUserCertificationProgress { + completedDate: string +} diff --git a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certification-in-progress.model.ts b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certification-in-progress.model.ts new file mode 100644 index 000000000..699eaf308 --- /dev/null +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certification-in-progress.model.ts @@ -0,0 +1,6 @@ +import { LearnUserCertificationProgress } from './user-certifications-functions' + +export interface UserCertificationInProgress extends LearnUserCertificationProgress { + currentLesson: string + startDate: string +} diff --git a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certification-progress-provider-data.model.ts b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certification-progress-provider-data.model.ts new file mode 100755 index 000000000..2f75dbe98 --- /dev/null +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certification-progress-provider-data.model.ts @@ -0,0 +1,8 @@ +import { LearnUserCertificationProgress } from './user-certifications-functions' + +export interface UserCertificationProgressProviderData { + certificationProgress?: LearnUserCertificationProgress + loading: boolean + ready: boolean + setCertificateProgress: (progess: LearnUserCertificationProgress) => void, +} diff --git a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certification-progress.provider.tsx b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certification-progress.provider.tsx new file mode 100644 index 000000000..6326b2856 --- /dev/null +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certification-progress.provider.tsx @@ -0,0 +1,48 @@ +import { Dispatch, SetStateAction, useEffect, useState } from 'react' + +import { UserCertificationProgressProviderData } from './user-certification-progress-provider-data.model' +import { LearnUserCertificationProgress, userCertificationProgressGetAsync } from './user-certifications-functions' + +export function useUserCertificationProgress(userId?: number, provider?: string, certification?: string): + UserCertificationProgressProviderData { + + function setCertificateProgress(progress: LearnUserCertificationProgress): void { + setState((prevState) => ({ ...prevState, certificationProgress: progress })) + } + + const [state, setState]: + [UserCertificationProgressProviderData, Dispatch>] + = useState({ + certificationProgress: undefined, + loading: false, + ready: false, + setCertificateProgress, + }) + + useEffect(() => { + + setState((prevState) => ({ + ...prevState, + loading: true, + })) + + if (!userId) { + return + } + + userCertificationProgressGetAsync(userId, provider, certification) + .then((myCertifications) => { + setState((prevState) => ({ + ...prevState, + certificationProgress: myCertifications.find(c => c.certification === certification), + loading: false, + ready: true, + })) + }) + }, [ + certification, + provider, + ]) + + return state +} diff --git a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/index.ts b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/index.ts new file mode 100755 index 000000000..507435d3c --- /dev/null +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/index.ts @@ -0,0 +1,9 @@ +export * from './learn-user-certification-progress.model' +export * from './learn-module-progress.model' +export * from './user-certification-progress-status.enum' +export * from './user-certification-update-progress-actions.enum' +export { + getAsync as userCertificationProgressGetAsync, + startAsync as userCertificationProgressStartAsync, + updateAsync as userCertificationProgressUpdateAsync +} from './user-certification-progress.store' diff --git a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/learn-my-module-progress.model.ts b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/learn-module-progress.model.ts similarity index 82% rename from src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/learn-my-module-progress.model.ts rename to src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/learn-module-progress.model.ts index 19f9e5229..d5e474c24 100644 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/learn-my-module-progress.model.ts +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/learn-module-progress.model.ts @@ -1,4 +1,4 @@ -export interface LearnMyModuleProgress { +export interface LearnModuleProgress { completedLessons: Array<{ completedDate?: string dashedName: string diff --git a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/learn-my-certification-progress.model.ts b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/learn-user-certification-progress.model.ts similarity index 50% rename from src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/learn-my-certification-progress.model.ts rename to src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/learn-user-certification-progress.model.ts index ff43b5847..f218dfd15 100644 --- a/src-ts/tools/learn/learn-lib/my-certifications-provider/my-certifications-functions/learn-my-certification-progress.model.ts +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/learn-user-certification-progress.model.ts @@ -1,7 +1,7 @@ -import { LearnMyModuleProgress } from './learn-my-module-progress.model' -import { MyCertificationProgressStatus } from './my-certification-progress-status.enum' +import { LearnModuleProgress } from './learn-module-progress.model' +import { UserCertificationProgressStatus } from './user-certification-progress-status.enum' -export interface LearnMyCertificationProgress { +export interface LearnUserCertificationProgress { academicHonestyPolicyAcceptedAt?: number, certification: string certificationId: string @@ -11,8 +11,8 @@ export interface LearnMyCertificationProgress { courseProgressPercentage: number currentLesson?: string id: string - modules: Array + modules: Array provider: string startDate: string - status: MyCertificationProgressStatus + status: UserCertificationProgressStatus } diff --git a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/user-certification-progress-status.enum.ts b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/user-certification-progress-status.enum.ts new file mode 100755 index 000000000..fd5998abb --- /dev/null +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/user-certification-progress-status.enum.ts @@ -0,0 +1,5 @@ +export enum UserCertificationProgressStatus { + inititialized = 'init', + inProgress = 'in-progress', + completed = 'completed', +} diff --git a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/user-certification-progress.store.ts b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/user-certification-progress.store.ts new file mode 100755 index 000000000..1174416d0 --- /dev/null +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/user-certification-progress.store.ts @@ -0,0 +1,33 @@ +import { xhrGetAsync, xhrPostAsync, xhrPutAsync } from '../../../../../lib/functions' +import { getPath } from '../../learn-url.config' + +import { LearnUserCertificationProgress } from './learn-user-certification-progress.model' +import { UserCertificationUpdateProgressActions } from './user-certification-update-progress-actions.enum' + +export function getAsync(userId: number, provider?: string, certification?: string): Promise> { + return xhrGetAsync>(getPath( + 'certification-progresses', + [ + `?userId=${userId}`, + provider && `provider=${provider}`, + certification && `certification=${certification}`, + ].filter(Boolean).join('&'), + )) +} + +export function startAsync(userId: number, certificationId: string, courseId: string, data: any): Promise { + return xhrPostAsync<{}, LearnUserCertificationProgress>(getPath( + 'certification-progresses', + `${userId}`, + certificationId, + courseId, + ), data) +} + +export function updateAsync(certificationProgressId: string, action: UserCertificationUpdateProgressActions, data: any): Promise { + return xhrPutAsync<{}, LearnUserCertificationProgress>(getPath( + 'certification-progresses', + certificationProgressId, + action + ), data) +} diff --git a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/user-certification-update-progress-actions.enum.ts b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/user-certification-update-progress-actions.enum.ts new file mode 100644 index 000000000..9e7017ba0 --- /dev/null +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/user-certification-update-progress-actions.enum.ts @@ -0,0 +1,6 @@ +export enum UserCertificationUpdateProgressActions { + acceptHonestyPolicy = 'honesty-policy', + currentLesson = 'current-lesson', + completeLesson = 'complete-lesson', + completeCertificate = 'complete-certification', +} diff --git a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-provider-data.model.ts b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-provider-data.model.ts new file mode 100755 index 000000000..807a60252 --- /dev/null +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-provider-data.model.ts @@ -0,0 +1,11 @@ +import { LearnUserCertificationProgress } from './user-certifications-functions' + +export interface UserCertificationsProviderData { + completed: Array + inProgress: Array + loading: boolean + ready: boolean +} diff --git a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications.provider.tsx b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications.provider.tsx new file mode 100644 index 000000000..750c1ef4e --- /dev/null +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications.provider.tsx @@ -0,0 +1,51 @@ +import { Dispatch, SetStateAction, useContext, useEffect, useState } from 'react' + +import { profileContext, ProfileContextData } from '../../../../lib' + +import { UserCertificationCompleted } from './user-certification-completed.model' +import { UserCertificationInProgress } from './user-certification-in-progress.model' +import { userCertificationProgressGetAsync, UserCertificationProgressStatus } from './user-certifications-functions' +import { UserCertificationsProviderData } from './user-certifications-provider-data.model' + +export function useUserCertifications(): UserCertificationsProviderData { + + const profileContextData: ProfileContextData = useContext(profileContext) + const [state, setState]: [UserCertificationsProviderData, Dispatch>] = useState({ + completed: [], + inProgress: [], + loading: false, + ready: false, + }) + + useEffect(() => { + + setState((prevState) => ({ + ...prevState, + loading: true, + })) + + const userId: number | undefined = profileContextData?.profile?.userId + if (!userId) { + return + } + + userCertificationProgressGetAsync(userId) + .then((myCertifications) => { + const completed: Array = myCertifications + .filter(c => c.status === UserCertificationProgressStatus.completed) + .map(c => c as UserCertificationCompleted) + const inProgress: Array = myCertifications + .filter(c => c.status === UserCertificationProgressStatus.inProgress) + .map(c => c as UserCertificationInProgress) + setState((prevState) => ({ + ...prevState, + completed, + inProgress, + loading: false, + ready: true, + })) + }) + }, [profileContextData?.profile?.userId]) + + return state +} 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..8fbd166a7 100755 --- a/src-ts/tools/learn/my-certificate/MyCertificate.tsx +++ b/src-ts/tools/learn/my-certificate/MyCertificate.tsx @@ -12,10 +12,10 @@ import { } from '../../../lib' import { CoursesProviderData, - MyCertificationProgressProviderData, - MyCertificationProgressStatus, - useCoursesProvider, - useMyCertificationProgress, + useCourses, + UserCertificationProgressProviderData, + UserCertificationProgressStatus, + useUserCertificationProgress, } from '../learn-lib' import { getCoursePath } from '../learn.routes' @@ -32,21 +32,21 @@ const MyCertificate: FC<{}> = () => { const providerParam: string = routeParams.provider ?? '' const certificationParam: string = routeParams.certification ?? '' const coursePath: string = getCoursePath(providerParam, certificationParam) - const certificateElRef: MutableRefObject = useRef() - const certificateWrapRef: MutableRefObject = useRef() + const certificateElRef: MutableRefObject = useRef() + const certificateWrapRef: MutableRefObject = useRef() const userName: string = [profile?.firstName, profile?.lastName].filter(Boolean).join(' ') const { course, ready: courseReady, - }: CoursesProviderData = useCoursesProvider(providerParam, certificationParam) + }: CoursesProviderData = useCourses(providerParam, certificationParam) const certificationTitle: string = `${userName} - ${course?.title} Certification` const { - certificateProgress, + certificationProgress: certificateProgress, ready: progressReady, - }: MyCertificationProgressProviderData = useMyCertificationProgress( + }: UserCertificationProgressProviderData = useUserCertificationProgress( profile?.userId, routeParams.provider, routeParams.certification @@ -60,7 +60,7 @@ const MyCertificate: FC<{}> = () => { navigate(-1) } - async function getCertificateCanvas(): Promise { + async function getCertificateCanvas(): Promise { if (!certificateElRef.current) { return } @@ -81,7 +81,7 @@ const MyCertificate: FC<{}> = () => { } async function handleDownload(): Promise { - const canvas: HTMLCanvasElement|void = await getCertificateCanvas() + const canvas: HTMLCanvasElement | void = await getCertificateCanvas() if (!canvas) { return } @@ -89,11 +89,11 @@ const MyCertificate: FC<{}> = () => { } async function handlePrint(): Promise { - const canvas: HTMLCanvasElement|void = await getCertificateCanvas() + const canvas: HTMLCanvasElement | void = await getCertificateCanvas() if (!canvas) { return } - const printWindow: Window|null = window.open('') + const printWindow: Window | null = window.open('') if (!printWindow) { return @@ -106,7 +106,7 @@ const MyCertificate: FC<{}> = () => { } async function handleShare(): Promise { - const canvas: HTMLCanvasElement|void = await getCertificateCanvas() + const canvas: HTMLCanvasElement | void = await getCertificateCanvas() if (!canvas) { return } @@ -118,15 +118,15 @@ const MyCertificate: FC<{}> = () => { files: [sharedImg], title: certificationTitle, }) - } catch (error) {} + } catch (error) { } } } useEffect(() => { - if (ready && certificateProgress?.status !== MyCertificationProgressStatus.completed) { - navigate(coursePath) + if (ready && certificateProgress?.status !== UserCertificationProgressStatus.completed) { + navigate(coursePath) } - }, [ + }, [ certificateProgress, coursePath, navigate, diff --git a/src-ts/tools/learn/my-learning/MyLearning.tsx b/src-ts/tools/learn/my-learning/MyLearning.tsx index e7ea6b341..90412c0e5 100755 --- a/src-ts/tools/learn/my-learning/MyLearning.tsx +++ b/src-ts/tools/learn/my-learning/MyLearning.tsx @@ -2,14 +2,14 @@ import { FC, useContext, useMemo } from 'react' import { Breadcrumb, BreadcrumbItemModel, ContentLayout, Portal, profileContext, ProfileContextData } from '../../../lib' import { - CertificationsProviderData, + AllCertificationsProviderData, LearnCertification, LearningHat, - MyCertificationsProviderData, MyCourseCompletedCard, MyCourseInProgressCard, - useCertificationsProvider, - useMyCertifications, + useAllCertifications, + UserCertificationsProviderData, + useUserCertifications, WaveHero } from '../learn-lib' import { LEARN_PATHS } from '../learn.routes' @@ -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 }: UserCertificationsProviderData = useUserCertifications() + const { certifications }: AllCertificationsProviderData = useAllCertifications() 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..cd7842056 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 { + AllCertificationsProviderData, + useAllCertifications, + UserCertificationsProviderData, + useUserCertifications, + 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: AllCertificationsProviderData = useAllCertifications() + const userCertsData: UserCertificationsProviderData = useUserCertifications() + + const coursesReady: boolean = allCertsData.ready && userCertsData.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..a17a20460 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, + UserCertificationCompleted, + UserCertificationInProgress, +} 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 + userCompletedCertifications: Array + userInProgressCertifications: 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.userCompletedCertifications + .some(comp => comp.certificationId === props.certification.id) + const inProgress: UserCertificationInProgress | undefined + = props.userInProgressCertifications + .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.userCompletedCertifications, + props.userInProgressCertifications, + 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..79c3c0667 100644 --- a/src-ts/tools/learn/welcome/progress-block/ProgressBlock.tsx +++ b/src-ts/tools/learn/welcome/progress-block/ProgressBlock.tsx @@ -1,93 +1,34 @@ -import { FC, ReactNode, useContext, useMemo } from 'react' +import { FC } from 'react' -import { Button, profileContext, ProfileContextData } from '../../../../lib' import { LearnCertification, - LearningHat, - MyCertificationsProviderData, - MyCourseCompletedCard, - MyCourseInProgressCard, - useMyCertifications + UserCertificationCompleted, + UserCertificationInProgress } from '../../learn-lib' -import { LEARN_PATHS } from '../../learn.routes' -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 + ready: boolean + userCompletedCertifications: Array + userInProgressCertifications: Array } 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 = ( - -