mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2025-12-19 18:18:27 -05:00
192 lines
5.3 KiB
TypeScript
192 lines
5.3 KiB
TypeScript
import React, { Fragment } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { connect } from 'react-redux';
|
|
import { createSelector } from 'reselect';
|
|
import { Spacer } from '@freecodecamp/ui';
|
|
|
|
import {
|
|
type SuperBlocks,
|
|
SuperBlockStage,
|
|
getStageOrder,
|
|
superBlockStages
|
|
} from '../../../../shared/config/curriculum';
|
|
import { SuperBlockIcon } from '../../assets/superblock-icon';
|
|
import LinkButton from '../../assets/icons/link-button';
|
|
import { ButtonLink } from '../helpers';
|
|
import { getSuperBlockTitleForMap } from '../../utils/superblock-map-titles';
|
|
import { showUpcomingChanges } from '../../../config/env.json';
|
|
|
|
import './map.css';
|
|
|
|
import {
|
|
isSignedInSelector,
|
|
currentCertsSelector
|
|
} from '../../redux/selectors';
|
|
|
|
import { RibbonIcon } from '../../assets/icons/completion-ribbon';
|
|
|
|
import { CurrentCert, ClaimedCertifications } from '../../redux/prop-types';
|
|
import {
|
|
certSlugTypeMap,
|
|
superBlockCertTypeMap
|
|
} from '../../../../shared/config/certification-settings';
|
|
import { completedChallengesIdsSelector } from '../../templates/Challenges/redux/selectors';
|
|
|
|
interface MapProps {
|
|
forLanding?: boolean;
|
|
isSignedIn: boolean;
|
|
currentCerts: CurrentCert[];
|
|
claimedCertifications?: ClaimedCertifications;
|
|
completedChallengeIds: string[];
|
|
allChallenges: {
|
|
id: string;
|
|
superBlock: SuperBlocks;
|
|
}[];
|
|
}
|
|
|
|
const linkSpacingStyle = {
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
gap: '15px'
|
|
};
|
|
|
|
const superBlockHeadings: { [key in SuperBlockStage]: string } = {
|
|
[SuperBlockStage.Core]: 'landing.core-certs-heading',
|
|
[SuperBlockStage.English]: 'landing.learn-english-heading',
|
|
[SuperBlockStage.Professional]: 'landing.professional-certs-heading',
|
|
[SuperBlockStage.Extra]: 'landing.interview-prep-heading',
|
|
[SuperBlockStage.Legacy]: 'landing.legacy-curriculum-heading',
|
|
[SuperBlockStage.Next]: 'landing.next-heading',
|
|
[SuperBlockStage.NextEnglish]: 'landing.next-english-heading',
|
|
[SuperBlockStage.Upcoming]: 'landing.upcoming-heading'
|
|
};
|
|
|
|
const mapStateToProps = createSelector(
|
|
isSignedInSelector,
|
|
currentCertsSelector,
|
|
completedChallengesIdsSelector,
|
|
(isSignedIn: boolean, currentCerts, completedChallengeIds: string[]) => ({
|
|
isSignedIn,
|
|
currentCerts,
|
|
completedChallengeIds
|
|
})
|
|
);
|
|
|
|
function MapLi({
|
|
superBlock,
|
|
landing = false,
|
|
completed,
|
|
claimed,
|
|
showProgressionLines = false,
|
|
showNumbers = false,
|
|
index
|
|
}: {
|
|
superBlock: SuperBlocks;
|
|
landing: boolean;
|
|
completed: boolean;
|
|
claimed: boolean;
|
|
showProgressionLines?: boolean;
|
|
showNumbers?: boolean;
|
|
index: number;
|
|
}) {
|
|
return (
|
|
<li
|
|
data-test-label='curriculum-map-button'
|
|
data-playwright-test-label='curriculum-map-button'
|
|
>
|
|
<div className='progress-icon-wrapper'>
|
|
<div
|
|
className={`progress-icon${showProgressionLines ? ' show-progression-lines' : ''}`}
|
|
>
|
|
<RibbonIcon
|
|
value={index + 1}
|
|
showNumbers={showNumbers}
|
|
isCompleted={completed}
|
|
isClaimed={claimed}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<ButtonLink
|
|
block
|
|
size='large'
|
|
className='map-superblock-link'
|
|
href={`/learn/${superBlock}/`}
|
|
>
|
|
<div style={linkSpacingStyle}>
|
|
<SuperBlockIcon className='map-icon' superBlock={superBlock} />
|
|
{getSuperBlockTitleForMap(superBlock)}
|
|
</div>
|
|
{landing && <LinkButton />}
|
|
</ButtonLink>
|
|
</li>
|
|
);
|
|
}
|
|
|
|
function Map({
|
|
forLanding = false,
|
|
isSignedIn,
|
|
currentCerts,
|
|
completedChallengeIds,
|
|
allChallenges
|
|
}: MapProps): React.ReactElement {
|
|
const { t } = useTranslation();
|
|
|
|
const allSuperblockChallengesCompleted = (superblock: SuperBlocks) => {
|
|
// array of all challenge ID's in the superblock
|
|
const allSuperblockChallenges = allChallenges
|
|
.filter(challenge => challenge.superBlock === superblock)
|
|
.map(challenge => challenge.id);
|
|
|
|
return allSuperblockChallenges.every(id =>
|
|
completedChallengeIds.includes(id)
|
|
);
|
|
};
|
|
|
|
const isClaimed = (stage: SuperBlocks) => {
|
|
return isSignedIn
|
|
? Boolean(
|
|
currentCerts?.find(
|
|
(cert: { certSlug: string }) =>
|
|
(certSlugTypeMap as { [key: string]: string })[cert.certSlug] ===
|
|
(superBlockCertTypeMap as { [key: string]: string })[stage]
|
|
)?.show
|
|
)
|
|
: false;
|
|
};
|
|
|
|
return (
|
|
<div className='map-ui' data-test-label='curriculum-map'>
|
|
{getStageOrder({
|
|
showUpcomingChanges
|
|
}).map(stage => (
|
|
<Fragment key={stage}>
|
|
<h2 className={forLanding ? 'big-heading' : ''}>
|
|
{t(superBlockHeadings[stage])}
|
|
</h2>
|
|
<ul key={stage}>
|
|
{superBlockStages[stage].map((superblock, i) => (
|
|
<MapLi
|
|
key={superblock}
|
|
superBlock={superblock}
|
|
landing={forLanding}
|
|
index={i}
|
|
claimed={isClaimed(superblock)}
|
|
showProgressionLines={stage === SuperBlockStage.Core}
|
|
showNumbers={stage === SuperBlockStage.Core}
|
|
completed={allSuperblockChallengesCompleted(superblock)}
|
|
/>
|
|
))}
|
|
</ul>
|
|
<Spacer size='m' />
|
|
</Fragment>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
Map.displayName = 'Map';
|
|
|
|
export default connect(mapStateToProps)(Map);
|