Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
}

.bottomActions {
@media (min-width: 1151px) {
@media (min-width: 1150px) {
display: none;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import {
useHoursEstimateToRange,
UserCertificationProgressStatus,
} from '../../../../learn-lib'
import { getCertificatePath, getCoursePath } from '../../../../learn.routes'
import {
getCertificatePath,
getCoursePath,
getLessonPathFromCurrentLesson,
} from '../../../../learn.routes'
import CurriculumCard from '../CurriculumCard'

import styles from './CourseCard.module.scss'
Expand Down Expand Up @@ -58,9 +62,10 @@ const CourseCard: FC<CourseCardProps> = (props: CourseCardProps) => {
buttonStyle='primary'
size='xs'
label='Resume'
route={getCoursePath(
route={getLessonPathFromCurrentLesson(
props.provider,
props.certification.certification,
props.progress?.currentLesson,
)}
/>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
gap: $space-xxxxl;
position: relative;

@media (min-width: 1151px) {
@media (min-width: 1150px) {
padding-right: calc(445px + $space-xxl);
}

Expand All @@ -30,7 +30,7 @@
padding-right: calc(40vw + $space-xxl * 2);
}

@media (min-width: 1151px) {
@media (min-width: 1150px) {
padding-right: calc(445px + $space-xxxl * 2);
}
}
Expand Down
6 changes: 2 additions & 4 deletions src-ts/tools/learn/course-details/CourseDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ import {
CoursesProviderData,
CourseTitle,
ResourceProviderData,
TCACertification,
TCACertificationsProviderData,
useGetAllTCACertifications,
useGetCertification,
useGetCourses,
useGetResourceProvider,
Expand All @@ -31,8 +28,8 @@ import {
import { getCoursePath } from '../learn.routes'

import { CourseCurriculum } from './course-curriculum'
import styles from './CourseDetailsPage.module.scss'
import { TCACertificationBanner } from './tca-certification-banner'
import styles from './CourseDetailsPage.module.scss'

const CourseDetailsPage: FC<{}> = () => {

Expand Down Expand Up @@ -198,6 +195,7 @@ const CourseDetailsPage: FC<{}> = () => {
/>

<TCACertificationBanner
userId={profile?.userId}
className={styles.tcaCertBanner}
fccCertificateId={certificate.id}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
border-radius: $space-sm;

padding: $space-xxl;

@include ltemd {
padding: $space-lg;
}
}

.header {
Expand All @@ -14,6 +18,7 @@

svg {
@include icon-size(47);
flex: 0 0 auto;
}

&Content {
Expand All @@ -27,6 +32,12 @@
}
}

.certTitle {
display: flex;
align-items: flex-start;
gap: $space-xs;
}

.desc {
margin-top: $space-lg;
}
Expand All @@ -40,4 +51,46 @@
font-weight: $font-weight-bold;
color: $turq-160;
text-transform: uppercase;
}

.externalLink {
display: flex;
height: 24px;
align-items: center;
color: $turq-160;

svg {
@include icon-lg;
}
}

.statusBox {
display: flex;
align-items: center;

background: $black-5;
border-radius: $space-sm;

margin-top: $space-lg;
padding: $space-sm;
padding-left: 0;

.icon {
display: flex;
width: 40px;
height: 24px;
align-items: center;
justify-content: center;
flex: 0 0 auto;

color: $black-40;

svg {
@include icon-xxl;
}

&:global(.green) {
color: $turq-75;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
/* eslint-disable react/no-danger */
import { FC, useMemo } from 'react'
import { FC, ReactNode, useMemo } from 'react'
import { Link } from 'react-router-dom'
import classNames from 'classnames'

import { IconOutline, IconSolid } from '../../../../lib'
import {
CertificateBadgeIcon,
LearnUserCertificationProgress,
TCACertification,
TCACertificationProgressProviderData,
TCACertificationsProviderData,
useGetAllTCACertifications,
useGetTCACertificationProgress,
useGetUserCertifications,
UserCertificationProgressStatus,
UserCertificationsProviderData,
} from '../../learn-lib'
import { getTCACertificationPath } from '../../learn.routes'

import styles from './TCACertificationBanner.module.scss'

interface ProgressByIdCollection {
[key: string]: LearnUserCertificationProgress
}

function getStatusBox(icon: ReactNode, text: string, theme: string = 'gray'): ReactNode {
return (
<div className={styles.statusBox}>
<div className={classNames(styles.icon, theme)}>
{icon}
</div>
<div className='body-small'>
{text}
</div>
</div>
)
}

export interface TCACertificationBannerProps {
userId?: number
className?: string
fccCertificateId?: string
}
Expand All @@ -30,12 +55,71 @@ const TCACertificationBanner: FC<TCACertificationBannerProps> = (props: TCACerti
))
), [tcaCertifications, props.fccCertificateId])

// Fetch Enrollment status & progress
const {
progress: certifProgress,
ready: certifProgressReady,
}: TCACertificationProgressProviderData = useGetTCACertificationProgress(
props.userId as unknown as string,
certification?.dashedName as string,
{ enabled: !!certification && !!props.userId },
)

// Fetch the User's progress for all the tca certification's courses
// so we can show their progress even before they enroll with the certification
const {
progresses: certsProgress,
}: UserCertificationsProviderData = useGetUserCertifications('freeCodeCamp', {
enabled: certifProgressReady && !certifProgress,
})

const progressById: ProgressByIdCollection = useMemo(() => (
certsProgress?.reduce((all, progress) => {
all[progress.certificationId] = progress
return all
}, {} as ProgressByIdCollection) ?? {}
), [certsProgress])

if (!certification) {
return <></>
}

const certifUrl: string = getTCACertificationPath(certification.dashedName)

function renderStatusBox(): ReactNode {
if (!certification) {
return <></>
}

const coursesCount: number = certification.coursesCount
const completedCoursesCount: number = certifProgress
? Math.round(coursesCount * (certifProgress.certificationProgress / 100))
: certification.certificationResources.filter(d => (
progressById[d.freeCodeCampCertification.fccId]?.status === UserCertificationProgressStatus.completed
)).length

if (!completedCoursesCount) {
return certifProgress ? getStatusBox(
<IconOutline.DotsCircleHorizontalIcon />,
'Begin working towards earning this Topcoder Certification by starting this course today!',
) : <></>
}

if (completedCoursesCount === 1) {
return getStatusBox(
<IconOutline.ClockIcon />,
`Good job! You are making progress with 1 of ${coursesCount} required courses.`,
'green',
)
}

return getStatusBox(
<IconSolid.CheckCircleIcon />,
`You have completed ${completedCoursesCount} of ${coursesCount} required courses.`,
'green',
)
}

return (
<div className={classNames(props.className, styles.wrap)}>
<div className={styles.header}>
Expand All @@ -47,23 +131,34 @@ const TCACertificationBanner: FC<TCACertificationBannerProps> = (props: TCACerti
<div className='overline'>
This course is part of a topcoder certification:
</div>
<div className='body-main-bold'>
<div className={classNames(styles.certTitle, 'body-main-bold')}>
{certification.title}
{!!certifProgress && (
<Link className={styles.externalLink} to={certifUrl}>
<IconOutline.ExternalLinkIcon />
</Link>
)}
</div>
</div>
</div>

<p className={classNames(styles.desc, 'body-small')}>
{certification.description}
</p>

<Link
className={styles.link}
title='Learn more'
to={certifUrl}
>
Learn more
</Link>
{!certifProgress && (
<p className={classNames(styles.desc, 'body-small')}>
{certification.description}
</p>
)}

{renderStatusBox()}

{!certifProgress && (
<Link
className={styles.link}
title='Learn more'
to={certifUrl}
>
Learn more
</Link>
)}
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ import { UserCertificationInProgress } from './user-certification-in-progress.mo
import { LearnUserCertificationProgress, UserCertificationProgressStatus } from './user-certifications-functions'
import { UserCertificationsProviderData } from './user-certifications-provider-data.model'

interface GetUserCertificationsOptions {
enabled?: boolean
}

export function useGetUserCertifications(
provider: string = 'freeCodeCamp',
options: GetUserCertificationsOptions = {} as GetUserCertificationsOptions,
): UserCertificationsProviderData {
const profileContextData: ProfileContextData = useContext<ProfileContextData>(profileContext)
const userId: number | undefined = profileContextData?.profile?.userId
Expand All @@ -25,7 +30,7 @@ export function useGetUserCertifications(
const url: string = learnUrlGet('certification-progresses', params)

const { data, error }: SWRResponse<ReadonlyArray<LearnUserCertificationProgress>> = useSWR(url, {
isPaused: () => !userId,
isPaused: () => !userId || options?.enabled === false,
})
const loading: boolean = !data && !error

Expand Down