diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json
index 21b5256bb36..b375e0452fe 100644
--- a/client/i18n/locales/english/translations.json
+++ b/client/i18n/locales/english/translations.json
@@ -284,6 +284,7 @@
"sign-in-save": "Sign in to save your progress",
"download-solution": "Download my solution",
"percent-complete": "{{percent}}% complete",
+ "project-complete": "Completed {{completedChallengesInBlock}} of {{totalChallengesInBlock}} certification projects",
"tried-rsa": "If you've already tried the <0>Read-Search-Ask0> method, then you can ask for help on the freeCodeCamp forum.",
"rsa": "Read, search, ask",
"rsa-forum": "Before making a new post please see if your question has <0>already been answered on the forum0>.",
diff --git a/client/src/templates/Challenges/components/completion-modal-body.test.tsx b/client/src/templates/Challenges/components/completion-modal-body.test.tsx
index 04d9a4a40b1..c0aefbb5d89 100644
--- a/client/src/templates/Challenges/components/completion-modal-body.test.tsx
+++ b/client/src/templates/Challenges/components/completion-modal-body.test.tsx
@@ -8,6 +8,9 @@ import CompletionModalBody from './completion-modal-body';
const props = {
block: 'basic-html-and-html5',
completedPercent: Math.floor(Math.random() * 101),
+ completedChallengesInBlock: 2,
+ totalChallengesInBlock: 5,
+ currentChallengeId: '',
superBlock: SuperBlocks.RespWebDesign
};
diff --git a/client/src/templates/Challenges/components/completion-modal-body.tsx b/client/src/templates/Challenges/components/completion-modal-body.tsx
index 1decdf4dbf0..e60dd54cee7 100644
--- a/client/src/templates/Challenges/components/completion-modal-body.tsx
+++ b/client/src/templates/Challenges/components/completion-modal-body.tsx
@@ -2,12 +2,16 @@ import BezierEasing from 'bezier-easing';
import React, { PureComponent } from 'react';
import { TFunction, withTranslation } from 'react-i18next';
import GreenPass from '../../../assets/icons/green-pass';
+import { certMap } from '../../../resources/cert-and-project-map';
interface CompletionModalBodyProps {
block: string;
+ completedChallengesInBlock: number;
completedPercent: number;
+ currentChallengeId: string;
superBlock: string;
t: TFunction;
+ totalChallengesInBlock: number;
}
interface CompletionModalBodyState {
@@ -70,9 +74,23 @@ export class CompletionModalBody extends PureComponent<
}
render(): JSX.Element {
- const { block, completedPercent, superBlock, t } = this.props;
+ const {
+ block,
+ completedPercent,
+ totalChallengesInBlock,
+ completedChallengesInBlock,
+ currentChallengeId,
+ superBlock,
+ t
+ } = this.props;
const blockTitle = t(`intro:${superBlock}.blocks.${block}.title`);
-
+ const isCertificationProject = certMap.some(cert => {
+ // @ts-expect-error If `projects` does not exist, no consequences
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
+ return cert.projects?.some(
+ (project: { id: string }) => project.id === currentChallengeId
+ );
+ });
return (
<>
@@ -112,6 +130,14 @@ export class CompletionModalBody extends PureComponent<
+ {isCertificationProject && (
+
+ )}
>
);
diff --git a/client/src/templates/Challenges/components/completion-modal.test.tsx b/client/src/templates/Challenges/components/completion-modal.test.tsx
index 8e6c76a7607..19aeb5efdcc 100644
--- a/client/src/templates/Challenges/components/completion-modal.test.tsx
+++ b/client/src/templates/Challenges/components/completion-modal.test.tsx
@@ -5,15 +5,10 @@ jest.mock('../../../analytics');
const completedChallengesIds = ['1', '3', '5'],
currentBlockIds = ['1', '3', '5', '7'],
id = '7',
- fakeId = '12345',
fakeCompletedChallengesIds = ['1', '3', '5', '7', '8'];
describe('', () => {
describe('getCompletedPercent', () => {
- it('returns 0 if no challenges have been completed', () => {
- expect(getCompletedPercent([], currentBlockIds, fakeId)).toBe(0);
- });
-
it('returns 25 if one out of four challenges are complete', () => {
expect(getCompletedPercent([], currentBlockIds, currentBlockIds[1])).toBe(
25
diff --git a/client/src/templates/Challenges/components/completion-modal.tsx b/client/src/templates/Challenges/components/completion-modal.tsx
index 6437f034e43..64a13b47403 100644
--- a/client/src/templates/Challenges/components/completion-modal.tsx
+++ b/client/src/templates/Challenges/components/completion-modal.tsx
@@ -77,21 +77,33 @@ export function getCompletedPercent(
currentBlockIds: string[] = [],
currentChallengeId: string
): number {
- completedChallengesIds = completedChallengesIds.includes(currentChallengeId)
- ? completedChallengesIds
- : [...completedChallengesIds, currentChallengeId];
-
- const completedChallengesInBlock = completedChallengesIds.filter(id => {
- return currentBlockIds.includes(id);
- });
-
+ const completedChallengesInBlock = getCompletedChallengesInBlock(
+ completedChallengesIds,
+ currentBlockIds,
+ currentChallengeId
+ );
const completedPercent = Math.round(
- (completedChallengesInBlock.length / currentBlockIds.length) * 100
+ (completedChallengesInBlock / currentBlockIds.length) * 100
);
return completedPercent > 100 ? 100 : completedPercent;
}
+function getCompletedChallengesInBlock(
+ completedChallengesIds: string[],
+ currentBlockChallengeIds: string[],
+ currentChallengeId: string
+) {
+ const oldCompletionCount = completedChallengesIds.filter(challengeId =>
+ currentBlockChallengeIds.includes(challengeId)
+ ).length;
+
+ const isAlreadyCompleted =
+ completedChallengesIds.includes(currentChallengeId);
+
+ return isAlreadyCompleted ? oldCompletionCount : oldCompletionCount + 1;
+}
+
interface CompletionModalsProps {
allowBlockDonationRequests: (arg0: string) => void;
block: string;
@@ -116,6 +128,7 @@ interface CompletionModalsProps {
interface CompletionModalInnerState {
downloadURL: null | string;
completedPercent: number;
+ completedChallengesInBlock: number;
}
export class CompletionModalInner extends Component<
@@ -129,7 +142,8 @@ export class CompletionModalInner extends Component<
this.state = {
downloadURL: null,
- completedPercent: 0
+ completedPercent: 0,
+ completedChallengesInBlock: 0
};
}
@@ -139,7 +153,11 @@ export class CompletionModalInner extends Component<
): CompletionModalInnerState {
const { challengeFiles, isOpen } = props;
if (!isOpen) {
- return { downloadURL: null, completedPercent: 0 };
+ return {
+ downloadURL: null,
+ completedPercent: 0,
+ completedChallengesInBlock: 0
+ };
}
const { downloadURL } = state;
if (downloadURL) {
@@ -168,7 +186,21 @@ export class CompletionModalInner extends Component<
const completedPercent = isSignedIn
? getCompletedPercent(completedChallengesIds, currentBlockIds, id)
: 0;
- return { downloadURL: newURL, completedPercent: completedPercent };
+
+ let completedChallengesInBlock = 0;
+ if (currentBlockIds) {
+ completedChallengesInBlock = getCompletedChallengesInBlock(
+ completedChallengesIds,
+ currentBlockIds,
+ id
+ );
+ }
+
+ return {
+ downloadURL: newURL,
+ completedPercent,
+ completedChallengesInBlock
+ };
}
handleKeypress(e: React.KeyboardEvent): void {
@@ -207,15 +239,19 @@ export class CompletionModalInner extends Component<
const {
block,
close,
+ currentBlockIds,
+ id,
isOpen,
- message,
- t,
- title,
isSignedIn,
- superBlock = ''
+ message,
+ superBlock = '',
+ t,
+ title
} = this.props;
- const { completedPercent } = this.state;
+ const { completedPercent, completedChallengesInBlock } = this.state;
+
+ const totalChallengesInBlock = currentBlockIds?.length ?? 0;
if (isOpen) {
executeGA({ type: 'modal', data: '/completion-modal' });
@@ -243,9 +279,14 @@ export class CompletionModalInner extends Component<