mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
fix(client): map progress ui (#54576)
Co-authored-by: moT01 <20648924+moT01@users.noreply.github.com>
This commit is contained in:
@@ -4,92 +4,87 @@ interface RibbonProps {
|
||||
value: number;
|
||||
isClaimed: boolean;
|
||||
isCompleted: boolean;
|
||||
showNumbers?: boolean;
|
||||
}
|
||||
|
||||
export const Arrow = () => (
|
||||
<svg width={70} height={40} xmlns='http://www.w3.org/2000/svg'>
|
||||
<line
|
||||
x1={50}
|
||||
y1={0}
|
||||
x2={50}
|
||||
y2={35}
|
||||
stroke='black'
|
||||
strokeWidth={3}
|
||||
strokeDasharray='6,1.3'
|
||||
className='map-arrow-icon'
|
||||
/>
|
||||
<rect
|
||||
x={36}
|
||||
y={35}
|
||||
width={15}
|
||||
height={2}
|
||||
fill='black'
|
||||
transform='rotate(45, 50, 36)'
|
||||
className='map-arrow-icon'
|
||||
/>
|
||||
<rect
|
||||
x={49}
|
||||
y={35}
|
||||
width={15}
|
||||
height={2}
|
||||
fill='black'
|
||||
transform='rotate(-45, 50, 36)'
|
||||
className='map-arrow-icon'
|
||||
/>
|
||||
<svg
|
||||
width='14'
|
||||
height='35'
|
||||
viewBox='0 0 14 35'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path d='M7 0V30' stroke='var(--secondary-color)' strokeWidth='2' />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const RibbonIcon = ({
|
||||
value,
|
||||
isCompleted: completed,
|
||||
showNumbers = false,
|
||||
isClaimed
|
||||
}: RibbonProps): JSX.Element => {
|
||||
const properClassName = completed ? 'completeIcon' : 'incompleteIcon';
|
||||
const fillColor = completed ? 'black' : 'gray';
|
||||
const textColor = completed
|
||||
? 'var(--secondary-background)'
|
||||
: 'var(--secondary-color)';
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='75%'
|
||||
height='75%'
|
||||
width='45'
|
||||
height='51'
|
||||
viewBox='0 0 45 50'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
className={properClassName}
|
||||
aria-hidden='true'
|
||||
>
|
||||
{isClaimed && (
|
||||
<>
|
||||
<path
|
||||
d='M25 35.3418L35.4851 28L44.5957 41.0113L36.2658 39.7151L34.1106 48.353L25 35.3418Z'
|
||||
className='map-icon'
|
||||
fill='var(--secondary-color)'
|
||||
/>
|
||||
<path
|
||||
d='M9.11059 29L19.5957 36.3418L10.4851 49.353L8.85418 41.0821L-4.67677e-07 42.0113L9.11059 29Z'
|
||||
className='map-icon'
|
||||
fill='var(--secondary-color)'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<circle cx={21.9999} cy={21} r={20} fill={fillColor} />
|
||||
<circle
|
||||
cx={22.5}
|
||||
cy={21}
|
||||
r={17.5}
|
||||
fill={fillColor}
|
||||
stroke='white'
|
||||
strokeWidth={2}
|
||||
cx='22'
|
||||
cy='21'
|
||||
r='20'
|
||||
fill='var(--secondary-background)'
|
||||
stroke='var(--secondary-color)'
|
||||
strokeWidth='3'
|
||||
/>
|
||||
<text
|
||||
x='50%'
|
||||
y='50%'
|
||||
fontFamily='Verdana'
|
||||
color={fillColor}
|
||||
fontSize='1.0rem'
|
||||
fill='#fff'
|
||||
textAnchor='middle'
|
||||
alignmentBaseline='central'
|
||||
>
|
||||
{value}
|
||||
</text>
|
||||
{completed && (
|
||||
<circle
|
||||
cx='22'
|
||||
cy='21'
|
||||
r='17.5'
|
||||
fill='var(--secondary-color)'
|
||||
stroke='var(--secondary-background)'
|
||||
strokeWidth='3'
|
||||
/>
|
||||
)}
|
||||
{showNumbers && (
|
||||
<g>
|
||||
<text
|
||||
x='48%'
|
||||
y='55%'
|
||||
fontFamily='lato'
|
||||
color={textColor}
|
||||
fontSize='1.0rem'
|
||||
fill={textColor}
|
||||
textAnchor='middle'
|
||||
>
|
||||
{value}
|
||||
</text>
|
||||
</g>
|
||||
)}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { graphql, useStaticQuery } from 'gatsby';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
@@ -22,17 +23,23 @@ import {
|
||||
|
||||
import { RibbonIcon, Arrow } from '../../assets/icons/completion-ribbon';
|
||||
|
||||
import { CurrentCert, ClaimedCertifications } from '../../redux/prop-types';
|
||||
import {
|
||||
CurrentCert,
|
||||
ClaimedCertifications,
|
||||
AllChallengeNode
|
||||
} 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[];
|
||||
}
|
||||
|
||||
const linkSpacingStyle = {
|
||||
@@ -51,9 +58,11 @@ const coreCurriculum = [
|
||||
const mapStateToProps = createSelector(
|
||||
isSignedInSelector,
|
||||
currentCertsSelector,
|
||||
(isSignedIn: boolean, currentCerts) => ({
|
||||
completedChallengesIdsSelector,
|
||||
(isSignedIn: boolean, currentCerts, completedChallengeIds: string[]) => ({
|
||||
isSignedIn,
|
||||
currentCerts
|
||||
currentCerts,
|
||||
completedChallengeIds
|
||||
})
|
||||
);
|
||||
|
||||
@@ -61,17 +70,19 @@ function MapLi({
|
||||
superBlock,
|
||||
landing = false,
|
||||
last = false,
|
||||
trackProgress,
|
||||
completed,
|
||||
claimed,
|
||||
showArrows = false,
|
||||
showNumbers = false,
|
||||
index
|
||||
}: {
|
||||
superBlock: SuperBlocks;
|
||||
landing: boolean;
|
||||
last?: boolean;
|
||||
trackProgress: boolean;
|
||||
completed: boolean;
|
||||
claimed: boolean;
|
||||
showArrows?: boolean;
|
||||
showNumbers?: boolean;
|
||||
index: number;
|
||||
}) {
|
||||
return (
|
||||
@@ -80,18 +91,17 @@ function MapLi({
|
||||
data-test-label='curriculum-map-button'
|
||||
data-playwright-test-label='curriculum-map-button'
|
||||
>
|
||||
{trackProgress && (
|
||||
<>
|
||||
<div className='progress-icon'>
|
||||
<RibbonIcon
|
||||
value={index + 1}
|
||||
isCompleted={completed}
|
||||
isClaimed={claimed}
|
||||
/>
|
||||
</div>
|
||||
<div className='progression-arrow'>{!last && <Arrow />}</div>
|
||||
</>
|
||||
)}
|
||||
<div className='progress-icon'>
|
||||
<RibbonIcon
|
||||
value={index + 1}
|
||||
showNumbers={showNumbers}
|
||||
isCompleted={completed}
|
||||
isClaimed={claimed}
|
||||
/>
|
||||
</div>
|
||||
<div className='progression-arrow'>
|
||||
{!last && showArrows && <Arrow />}
|
||||
</div>
|
||||
|
||||
<Link className='btn link-btn btn-lg' to={`/learn/${superBlock}/`}>
|
||||
<div style={linkSpacingStyle}>
|
||||
@@ -108,26 +118,41 @@ function MapLi({
|
||||
function Map({
|
||||
forLanding = false,
|
||||
isSignedIn,
|
||||
currentCerts
|
||||
currentCerts,
|
||||
completedChallengeIds
|
||||
}: MapProps): React.ReactElement {
|
||||
const {
|
||||
allChallengeNode: { edges }
|
||||
}: {
|
||||
allChallengeNode: AllChallengeNode;
|
||||
} = useStaticQuery(graphql`
|
||||
query allChallenges {
|
||||
allChallengeNode {
|
||||
edges {
|
||||
node {
|
||||
challenge {
|
||||
id
|
||||
superBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const allChallenges = edges.map(edge => edge.node.challenge);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isTracking = (stage: SuperBlocks) =>
|
||||
![
|
||||
...superBlockOrder[SuperBlockStages.Upcoming],
|
||||
...superBlockOrder[SuperBlockStages.Extra]
|
||||
].includes(stage);
|
||||
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);
|
||||
|
||||
const isCompleted = (stage: SuperBlocks) => {
|
||||
return isSignedIn
|
||||
? Boolean(
|
||||
currentCerts?.find(
|
||||
(cert: { certSlug: string }) =>
|
||||
(certSlugTypeMap as { [key: string]: string })[cert.certSlug] ===
|
||||
(superBlockCertTypeMap as { [key: string]: string })[stage]
|
||||
)
|
||||
)
|
||||
: false;
|
||||
return allSuperblockChallenges.every(id =>
|
||||
completedChallengeIds.includes(id)
|
||||
);
|
||||
};
|
||||
|
||||
const isClaimed = (stage: SuperBlocks) => {
|
||||
@@ -153,10 +178,11 @@ function Map({
|
||||
key={i}
|
||||
superBlock={superBlock}
|
||||
landing={forLanding}
|
||||
trackProgress={isTracking(superBlock)}
|
||||
index={i}
|
||||
claimed={isClaimed(superBlock)}
|
||||
completed={isCompleted(superBlock)}
|
||||
showArrows={true}
|
||||
showNumbers={true}
|
||||
completed={allSuperblockChallengesCompleted(superBlock)}
|
||||
last={i + 1 == coreCurriculum.length}
|
||||
/>
|
||||
))}
|
||||
@@ -171,8 +197,7 @@ function Map({
|
||||
key={i}
|
||||
superBlock={superBlock}
|
||||
landing={forLanding}
|
||||
trackProgress={isTracking(superBlock)}
|
||||
completed={isCompleted(superBlock)}
|
||||
completed={allSuperblockChallengesCompleted(superBlock)}
|
||||
claimed={isClaimed(superBlock)}
|
||||
index={i}
|
||||
last={i + 1 == superBlockOrder[SuperBlockStages.English].length}
|
||||
@@ -189,8 +214,7 @@ function Map({
|
||||
key={i}
|
||||
superBlock={superBlock}
|
||||
landing={forLanding}
|
||||
trackProgress={isTracking(superBlock)}
|
||||
completed={isCompleted(superBlock)}
|
||||
completed={allSuperblockChallengesCompleted(superBlock)}
|
||||
claimed={isClaimed(superBlock)}
|
||||
index={i}
|
||||
last={
|
||||
@@ -209,8 +233,7 @@ function Map({
|
||||
key={i}
|
||||
superBlock={superBlock}
|
||||
landing={forLanding}
|
||||
trackProgress={isTracking(superBlock)}
|
||||
completed={isCompleted(superBlock)}
|
||||
completed={allSuperblockChallengesCompleted(superBlock)}
|
||||
claimed={isClaimed(superBlock)}
|
||||
index={i}
|
||||
last={i + 1 == superBlockOrder[SuperBlockStages.Extra].length}
|
||||
@@ -227,8 +250,7 @@ function Map({
|
||||
key={i}
|
||||
superBlock={superBlock}
|
||||
landing={forLanding}
|
||||
trackProgress={isTracking(superBlock)}
|
||||
completed={isCompleted(superBlock)}
|
||||
completed={allSuperblockChallengesCompleted(superBlock)}
|
||||
claimed={isClaimed(superBlock)}
|
||||
index={i}
|
||||
last={i + 1 == superBlockOrder[SuperBlockStages.Legacy].length}
|
||||
@@ -247,8 +269,7 @@ function Map({
|
||||
key={i}
|
||||
superBlock={superBlock}
|
||||
landing={forLanding}
|
||||
trackProgress={isTracking(superBlock)}
|
||||
completed={isCompleted(superBlock)}
|
||||
completed={allSuperblockChallengesCompleted(superBlock)}
|
||||
index={i}
|
||||
claimed={isClaimed(superBlock)}
|
||||
last={
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
.progression-arrow {
|
||||
position: absolute;
|
||||
bottom: -18px;
|
||||
left: -18px;
|
||||
bottom: -16px;
|
||||
left: 25px;
|
||||
}
|
||||
|
||||
.map-ui ul .progress-icon {
|
||||
|
||||
Reference in New Issue
Block a user