Skip to content

Commit 898b78c

Browse files
feat(client/curriculum): add generic challenge and first review block (freeCodeCamp#56631)
Co-authored-by: Oliver Eyton-Williams <[email protected]>
1 parent 6636287 commit 898b78c

File tree

10 files changed

+370
-10
lines changed

10 files changed

+370
-10
lines changed

client/i18n/locales/english/intro.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1727,7 +1727,10 @@
17271727
"intro": ["For this lab, you will create a video compilation web page."]
17281728
},
17291729
"bzfv": { "title": "8", "intro": [] },
1730-
"snuv": { "title": "9", "intro": [] },
1730+
"review-basic-html": {
1731+
"title": "Basic HTML Review",
1732+
"intro": ["Review the basic HTML topics."]
1733+
},
17311734
"quiz-basic-html": {
17321735
"title": "Basic HTML Quiz",
17331736
"intro": [
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: Introduction to the Basic HTML Review
3+
block: review-basic-html
4+
superBlock: front-end-development
5+
---
6+
7+
## Introduction to the Basic HTML Review
8+
9+
Review the basic HTML topics.
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
import { graphql } from 'gatsby';
2+
import React, { useEffect, useRef, useState } from 'react';
3+
import Helmet from 'react-helmet';
4+
import { useTranslation } from 'react-i18next';
5+
import { connect } from 'react-redux';
6+
import { Container, Col, Row, Button } from '@freecodecamp/ui';
7+
8+
// Local Utilities
9+
import Spacer from '../../../components/helpers/spacer';
10+
import LearnLayout from '../../../components/layouts/learn';
11+
import { ChallengeNode, ChallengeMeta, Test } from '../../../redux/prop-types';
12+
import ChallengeDescription from '../components/challenge-description';
13+
import Hotkeys from '../components/hotkeys';
14+
import ChallengeTitle from '../components/challenge-title';
15+
import VideoPlayer from '../components/video-player';
16+
import CompletionModal from '../components/completion-modal';
17+
import Assignments from '../components/assignments';
18+
import {
19+
challengeMounted,
20+
updateChallengeMeta,
21+
openModal,
22+
updateSolutionFormValues,
23+
initTests
24+
} from '../redux/actions';
25+
import { isChallengeCompletedSelector } from '../redux/selectors';
26+
import { BlockTypes } from '../../../../../shared/config/blocks';
27+
28+
// Redux Setup
29+
const mapStateToProps = (state: unknown) => ({
30+
isChallengeCompleted: isChallengeCompletedSelector(state) as boolean
31+
});
32+
33+
const mapDispatchToProps = {
34+
initTests,
35+
updateChallengeMeta,
36+
challengeMounted,
37+
updateSolutionFormValues,
38+
openCompletionModal: () => openModal('completion')
39+
};
40+
41+
// Types
42+
interface ShowQuizProps {
43+
challengeMounted: (arg0: string) => void;
44+
data: { challengeNode: ChallengeNode };
45+
description: string;
46+
initTests: (xs: Test[]) => void;
47+
isChallengeCompleted: boolean;
48+
openCompletionModal: () => void;
49+
pageContext: {
50+
challengeMeta: ChallengeMeta;
51+
};
52+
updateChallengeMeta: (arg0: ChallengeMeta) => void;
53+
updateSolutionFormValues: () => void;
54+
}
55+
56+
const ShowGeneric = ({
57+
challengeMounted,
58+
data: {
59+
challengeNode: {
60+
challenge: {
61+
assignments,
62+
bilibiliIds,
63+
block,
64+
blockType,
65+
description,
66+
challengeType,
67+
fields: { tests },
68+
helpCategory,
69+
instructions,
70+
title,
71+
translationPending,
72+
superBlock,
73+
videoId,
74+
videoLocaleIds
75+
}
76+
}
77+
},
78+
pageContext: { challengeMeta },
79+
initTests,
80+
updateChallengeMeta,
81+
openCompletionModal,
82+
isChallengeCompleted
83+
}: ShowQuizProps) => {
84+
const { t } = useTranslation();
85+
const { nextChallengePath, prevChallengePath } = challengeMeta;
86+
const container = useRef<HTMLElement | null>(null);
87+
88+
const blockNameTitle = `${t(
89+
`intro:${superBlock}.blocks.${block}.title`
90+
)} - ${title}`;
91+
92+
useEffect(() => {
93+
initTests(tests);
94+
updateChallengeMeta({
95+
...challengeMeta,
96+
title,
97+
challengeType,
98+
helpCategory
99+
});
100+
challengeMounted(challengeMeta.id);
101+
container.current?.focus();
102+
// This effect should be run once on mount
103+
// eslint-disable-next-line react-hooks/exhaustive-deps
104+
}, []);
105+
106+
useEffect(() => {
107+
updateChallengeMeta({
108+
...challengeMeta,
109+
title,
110+
challengeType,
111+
helpCategory
112+
});
113+
challengeMounted(challengeMeta.id);
114+
}, [
115+
title,
116+
challengeMeta,
117+
challengeType,
118+
helpCategory,
119+
challengeMounted,
120+
updateChallengeMeta
121+
]);
122+
123+
// video
124+
const [videoIsLoaded, setVideoIsLoaded] = useState(false);
125+
126+
const handleVideoIsLoaded = () => {
127+
setVideoIsLoaded(true);
128+
};
129+
130+
// assignments
131+
const [assignmentsCompleted, setAssignmentsCompleted] = useState(0);
132+
const allAssignmentsCompleted = assignmentsCompleted === assignments.length;
133+
134+
const handleAssignmentChange = (
135+
event: React.ChangeEvent<HTMLInputElement>
136+
) => {
137+
const isCompleted = event.target.checked; // extract value before target is nullified
138+
setAssignmentsCompleted(a => (isCompleted ? a + 1 : a - 1));
139+
};
140+
141+
// submit
142+
const handleSubmit = () => {
143+
if (assignments.length == 0 || allAssignmentsCompleted) {
144+
openCompletionModal();
145+
}
146+
};
147+
148+
return (
149+
<Hotkeys
150+
executeChallenge={handleSubmit}
151+
containerRef={container}
152+
nextChallengePath={nextChallengePath}
153+
prevChallengePath={prevChallengePath}
154+
>
155+
<LearnLayout>
156+
<Helmet
157+
title={`${blockNameTitle} | ${t('learn.learn')} | freeCodeCamp.org`}
158+
/>
159+
<Container>
160+
<Row>
161+
<Spacer size='medium' />
162+
<ChallengeTitle
163+
isCompleted={isChallengeCompleted}
164+
translationPending={translationPending}
165+
>
166+
{title}
167+
</ChallengeTitle>
168+
169+
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
170+
{description && (
171+
<ChallengeDescription description={description} />
172+
)}
173+
</Col>
174+
175+
<Col lg={10} lgOffset={1} md={10} mdOffset={1}>
176+
{videoId && (
177+
<VideoPlayer
178+
bilibiliIds={bilibiliIds}
179+
onVideoLoad={handleVideoIsLoaded}
180+
title={title}
181+
videoId={videoId}
182+
videoIsLoaded={videoIsLoaded}
183+
videoLocaleIds={videoLocaleIds}
184+
/>
185+
)}
186+
</Col>
187+
188+
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
189+
{instructions && (
190+
<ChallengeDescription description={instructions} />
191+
)}
192+
193+
<Spacer size='medium' />
194+
195+
{assignments.length > 0 && (
196+
<Assignments
197+
assignments={assignments}
198+
allAssignmentsCompleted={allAssignmentsCompleted}
199+
handleAssignmentChange={handleAssignmentChange}
200+
/>
201+
)}
202+
203+
<Button block={true} variant='primary' onClick={handleSubmit}>
204+
{blockType === BlockTypes.review
205+
? t('buttons.submit')
206+
: t('buttons.check-answer')}
207+
</Button>
208+
209+
<Spacer size='large' />
210+
</Col>
211+
<CompletionModal />
212+
</Row>
213+
</Container>
214+
</LearnLayout>
215+
</Hotkeys>
216+
);
217+
};
218+
219+
ShowGeneric.displayName = 'ShowGeneric';
220+
221+
export default connect(mapStateToProps, mapDispatchToProps)(ShowGeneric);
222+
223+
export const query = graphql`
224+
query GenericChallenge($id: String!) {
225+
challengeNode(id: { eq: $id }) {
226+
challenge {
227+
assignments
228+
bilibiliIds {
229+
aid
230+
bvid
231+
cid
232+
}
233+
block
234+
blockType
235+
challengeType
236+
description
237+
helpCategory
238+
instructions
239+
fields {
240+
blockName
241+
slug
242+
tests {
243+
text
244+
testString
245+
}
246+
}
247+
superBlock
248+
title
249+
translationPending
250+
videoId
251+
videoId
252+
videoLocaleIds {
253+
espanol
254+
italian
255+
portuguese
256+
}
257+
}
258+
}
259+
}
260+
`;

client/utils/gatsby/challenge-page-creator.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ const fillInTheBlank = path.resolve(
6161
'../../src/templates/Challenges/fill-in-the-blank/show.tsx'
6262
);
6363

64+
const generic = path.resolve(
65+
__dirname,
66+
'../../src/templates/Challenges/generic/show.tsx'
67+
);
68+
6469
const views = {
6570
backend,
6671
classic,
@@ -73,8 +78,8 @@ const views = {
7378
exam,
7479
msTrophy,
7580
dialogue,
76-
fillInTheBlank
77-
// quiz: Quiz
81+
fillInTheBlank,
82+
generic
7883
};
7984

8085
function getIsFirstStepInBlock(id, edges) {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "Basic HTML Review",
3+
"isUpcomingChange": true,
4+
"dashedName": "review-basic-html",
5+
"order": 9,
6+
"superBlock": "front-end-development",
7+
"challengeOrder": [{ "id": "67072fc183c7ca6c588feb4d", "title": "Basic HTML Review" }],
8+
"helpCategory": "HTML-CSS",
9+
"blockType": "review"
10+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
id: 67072fc183c7ca6c588feb4d
3+
title: Basic HTML Review
4+
challengeType: 24
5+
dashedName: basic-html-review
6+
---
7+
8+
# --description--
9+
10+
Review the concepts below to prepare for the upcoming quiz.
11+
12+
## HTML Basics
13+
14+
- **Role of HTML**: Foundation of web structure.
15+
- **Block-level vs. inline elements**: Understanding `div` and `span`.
16+
- **Attributes**: Adding metadata and behavior to elements.
17+
18+
## Identifiers and Grouping
19+
20+
- **IDs**: Unique element identifiers.
21+
- **Classes**: Grouping elements for styling and behavior.
22+
23+
## Special Characters and Linking
24+
25+
- **HTML entities**: Using special characters like `&amp;` and `&lt;`.
26+
- **`<link>` element**: Linking to external stylesheets.
27+
- **`<script>` element**: Embedding external JavaScript files.
28+
29+
## Boilerplate and Encoding
30+
31+
- **HTML boilerplate**: Basic structure of a webpage.
32+
- **UTF-8 character encoding**: Ensuring universal character display.
33+
34+
## SEO and Social Sharing
35+
36+
- **Meta tags (`description`)**: How it impacts SEO.
37+
- **Open Graph tags**: Enhancing social media sharing.
38+
39+
## Media Elements and Optimization
40+
41+
- **Replaced elements**: Embedded content (e.g., images, iframes).
42+
- **Optimizing media**: Techniques to improve media performance.
43+
- **Image formats and licenses**: Understanding usage rights and types.
44+
- **SVGs**: Scalable vector graphics for sharp visuals.
45+
46+
## Multimedia Integration
47+
48+
- **HTML audio and video elements**: Embedding multimedia.
49+
- **Embedding with `<iframe>`**: Integrating external video content.
50+
51+
## Paths and Link Behavior
52+
53+
- **Target attribute types**: Controlling link behavior.
54+
- **Absolute vs. relative paths**: Navigating directories.
55+
- **Path syntax**: Understanding `/`, `./`, `../` for file navigation.
56+
- **Link states**: Managing different link interactions (hover, active).
57+
- **Inline vs. block-level links**: Layout and behavior differences.
58+
59+
# --assignment--
60+
61+
Review the Basic HTML topics and concepts.

curriculum/schema/__snapshots__/challenge-schema.test.js.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ const schema = Joi.object()
143143
}),
144144
challengeOrder: Joi.number(),
145145
certification: Joi.string().regex(slugWithSlashRE),
146-
challengeType: Joi.number().min(0).max(23).required(),
146+
challengeType: Joi.number().min(0).max(24).required(),
147147
checksum: Joi.number(),
148148
// TODO: require this only for normal challenges, not certs
149149
dashedName: Joi.string().regex(slugRE),

curriculum/schema/challenge-schema.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ const schema = Joi.object()
140140
}),
141141
challengeOrder: Joi.number(),
142142
certification: Joi.string().regex(slugWithSlashRE),
143-
challengeType: Joi.number().min(0).max(23).required(),
143+
challengeType: Joi.number().min(0).max(24).required(),
144144
checksum: Joi.number(),
145145
// TODO: require this only for normal challenges, not certs
146146
dashedName: Joi.string().regex(slugRE),

0 commit comments

Comments
 (0)