mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-06-22 21:08:12 +08:00
fix(client): compute step number from challenge order (#57209)
This commit is contained in:
parent
d7a6951918
commit
594ee9af58
@ -152,7 +152,7 @@ export interface PrerequisiteChallenge {
|
||||
slug?: string;
|
||||
}
|
||||
|
||||
export type ChallengeWithCompletedNode = {
|
||||
export type ExtendedChallenge = {
|
||||
block: string;
|
||||
challengeType: number;
|
||||
dashedName: string;
|
||||
@ -163,6 +163,7 @@ export type ChallengeWithCompletedNode = {
|
||||
isCompleted: boolean;
|
||||
order: number;
|
||||
superBlock: SuperBlocks;
|
||||
stepNumber: number;
|
||||
title: string;
|
||||
};
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Spacer } from '@freecodecamp/ui';
|
||||
|
||||
import { challengeTypes } from '../../../../../shared/config/challenge-types';
|
||||
import { SuperBlocks } from '../../../../../shared/config/curriculum';
|
||||
import envData from '../../../../config/env.json';
|
||||
import { isAuditedSuperBlock } from '../../../../../shared/utils/is-audited';
|
||||
@ -83,8 +84,9 @@ class Block extends Component<BlockProps> {
|
||||
} = this.props;
|
||||
|
||||
let completedCount = 0;
|
||||
let stepNumber = 0;
|
||||
|
||||
const challengesWithCompleted = challenges.map(challenge => {
|
||||
const extendedChallenges = challenges.map(challenge => {
|
||||
const { id } = challenge;
|
||||
const isCompleted = completedChallengeIds.some(
|
||||
(completedChallengeId: string) => completedChallengeId === id
|
||||
@ -92,7 +94,13 @@ class Block extends Component<BlockProps> {
|
||||
if (isCompleted) {
|
||||
completedCount++;
|
||||
}
|
||||
return { ...challenge, isCompleted };
|
||||
// Dialogues are interwoven with other challenges in the curriculum, but
|
||||
// are not considered to be steps.
|
||||
if (challenge.challengeType !== challengeTypes.dialogue) {
|
||||
stepNumber++;
|
||||
}
|
||||
|
||||
return { ...challenge, isCompleted, stepNumber };
|
||||
});
|
||||
|
||||
const isProjectBlock = challenges.some(challenge => {
|
||||
@ -118,17 +126,17 @@ class Block extends Component<BlockProps> {
|
||||
const expandText = t('intro:misc-text.expand');
|
||||
const collapseText = t('intro:misc-text.collapse');
|
||||
|
||||
const isBlockCompleted = completedCount === challengesWithCompleted.length;
|
||||
const isBlockCompleted = completedCount === extendedChallenges.length;
|
||||
|
||||
const percentageCompleted = Math.floor(
|
||||
(completedCount / challengesWithCompleted.length) * 100
|
||||
(completedCount / extendedChallenges.length) * 100
|
||||
);
|
||||
|
||||
const courseCompletionStatus = () => {
|
||||
if (completedCount === 0) {
|
||||
return t('learn.not-started');
|
||||
}
|
||||
if (completedCount === challengesWithCompleted.length) {
|
||||
if (completedCount === extendedChallenges.length) {
|
||||
return t('learn.completed');
|
||||
}
|
||||
return `${percentageCompleted}% ${t('learn.completed')}`;
|
||||
@ -174,19 +182,19 @@ class Block extends Component<BlockProps> {
|
||||
<span
|
||||
aria-hidden='true'
|
||||
className='map-completed-count'
|
||||
>{`${completedCount}/${challengesWithCompleted.length}`}</span>
|
||||
>{`${completedCount}/${extendedChallenges.length}`}</span>
|
||||
<span className='sr-only'>
|
||||
,{' '}
|
||||
{t('learn.challenges-completed', {
|
||||
completedCount,
|
||||
totalChallenges: challengesWithCompleted.length
|
||||
totalChallenges: extendedChallenges.length
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
{isExpanded && (
|
||||
<Challenges
|
||||
challengesWithCompleted={challengesWithCompleted}
|
||||
challenges={extendedChallenges}
|
||||
isProjectBlock={isProjectBlock}
|
||||
/>
|
||||
)}
|
||||
@ -218,7 +226,7 @@ class Block extends Component<BlockProps> {
|
||||
</div>
|
||||
<BlockIntros intros={blockIntroArr} />
|
||||
<Challenges
|
||||
challengesWithCompleted={challengesWithCompleted}
|
||||
challenges={extendedChallenges}
|
||||
isProjectBlock={isProjectBlock}
|
||||
/>
|
||||
</div>
|
||||
@ -258,7 +266,7 @@ class Block extends Component<BlockProps> {
|
||||
<div id={`${block}-panel`}>
|
||||
<BlockIntros intros={blockIntroArr} />
|
||||
<Challenges
|
||||
challengesWithCompleted={challengesWithCompleted}
|
||||
challenges={extendedChallenges}
|
||||
isProjectBlock={isProjectBlock}
|
||||
isGridMap={true}
|
||||
blockTitle={blockTitle}
|
||||
@ -301,7 +309,7 @@ class Block extends Component<BlockProps> {
|
||||
onClick={() => {
|
||||
this.handleBlockClick();
|
||||
}}
|
||||
to={challengesWithCompleted[0].fields.slug}
|
||||
to={extendedChallenges[0].fields.slug}
|
||||
>
|
||||
<CheckMark isCompleted={isBlockCompleted} />
|
||||
{blockTitle}{' '}
|
||||
@ -351,7 +359,7 @@ class Block extends Component<BlockProps> {
|
||||
{isExpanded && (
|
||||
<div id={`${block}-panel`}>
|
||||
<Challenges
|
||||
challengesWithCompleted={challengesWithCompleted}
|
||||
challenges={extendedChallenges}
|
||||
isProjectBlock={isProjectBlock}
|
||||
/>
|
||||
</div>
|
||||
@ -386,7 +394,7 @@ class Block extends Component<BlockProps> {
|
||||
onClick={() => {
|
||||
this.handleBlockClick();
|
||||
}}
|
||||
to={challengesWithCompleted[0].fields.slug}
|
||||
to={extendedChallenges[0].fields.slug}
|
||||
>
|
||||
<CheckMark isCompleted={isBlockCompleted} />
|
||||
{blockType && <BlockLabel blockType={blockType} />}
|
||||
@ -433,7 +441,7 @@ class Block extends Component<BlockProps> {
|
||||
{isExpanded && (
|
||||
<div id={`${block}-panel`} className='challenge-grid-block-panel'>
|
||||
<Challenges
|
||||
challengesWithCompleted={challengesWithCompleted}
|
||||
challenges={extendedChallenges}
|
||||
isProjectBlock={isProjectBlock}
|
||||
isGridMap={true}
|
||||
blockTitle={blockTitle}
|
||||
|
||||
@ -3,20 +3,14 @@ import { withTranslation, useTranslation } from 'react-i18next';
|
||||
|
||||
import GreenNotCompleted from '../../../assets/icons/green-not-completed';
|
||||
import GreenPass from '../../../assets/icons/green-pass';
|
||||
import { ChallengeWithCompletedNode } from '../../../redux/prop-types';
|
||||
import { ExtendedChallenge } from '../../../redux/prop-types';
|
||||
import { SuperBlocks } from '../../../../../shared/config/curriculum';
|
||||
import { challengeTypes } from '../../../../../shared/config/challenge-types';
|
||||
import { Link } from '../../../components/helpers';
|
||||
import { ButtonLink } from '../../../components/helpers/button-link';
|
||||
|
||||
const getStepNumber = (dashedName: string) => {
|
||||
// dashedName should be in the format 'step-1' or 'task-1'
|
||||
const match = dashedName.match(/-(\d+)/);
|
||||
return match ? match[1] : '';
|
||||
};
|
||||
|
||||
interface Challenges {
|
||||
challengesWithCompleted: ChallengeWithCompletedNode[];
|
||||
challenges: ExtendedChallenge[];
|
||||
isProjectBlock: boolean;
|
||||
isGridMap?: boolean;
|
||||
blockTitle?: string | null;
|
||||
@ -25,11 +19,7 @@ interface Challenges {
|
||||
const CheckMark = ({ isCompleted }: { isCompleted: boolean }) =>
|
||||
isCompleted ? <GreenPass /> : <GreenNotCompleted />;
|
||||
|
||||
const Challenge = ({
|
||||
challenge
|
||||
}: {
|
||||
challenge: ChallengeWithCompletedNode;
|
||||
}) => (
|
||||
const Challenge = ({ challenge }: { challenge: ExtendedChallenge }) => (
|
||||
<Link to={challenge.fields.slug}>
|
||||
<span className='map-badge'>
|
||||
<CheckMark isCompleted={challenge.isCompleted} />
|
||||
@ -38,7 +28,7 @@ const Challenge = ({
|
||||
</Link>
|
||||
);
|
||||
|
||||
const Project = ({ challenge }: { challenge: ChallengeWithCompletedNode }) => (
|
||||
const Project = ({ challenge }: { challenge: ExtendedChallenge }) => (
|
||||
<Link to={challenge.fields.slug}>
|
||||
{challenge.title}
|
||||
<span className='map-badge map-project-checkmark'>
|
||||
@ -48,18 +38,18 @@ const Project = ({ challenge }: { challenge: ChallengeWithCompletedNode }) => (
|
||||
);
|
||||
|
||||
function Challenges({
|
||||
challengesWithCompleted,
|
||||
challenges,
|
||||
isProjectBlock,
|
||||
isGridMap = false,
|
||||
blockTitle
|
||||
}: Challenges): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const firstIncompleteChallenge = challengesWithCompleted.find(
|
||||
const firstIncompleteChallenge = challenges.find(
|
||||
challenge => !challenge.isCompleted
|
||||
);
|
||||
|
||||
const isChallengeStarted = !!challengesWithCompleted.find(
|
||||
const isChallengeStarted = !!challenges.find(
|
||||
challenge => challenge.isCompleted
|
||||
);
|
||||
|
||||
@ -78,14 +68,14 @@ function Challenges({
|
||||
<nav
|
||||
aria-label={
|
||||
blockTitle
|
||||
? challengesWithCompleted[0].superBlock === SuperBlocks.A2English
|
||||
? challenges[0].superBlock === SuperBlocks.A2English
|
||||
? t('aria.dialogues-and-tasks-for', { blockTitle })
|
||||
: t('aria.steps-for', { blockTitle })
|
||||
: t('aria.steps')
|
||||
}
|
||||
>
|
||||
<ul className={`map-challenges-ul map-challenges-grid `}>
|
||||
{challengesWithCompleted.map(challenge => (
|
||||
{challenges.map(challenge => (
|
||||
<li
|
||||
className={`map-challenge-title map-challenge-title-grid ${
|
||||
isProjectBlock
|
||||
@ -111,7 +101,7 @@ function Challenges({
|
||||
? t('aria.task')
|
||||
: t('aria.step')}
|
||||
</span>
|
||||
<span>{getStepNumber(challenge.dashedName)}</span>
|
||||
<span>{challenge.stepNumber}</span>
|
||||
<span className='sr-only'>
|
||||
{challenge.isCompleted
|
||||
? t('icons.passed')
|
||||
@ -130,7 +120,7 @@ function Challenges({
|
||||
</>
|
||||
) : (
|
||||
<ul className={`map-challenges-ul`}>
|
||||
{challengesWithCompleted.map(challenge => (
|
||||
{challenges.map(challenge => (
|
||||
<li
|
||||
className={`map-challenge-title ${
|
||||
isProjectBlock ? 'map-project-wrap' : 'map-challenge-wrap'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user