diff --git a/client/gatsby-node.js b/client/gatsby-node.js index 1ff2ba48ee6..5b72129beca 100644 --- a/client/gatsby-node.js +++ b/client/gatsby-node.js @@ -85,6 +85,10 @@ exports.createPages = function createPages({ graphql, actions, reporter }) { hasEditableBoundaries id order + prerequisites { + id + title + } required { link src @@ -284,6 +288,7 @@ exports.createSchemaCustomization = ({ actions }) => { notes: String url: String assignments: [String] + prerequisites: [PrerequisiteChallenge] } type FileContents { fileKey: String @@ -294,6 +299,10 @@ exports.createSchemaCustomization = ({ actions }) => { tail: String editableRegionBoundaries: [Int] } + type PrerequisiteChallenge { + id: String + title: String + } `; createTypes(typeDefs); }; diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json index 8f26c287a54..bde5a87379a 100644 --- a/client/i18n/locales/english/intro.json +++ b/client/i18n/locales/english/intro.json @@ -1011,6 +1011,7 @@ "Through hands-on exercises and projects, you will learn the fundamentals of C#, including variables, data types, control structures, and object-oriented programming principles.", "By the end of this course, you will have gained the practical skills and knowledge needed to confidently leverage C# for building robust and scalable applications" ], + "note": "Note: You must complete the trophy challenge in each section below to qualify for the certification exam.", "blocks": { "write-your-first-code-using-c-sharp": { "title": "Write Your First Code Using C#", diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index 3078971abb1..cb7235a58de 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -402,7 +402,9 @@ "season-greetings-fcc": "Season's Greetings from the freeCodeCamp community 🎉", "if-getting-value": "If you're getting a lot out of freeCodeCamp, now is a great time to donate to support our charity's mission.", "building-a-university": "We're Building a Free Computer Science University Degree Program", - "if-help-university": "We've already made a ton of progress. Support our charity with the long road ahead." + "if-help-university": "We've already made a ton of progress. Support our charity with the long road ahead.", + "qualified-for-exam": "Congratulations, you have completed all the requirements to qualify for the exam.", + "not-qualified-for-exam": "You have not met the requirements to be eligible for the exam. To qualify, please complete the following challenges:" }, "donate": { "title": "Support our charity", diff --git a/client/src/redux/prop-types.ts b/client/src/redux/prop-types.ts index 565102b59bb..fc109541aed 100644 --- a/client/src/redux/prop-types.ts +++ b/client/src/redux/prop-types.ts @@ -71,6 +71,11 @@ export interface VideoLocaleIds { portuguese?: string; } +export interface PrerequisiteChallenge { + id: string; + title: string; +} + export type ChallengeWithCompletedNode = { block: string; challengeType: number; @@ -114,6 +119,7 @@ export type ChallengeNode = { type: string; }; notes: string; + prerequisites: PrerequisiteChallenge[]; removeComments: boolean; isLocked: boolean; isPrivate: boolean; diff --git a/client/src/templates/Challenges/exam/show.tsx b/client/src/templates/Challenges/exam/show.tsx index 2e20d13adc9..d062176a338 100644 --- a/client/src/templates/Challenges/exam/show.tsx +++ b/client/src/templates/Challenges/exam/show.tsx @@ -1,5 +1,5 @@ // Package Utilities -import { Grid, Col, Row, Button } from '@freecodecamp/react-bootstrap'; +import { Alert, Grid, Col, Row, Button } from '@freecodecamp/react-bootstrap'; import { graphql } from 'gatsby'; import React, { Component, RefObject } from 'react'; import Helmet from 'react-helmet'; @@ -21,7 +21,6 @@ import Hotkeys from '../components/hotkeys'; import { startExam, stopExam } from '../../../redux/actions'; import { completedChallengesSelector, - partiallyCompletedChallengesSelector, isSignedInSelector, examInProgressSelector } from '../../../redux/selectors'; @@ -51,19 +50,16 @@ const mapStateToProps = createSelector( completedChallengesSelector, isChallengeCompletedSelector, isSignedInSelector, - partiallyCompletedChallengesSelector, examInProgressSelector, ( completedChallenges: CompletedChallenge[], isChallengeCompleted: boolean, isSignedIn: boolean, - partiallyCompletedChallenges: CompletedChallenge[], examInProgress: boolean ) => ({ completedChallenges, isChallengeCompleted, isSignedIn, - partiallyCompletedChallenges, examInProgress }) ); @@ -369,9 +365,9 @@ class ShowExam extends Component { }); this.timerInterval = setInterval(() => { - this.setState({ - examTimeInSeconds: this.state.examTimeInSeconds + 1 - }); + this.setState(state => ({ + examTimeInSeconds: state.examTimeInSeconds + 1 + })); }, 1000); this.setState( @@ -462,6 +458,7 @@ class ShowExam extends Component { description, fields: { blockName }, instructions, + prerequisites, superBlock, title, translationPending @@ -469,6 +466,7 @@ class ShowExam extends Component { } }, examInProgress, + completedChallenges, isChallengeCompleted, openFinishExamModal, pageContext: { @@ -485,6 +483,13 @@ class ShowExam extends Component { showResults } = this.state; + const missingPrequisites = prerequisites.filter( + prerequisite => + !completedChallenges.find(({ id }) => prerequisite.id === id) + ); + + const qualifiedForExam = missingPrequisites.length === 0; + const blockNameTitle = `${t( `intro:${superBlock}.blocks.${block}.title` )}: ${title}`; @@ -613,6 +618,23 @@ class ShowExam extends Component { {title} + + {qualifiedForExam ? ( + +

{t('learn.qualified-for-exam')}

+
+ ) : ( + +

{t('learn.not-qualified-for-exam')}

+ +
    + {missingPrequisites.map(({ title, id }) => ( +
  • {title}
  • + ))} +
+
+ )} + @@ -621,6 +643,7 @@ class ShowExam extends Component { block={true} bsStyle='primary' data-cy='start-exam' + disabled={!qualifiedForExam} onClick={this.runExam} > {t('buttons.click-start-exam')} @@ -648,6 +671,7 @@ export const query = graphql` query ExamChallenge($slug: String!) { challengeNode(challenge: { fields: { slug: { eq: $slug } } }) { challenge { + block challengeType description fields { @@ -656,6 +680,10 @@ export const query = graphql` helpCategory id instructions + prerequisites { + id + title + } superBlock title translationPending diff --git a/curriculum/challenges/english/19-foundational-c-sharp-with-microsoft/foundational-c-sharp-with-microsoft-certification-exam/foundational-c-sharp-with-microsoft-certification-exam.md b/curriculum/challenges/english/19-foundational-c-sharp-with-microsoft/foundational-c-sharp-with-microsoft-certification-exam/foundational-c-sharp-with-microsoft-certification-exam.md index 21ac654ec6d..54119ff1976 100644 --- a/curriculum/challenges/english/19-foundational-c-sharp-with-microsoft/foundational-c-sharp-with-microsoft-certification-exam/foundational-c-sharp-with-microsoft-certification-exam.md +++ b/curriculum/challenges/english/19-foundational-c-sharp-with-microsoft/foundational-c-sharp-with-microsoft-certification-exam/foundational-c-sharp-with-microsoft-certification-exam.md @@ -3,6 +3,19 @@ id: 647e22d18acb466c97ccbef8 title: Foundational C# with Microsoft Certification Exam challengeType: 17 dashedName: foundational-c-sharp-with-microsoft-certification-exam +prerequisites: + - id: 647f85d407d29547b3bee1bb + title: Trophy - Write Your First Code Using C# + - id: 647f87dc07d29547b3bee1bf + title: Trophy - Create and Run Simple C# Console Applications + - id: 647f882207d29547b3bee1c0 + title: Trophy - Add Logic to C# Console Applications + - id: 647f867a07d29547b3bee1bc + title: Trophy - Work with Variable Data in C# Console Applications + - id: 647f877f07d29547b3bee1be + title: Trophy - Create Methods in C# Console Applications + - id: 647f86ff07d29547b3bee1bd + title: Trophy - Debug C# Console Applications --- # --description-- diff --git a/curriculum/schema/challenge-schema.js b/curriculum/schema/challenge-schema.js index 7d3a395c325..2b918533bc5 100644 --- a/curriculum/schema/challenge-schema.js +++ b/curriculum/schema/challenge-schema.js @@ -21,6 +21,11 @@ const fileJoi = Joi.object().keys({ history: Joi.array().items(Joi.string().allow('')) }); +const prerequisitesJoi = Joi.object().keys({ + id: Joi.objectId().required(), + title: Joi.string().required() +}); + const schema = Joi.object() .keys({ block: Joi.string().regex(slugRE).required(), @@ -55,6 +60,10 @@ const schema = Joi.object() isPrivate: Joi.bool(), notes: Joi.string().allow(''), order: Joi.number(), + prerequisites: Joi.when('challengeType', { + is: [challengeTypes.exam], + then: Joi.array().items(prerequisitesJoi) + }), // video challenges only: videoId: Joi.when('challengeType', { is: challengeTypes.video,