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
a2937 2023-10-03 11:24:06 -04:00 committed by GitHub
parent 58539ce080
commit 4bacab4069
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 1759 additions and 3795 deletions

View File

@ -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",

View File

@ -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'

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff