feat(client): show confetti when project complete (#51072)
Co-authored-by: ahmad abdolsaheb <ahmad.abdolsaheb@gmail.com> Co-authored-by: Naomi Carrigan <nhcarrigan@gmail.com> Co-authored-by: Sboonny <MuhammedElruby@gmail.com>pull/51783/head
parent
58539ce080
commit
4bacab4069
|
@ -69,6 +69,7 @@
|
|||
"bezier-easing": "2.1.0",
|
||||
"browser-cookies": "1.2.0",
|
||||
"buffer": "6.0.3",
|
||||
"canvas-confetti": "^1.6.0",
|
||||
"chai": "4.3.10",
|
||||
"crypto-browserify": "3.12.0",
|
||||
"date-fns": "2.30.0",
|
||||
|
@ -145,6 +146,7 @@
|
|||
"@testing-library/jest-dom": "5.17.0",
|
||||
"@testing-library/react": "12.1.5",
|
||||
"@total-typescript/ts-reset": "^0.5.0",
|
||||
"@types/canvas-confetti": "^1.6.0",
|
||||
"@types/chai": "^4.3.4",
|
||||
"@types/enzyme": "^3.10.12",
|
||||
"@types/enzyme-adapter-react-16": "1.0.7",
|
||||
|
|
|
@ -28,6 +28,8 @@ import ProgressBar from '../../../components/ProgressBar';
|
|||
import GreenPass from '../../../assets/icons/green-pass';
|
||||
|
||||
import './completion-modal.css';
|
||||
import { fireConfetti } from '../../../utils/fire-confetti';
|
||||
import { certsToProjects } from '../../../../config/cert-and-project-map';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
challengeFilesSelector,
|
||||
|
@ -40,7 +42,7 @@ const mapStateToProps = createSelector(
|
|||
isSubmittingSelector,
|
||||
(
|
||||
challengeFiles: ChallengeFiles,
|
||||
{ dashedName }: { dashedName: string },
|
||||
{ dashedName, id }: { dashedName: string; id: string },
|
||||
completedChallengesIds: string[],
|
||||
isOpen: boolean,
|
||||
isSignedIn: boolean,
|
||||
|
@ -49,6 +51,7 @@ const mapStateToProps = createSelector(
|
|||
isSubmitting: boolean
|
||||
) => ({
|
||||
challengeFiles,
|
||||
id,
|
||||
dashedName,
|
||||
completedChallengesIds,
|
||||
isOpen,
|
||||
|
@ -78,6 +81,11 @@ interface CompletionModalState {
|
|||
downloadURL: null | string;
|
||||
}
|
||||
|
||||
const isCertificationProject = (id: string) =>
|
||||
Object.values(certsToProjects).some(cert =>
|
||||
cert.some(project => project.id === id)
|
||||
);
|
||||
|
||||
class CompletionModal extends Component<
|
||||
CompletionModalsProps,
|
||||
CompletionModalState
|
||||
|
@ -149,18 +157,26 @@ class CompletionModal extends Component<
|
|||
const {
|
||||
close,
|
||||
isOpen,
|
||||
id,
|
||||
isSignedIn,
|
||||
isSubmitting,
|
||||
message,
|
||||
t,
|
||||
dashedName,
|
||||
submitChallenge
|
||||
submitChallenge,
|
||||
completedChallengesIds
|
||||
} = this.props;
|
||||
|
||||
if (isOpen) {
|
||||
executeGA({ event: 'pageview', pagePath: '/completion-modal' });
|
||||
if (
|
||||
isCertificationProject(id) &&
|
||||
!completedChallengesIds.includes(id) &&
|
||||
!isSubmitting
|
||||
) {
|
||||
fireConfetti();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
data-cy='completion-modal'
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import confetti from 'canvas-confetti';
|
||||
|
||||
export const fireConfetti = () => {
|
||||
const count = 200;
|
||||
const defaults = {
|
||||
origin: { y: 0.7 },
|
||||
zIndex: 10000
|
||||
};
|
||||
|
||||
function fire(
|
||||
particleRatio: number,
|
||||
opts: {
|
||||
spread?: number;
|
||||
startVelocity?: number;
|
||||
decay?: number;
|
||||
scalar?: number;
|
||||
}
|
||||
) {
|
||||
confetti(
|
||||
Object.assign({}, defaults, opts, {
|
||||
particleCount: Math.floor(count * particleRatio)
|
||||
})
|
||||
)?.catch(err => console.log(err));
|
||||
}
|
||||
|
||||
fire(0.25, {
|
||||
spread: 26,
|
||||
startVelocity: 55
|
||||
});
|
||||
fire(0.2, {
|
||||
spread: 60
|
||||
});
|
||||
fire(0.35, {
|
||||
spread: 100,
|
||||
decay: 0.91,
|
||||
scalar: 0.8
|
||||
});
|
||||
fire(0.1, {
|
||||
spread: 120,
|
||||
startVelocity: 25,
|
||||
decay: 0.92,
|
||||
scalar: 1.2
|
||||
});
|
||||
fire(0.1, {
|
||||
spread: 120,
|
||||
startVelocity: 45
|
||||
});
|
||||
};
|
|
@ -25,4 +25,22 @@ describe('lower jaw', () => {
|
|||
);
|
||||
cy.get('#lowerjaw-quote p').should('not.be.empty');
|
||||
});
|
||||
|
||||
it('Should not show the confetti when step code passes', () => {
|
||||
cy.visit(
|
||||
'/learn/2022/responsive-web-design/learn-html-by-building-a-cat-photo-app/step-2'
|
||||
);
|
||||
|
||||
cy.get('canvas').then(canvases => {
|
||||
const currentCanvasCount = canvases.length;
|
||||
cy.get(`${'.react-monaco-editor-container'} textarea`, { timeout: 16000 })
|
||||
.click()
|
||||
.focused()
|
||||
.type('{downArrow}')
|
||||
.clear()
|
||||
.type('<h2>Cat Photos</h2>');
|
||||
cy.contains('Check Your Code (Ctrl + Enter)').click();
|
||||
cy.get('canvas').should('have.length', currentCanvasCount);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,34 @@ const editorElements = {
|
|||
closeFlash: '.close'
|
||||
};
|
||||
|
||||
const portfolioChallenge = {
|
||||
url: '/learn/2022/responsive-web-design/build-a-personal-portfolio-webpage-project/build-a-personal-portfolio-webpage'
|
||||
};
|
||||
|
||||
const portfolioChallengeSolution = `<head>
|
||||
<style>
|
||||
@media (max-width: 500px){
|
||||
nav{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="navbar">
|
||||
<a href="#projects">text</a> |
|
||||
</nav>
|
||||
<main>
|
||||
<section id="welcome-section">
|
||||
<h1>text</h1>
|
||||
</section><hr>
|
||||
<section id="projects">
|
||||
<h1>Projects</h1>
|
||||
<h2 class="project-tile"><a id="profile-link" target="_blank" href="https://freecodecamp.org">text</a></h2>
|
||||
</section><hr>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
describe('multifileCertProjects', function () {
|
||||
before(() => {
|
||||
cy.task('seed');
|
||||
|
@ -63,4 +91,17 @@ describe('multifileCertProjects', function () {
|
|||
.type(`{ctrl+s}`);
|
||||
cy.contains('Your code was not saved.');
|
||||
});
|
||||
|
||||
it('Should show the confetti when a cert challenge is completed', () => {
|
||||
cy.visit(portfolioChallenge.url);
|
||||
cy.get('[data-cy=editor-container-indexhtml]')
|
||||
.click()
|
||||
.type(portfolioChallengeSolution)
|
||||
.type('{ctrl}{enter}', { release: false, delay: 100 });
|
||||
cy.get('canvas').then(canvases => {
|
||||
const currentCanvasCount = canvases.length;
|
||||
cy.contains('Run the Tests (Ctrl + Enter)').click();
|
||||
cy.get('canvas').should('have.length', currentCanvasCount + 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
5423
pnpm-lock.yaml
5423
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue