feat(client): add "completed x of y" in completion modal (#47022)

* feat: wrote a new function and updated the test case

* fix: lint errors

* fix: Fixed the changes not being reflected in the code. And undid unwanted commits from the last time

* fix: removing unwanted updates to the package-lock.json

* fix: Removing unwanted changes from package-lock.json

* fix: removing unwanted changes from package-lock.json

* fix: updating snapshots to fix the test case

* chore: update snapshot

* Update client/src/templates/Challenges/components/completion-modal.tsx

Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>

* Update client/src/templates/Challenges/components/completion-modal.tsx

Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>

* Update client/src/templates/Challenges/components/completion-modal.tsx

Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>

* fix: removed hard coded english words from translation text

* fix: only show output when id is cert project

* fix: correctly count completed challenges

* fix: update test and snapshot

[skip ci]

* fix: at least one challenge is always completed

When the modal pops-up, there is always at least one challenge counted as "complete"

* refactor: slightly improve readability

Co-authored-by: Ilenia <nethleen@gmail.com>
Co-authored-by: Mrugesh Mohapatra <hi@mrugesh.dev>
Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Jagruti Tiwari
2022-10-27 19:56:45 +05:30
committed by GitHub
parent 65644c420d
commit af156fdfe4
5 changed files with 93 additions and 27 deletions

View File

@@ -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-Ask</0> method, then you can ask for help on the freeCodeCamp forum.",
"rsa": "Read, search, ask",
"rsa-forum": "<strong>Before making a new post</strong> please see if your question has <0>already been answered on the forum</0>.",

View File

@@ -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
};

View File

@@ -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 (
<>
<div className='completion-challenge-details'>
@@ -112,6 +130,14 @@ export class CompletionModalBody extends PureComponent<
</div>
</div>
</div>
{isCertificationProject && (
<output>
{t('learn.project-complete', {
completedChallengesInBlock,
totalChallengesInBlock
})}
</output>
)}
</div>
</>
);

View File

@@ -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('<CompletionModal />', () => {
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

View File

@@ -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<
</Modal.Header>
<Modal.Body className='completion-modal-body'>
<CompletionModalBody
block={block}
completedPercent={completedPercent}
superBlock={superBlock}
{...{
block,
completedPercent,
completedChallengesInBlock,
currentChallengeId: id,
superBlock,
totalChallengesInBlock
}}
/>
</Modal.Body>
<Modal.Footer>