mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-04-30 16:01:14 -04:00
feat(client/curriculum): add prerequisites to take exam (#50767)
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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#",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<ShowExamProps, ShowExamState> {
|
||||
});
|
||||
|
||||
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<ShowExamProps, ShowExamState> {
|
||||
description,
|
||||
fields: { blockName },
|
||||
instructions,
|
||||
prerequisites,
|
||||
superBlock,
|
||||
title,
|
||||
translationPending
|
||||
@@ -469,6 +466,7 @@ class ShowExam extends Component<ShowExamProps, ShowExamState> {
|
||||
}
|
||||
},
|
||||
examInProgress,
|
||||
completedChallenges,
|
||||
isChallengeCompleted,
|
||||
openFinishExamModal,
|
||||
pageContext: {
|
||||
@@ -485,6 +483,13 @@ class ShowExam extends Component<ShowExamProps, ShowExamState> {
|
||||
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<ShowExamProps, ShowExamState> {
|
||||
{title}
|
||||
</ChallengeTitle>
|
||||
<Spacer size='medium' />
|
||||
|
||||
{qualifiedForExam ? (
|
||||
<Alert id='qualified-for-exam' bsStyle='info'>
|
||||
<p>{t('learn.qualified-for-exam')}</p>
|
||||
</Alert>
|
||||
) : (
|
||||
<Alert id='not-qualified-for-exam' bsStyle='danger'>
|
||||
<p>{t('learn.not-qualified-for-exam')}</p>
|
||||
<Spacer size='small' />
|
||||
<ul>
|
||||
{missingPrequisites.map(({ title, id }) => (
|
||||
<li key={id}>{title}</li>
|
||||
))}
|
||||
</ul>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<PrismFormatted text={description} />
|
||||
<Spacer size='medium' />
|
||||
<PrismFormatted text={instructions} />
|
||||
@@ -621,6 +643,7 @@ class ShowExam extends Component<ShowExamProps, ShowExamState> {
|
||||
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
|
||||
|
||||
@@ -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--
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user