diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json
index 373a226d333..2c3dbb6bbc8 100644
--- a/client/i18n/locales/english/intro.json
+++ b/client/i18n/locales/english/intro.json
@@ -2022,7 +2022,10 @@
"Open up this page to review concepts around the basics of HTML elements, semantic HTML, tables, forms and accessibility."
]
},
- "qpra": { "title": "30", "intro": [] },
+ "qpra": {
+ "title": "30",
+ "intro": []
+ },
"lecture-understanding-computer-internet-and-tooling-basics": {
"title": "Understanding Computer, Internet, and Tooling Basics",
"intro": [
@@ -3426,7 +3429,10 @@
"Open up this page to review all of the concepts taught including variables, strings, booleans, functions, objects, arrays, debugging, working with the DOM and more."
]
},
- "kagw": { "title": "258", "intro": [] },
+ "kagw": {
+ "title": "258",
+ "intro": []
+ },
"lecture-introduction-to-javascript-libraries-and-frameworks": {
"title": "Introduction to JavaScript Libraries and Frameworks",
"intro": [
@@ -3479,16 +3485,28 @@
"In these lecture videos, you will learn about working with state and responding to events with React."
]
},
- "rmpy": { "title": "268", "intro": [] },
- "dbta": { "title": "269", "intro": [] },
+ "rmpy": {
+ "title": "268",
+ "intro": []
+ },
+ "dbta": {
+ "title": "269",
+ "intro": []
+ },
"lecture-understanding-effects-and-referencing-values-in-react": {
"title": "Understanding Effects and Referencing Values in React",
"intro": [
"In these lecture videos, you will learn about effects and referencing values with React."
]
},
- "xdyh": { "title": "270", "intro": [] },
- "vjgg": { "title": "272", "intro": [] },
+ "xdyh": {
+ "title": "270",
+ "intro": []
+ },
+ "vjgg": {
+ "title": "272",
+ "intro": []
+ },
"review-react-state-and-hooks": {
"title": "React State and Hooks Review",
"intro": [
@@ -3508,12 +3526,15 @@
"In these lecture videos, you will learn about working with forms in React."
]
},
- "sgau": { "title": "276", "intro": [] },
- "lab-currency-converter": {
- "title": "Build a Currency Converter",
+ "sgau": {
+ "title": "276",
+ "intro": []
+ },
+ "lab-event-rsvp": {
+ "title": "Build an Event RSVP",
"intro": [
- "For this lab, you'll build a currency converter app.",
- "You'll use React state, memoization, and controlled components to convert between currencies."
+ "In this lab, you'll build an Event RSVP form using React.",
+ "You'll practice using the useState hook to manage form input and display user responses."
]
},
"lecture-working-with-data-fetching-and-memoization-in-react": {
@@ -3522,15 +3543,27 @@
"In these lecture videos, you will learn about data fetching and memoization in React."
]
},
- "ffpt": { "title": "279", "intro": [] },
- "lrof": { "title": "280", "intro": [] },
+ "ffpt": {
+ "title": "279",
+ "intro": []
+ },
+ "lab-currency-converter": {
+ "title": "Build a Currency Converter",
+ "intro": [
+ "For this lab, you'll build a currency converter app.",
+ "You'll use React state, memoization, and controlled components to convert between currencies."
+ ]
+ },
"lecture-routing-react-frameworks-and-dependency-management-tools": {
"title": "Routing, React Frameworks, and Dependency Management Tools",
"intro": [
"In these lecture videos, you will learn about routing in React, React frameworks, and dependency management tools."
]
},
- "vyzp": { "title": "281", "intro": [] },
+ "vyzp": {
+ "title": "281",
+ "intro": []
+ },
"lecture-react-strategies-and-debugging": {
"title": "React Strategies and Debugging",
"intro": [
@@ -3611,11 +3644,26 @@
"In these lecture videos, you will learn what TypeScript is and how to use it."
]
},
- "trvf": { "title": "293", "intro": [] },
- "kwmg": { "title": "294", "intro": [] },
- "nodx": { "title": "295", "intro": [] },
- "erfj": { "title": "296", "intro": [] },
- "muyw": { "title": "297", "intro": [] },
+ "trvf": {
+ "title": "293",
+ "intro": []
+ },
+ "kwmg": {
+ "title": "294",
+ "intro": []
+ },
+ "nodx": {
+ "title": "295",
+ "intro": []
+ },
+ "erfj": {
+ "title": "296",
+ "intro": []
+ },
+ "muyw": {
+ "title": "297",
+ "intro": []
+ },
"review-typescript": {
"title": "Typescript Review",
"intro": [
@@ -3633,8 +3681,14 @@
"Review the Front End Libraries concepts to prepare for the upcoming quiz."
]
},
- "rdzk": { "title": "301", "intro": [] },
- "vtpz": { "title": "302", "intro": [] },
+ "rdzk": {
+ "title": "301",
+ "intro": []
+ },
+ "vtpz": {
+ "title": "302",
+ "intro": []
+ },
"workshop-bash-boilerplate": {
"title": "Build a Boilerplate",
"intro": [
@@ -3652,7 +3706,10 @@
"title": "Bash Commands Quiz",
"intro": ["Test what you've learned bash commands with this quiz."]
},
- "voks": { "title": "306", "intro": [] },
+ "voks": {
+ "title": "306",
+ "intro": []
+ },
"workshop-database-of-video-game-characters": {
"title": "Build a Database of Video Game Characters",
"intro": [
@@ -3678,7 +3735,10 @@
"Test what you've learned on relational databases with this quiz."
]
},
- "pexz": { "title": "311", "intro": [] },
+ "pexz": {
+ "title": "311",
+ "intro": []
+ },
"workshop-bash-five-programs": {
"title": "Build Five Programs",
"intro": [
@@ -3696,7 +3756,10 @@
"title": "Bash Scripting Quiz",
"intro": ["Test what you've learned on bash scripting in this quiz."]
},
- "tkgg": { "title": "315", "intro": [] },
+ "tkgg": {
+ "title": "315",
+ "intro": []
+ },
"workshop-sql-student-database-part-1": {
"title": "Build a Student Database: Part 1",
"intro": [
@@ -3746,7 +3809,10 @@
"title": "Bash and SQL Quiz",
"intro": ["Test what you've learned in this quiz on Bash and SQL."]
},
- "eeez": { "title": "324", "intro": [] },
+ "eeez": {
+ "title": "324",
+ "intro": []
+ },
"workshop-castle": {
"title": "Build a Castle",
"intro": [
@@ -3762,7 +3828,10 @@
"title": "Nano Quiz",
"intro": ["Test what you've learned on Nano with this quiz ."]
},
- "rhhl": { "title": "328", "intro": [] },
+ "rhhl": {
+ "title": "328",
+ "intro": []
+ },
"workshop-sql-reference-object": {
"title": "Build an SQL Reference Object",
"intro": [
@@ -3904,7 +3973,10 @@
"title": "Placeholder - waiting for title",
"intro": [""]
},
- "lab-budget-app": { "title": "Build a Budget App", "intro": [""] },
+ "lab-budget-app": {
+ "title": "Build a Budget App",
+ "intro": [""]
+ },
"review-classes-and-objects": {
"title": "Classes and Objects Review",
"intro": [
@@ -3950,7 +4022,10 @@
"title": "Placeholder - Waiting for title",
"intro": [""]
},
- "lab-placeholder-oop-3": { "title": "", "intro": [""] },
+ "lab-placeholder-oop-3": {
+ "title": "",
+ "intro": [""]
+ },
"review-object-oriented-programming": {
"title": "Object Oriented Programming Review",
"intro": [
@@ -4001,8 +4076,14 @@
"title": "Build a Bisection Method",
"intro": [""]
},
- "workshop-merge-sort": { "title": "Build a Merge Sort", "intro": [""] },
- "lab-quick-sort": { "title": "Build a Quick Sort", "intro": [""] },
+ "workshop-merge-sort": {
+ "title": "Build a Merge Sort",
+ "intro": [""]
+ },
+ "lab-quick-sort": {
+ "title": "Build a Quick Sort",
+ "intro": [""]
+ },
"lab-selection-sort": {
"title": "Build a Selection Sort",
"intro": [""]
diff --git a/client/src/pages/learn/full-stack-developer/lab-event-rsvp/index.md b/client/src/pages/learn/full-stack-developer/lab-event-rsvp/index.md
new file mode 100644
index 00000000000..a495c920a0a
--- /dev/null
+++ b/client/src/pages/learn/full-stack-developer/lab-event-rsvp/index.md
@@ -0,0 +1,9 @@
+---
+title: Introduction to the Build an Event RSVP
+block: lab-event-rsvp
+superBlock: full-stack-developer
+---
+
+## Introduction to the Build an Event RSVP
+
+For this lab you will use React.useState to manage a form in a React component.
diff --git a/curriculum/challenges/_meta/lab-event-rsvp/meta.json b/curriculum/challenges/_meta/lab-event-rsvp/meta.json
new file mode 100644
index 00000000000..90c7dc4fcfd
--- /dev/null
+++ b/curriculum/challenges/_meta/lab-event-rsvp/meta.json
@@ -0,0 +1,16 @@
+{
+ "name": "Build an Event RSVP",
+ "usesMultifileEditor": true,
+ "dashedName": "lab-event-rsvp",
+ "superBlock": "full-stack-developer",
+ "challengeOrder": [
+ {
+ "id": "67d936de7055982b02baf186",
+ "title": "Build an Event RSVP"
+ }
+ ],
+ "helpCategory": "JavaScript",
+ "isUpcomingChange": false,
+ "blockLayout": "link",
+ "blockType": "lab"
+}
\ No newline at end of file
diff --git a/curriculum/challenges/english/25-front-end-development/lab-event-rsvp/67d936de7055982b02baf186.md b/curriculum/challenges/english/25-front-end-development/lab-event-rsvp/67d936de7055982b02baf186.md
new file mode 100644
index 00000000000..bae8f6c0c39
--- /dev/null
+++ b/curriculum/challenges/english/25-front-end-development/lab-event-rsvp/67d936de7055982b02baf186.md
@@ -0,0 +1,917 @@
+---
+id: 67d936de7055982b02baf186
+title: Build an Event RSVP
+challengeType: 25
+dashedName: build-an-event-rsvp
+demoType: onClick
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should create a form with fields for name, email, number of attendees, dietary preferences, and an option to indicate if you are bringing additional guests.
+
+2. You should have a text input field where you would enter your name (mandatory).
+
+3. You should have an email input field where you would enter your email address (mandatory). The form should validate the format to ensure it is a proper email.
+
+4. You should have a number input field where you would enter the number of attendees in the form (mandatory), and the number should not be less than one.
+
+5. You should have a text input field where you would enter your dietary preferences, and this information should be optional.
+
+6. You should be able to check or uncheck a checkbox to indicate whether you are bringing additional guests.
+
+7. You should have a button which submits the form, and the form should not cause the page to reload upon submission.
+
+8. You should see a confirmation message displayed below the form after submitting, followed by the details provided (name, email, number of attendees, dietary preferences, and optional additional guests).
+
+Here is an example message after submitting the form:
+
+```markdown
+RSVP Submitted!
+Name: John Doe
+Email: example@example.com
+Number of attendees: 2
+Dietary preferences: None
+Bringing additional guests: Yes
+```
+
+# --hints--
+
+You should have one `form` element to hold all your form content.
+
+```js
+const forms = document.querySelectorAll('form');
+assert.equal(forms.length, 1);
+```
+
+You should have two `input` elements of type `text`.
+
+```js
+const inputs = document.querySelectorAll('form input[type="text"]');
+assert.equal(inputs.length, 2);
+```
+
+You should have one `input` element of type `email`.
+
+```js
+try {
+ const inputs = document.querySelectorAll('form input[type="email"]');
+ assert.equal(inputs.length, 1);
+} catch (e) {
+ console.log(e);
+ throw e;
+}
+```
+
+You should have one `input` element of type `number`.
+
+```js
+try {
+ const inputs = document.querySelectorAll('form input[type="number"]');
+ assert.equal(inputs.length, 1);
+} catch (e) {
+ console.log(e);
+ throw e;
+}
+```
+
+You should have one `input` element of type `checkbox`.
+
+```js
+try {
+ const inputs = document.querySelectorAll('form input[type="checkbox"]');
+ assert.equal(inputs.length, 1);
+} catch (e) {
+ console.log(e);
+ throw e;
+}
+```
+
+One `input[type="text"]` element should be required.
+
+```js
+try {
+ const input = document.querySelectorAll('form input[type="text"][required]');
+ assert.equal(input.length, 1);
+} catch (e) {
+ console.log(e);
+ throw e;
+}
+```
+
+The `input[type="email"]` element should be required.
+
+```js
+try {
+ const input = document.querySelector('form input[type="email"][required]');
+ assert.exists(input);
+} catch (e) {
+ console.log(e);
+ throw e;
+}
+```
+
+The `input[type="number"]` element should be required.
+
+```js
+try {
+ const input = document.querySelector('form input[type="number"][required]');
+ assert.exists(input);
+} catch (e) {
+ console.log(e);
+ throw e;
+}
+```
+
+Changing the value of the `input[type="text"]` elements should update component state.
+
+```js
+async () => {
+ try {
+ const abuseState = __helpers.spyOn(React, 'useState');
+ const script = [...document.querySelectorAll('script')]
+ .find(s => s.dataset.src === 'index.jsx')
+ .innerText.replace('_React.useState', 'abuseState');
+
+ const exports = {};
+ const a = eval(script);
+
+ const s = await __helpers.prepTestComponent(exports.EventRSVPForm);
+ const inp = s.querySelector('input[type="text"]');
+
+ await React.act(async () => {
+ inp.value = 'John Doe';
+ const ev = new InputEvent('change', { bubbles: true, cancelable: false });
+ inp[Object.keys(inp).find(k => k.startsWith('__reactProps'))].onChange({
+ ...ev,
+ target: inp
+ });
+ });
+
+ // For all state in `abuseState.returns`, there will be a multiple of two calls - one before the change, and one after.
+ const alteredStates = abuseState.returns;
+ const initialStates = alteredStates.splice(
+ 0,
+ abuseState.returns.length / 2
+ );
+
+ const stateChanged = initialStates.some((s, i) => {
+ try {
+ assert.deepEqual(s[0], alteredStates[i][0]);
+ return false;
+ } catch (e) {
+ return true;
+ }
+ });
+
+ abuseState.restore();
+ assert.isTrue(stateChanged);
+ } catch (e) {
+ console.log(e);
+ throw e;
+ }
+};
+```
+
+Changing the value of the `input[type="email"]` elements should update component state.
+
+```js
+async () => {
+ try {
+ const abuseState = __helpers.spyOn(React, 'useState');
+ const script = [...document.querySelectorAll('script')]
+ .find(s => s.dataset.src === 'index.jsx')
+ .innerText.replace('_React.useState', 'abuseState');
+
+ const exports = {};
+ const a = eval(script);
+
+ const s = await __helpers.prepTestComponent(exports.EventRSVPForm);
+ const inp = s.querySelector('input[type="email"]');
+
+ await React.act(async () => {
+ inp.value = 'fcc@freecodecamp.org';
+ const ev = new InputEvent('change', { bubbles: true, cancelable: false });
+ inp[Object.keys(inp).find(k => k.startsWith('__reactProps'))].onChange({
+ ...ev,
+ target: inp
+ });
+ });
+
+ // For all state in `abuseState.returns`, there will be a multiple of two calls - one before the change, and one after.
+ const alteredStates = abuseState.returns;
+ const initialStates = alteredStates.splice(
+ 0,
+ abuseState.returns.length / 2
+ );
+
+ const stateChanged = initialStates.some((s, i) => {
+ try {
+ assert.deepEqual(s[0], alteredStates[i][0]);
+ return false;
+ } catch (e) {
+ return true;
+ }
+ });
+
+ abuseState.restore();
+ assert.isTrue(stateChanged);
+ } catch (e) {
+ console.log(e);
+ throw e;
+ }
+};
+```
+
+Changing the value of the `input[type="number"]` elements should update component state.
+
+```js
+async () => {
+ try {
+ const abuseState = __helpers.spyOn(React, 'useState');
+ const script = [...document.querySelectorAll('script')]
+ .find(s => s.dataset.src === 'index.jsx')
+ .innerText.replace('_React.useState', 'abuseState');
+
+ const exports = {};
+ const a = eval(script);
+
+ const s = await __helpers.prepTestComponent(exports.EventRSVPForm);
+ const inp = s.querySelector('input[type="number"]');
+
+ await React.act(async () => {
+ inp.value = 2;
+ const ev = new InputEvent('change', { bubbles: true, cancelable: false });
+ inp[Object.keys(inp).find(k => k.startsWith('__reactProps'))].onChange({
+ ...ev,
+ target: inp
+ });
+ });
+
+ // For all state in `abuseState.returns`, there will be a multiple of two calls - one before the change, and one after.
+ const alteredStates = abuseState.returns;
+ const initialStates = alteredStates.splice(
+ 0,
+ abuseState.returns.length / 2
+ );
+
+ const stateChanged = initialStates.some((s, i) => {
+ try {
+ assert.deepEqual(s[0], alteredStates[i][0]);
+ return false;
+ } catch (e) {
+ return true;
+ }
+ });
+
+ abuseState.restore();
+ assert.isTrue(stateChanged);
+ } catch (e) {
+ console.log(e);
+ throw e;
+ }
+};
+```
+
+Changing the value of the `input[type="checkbox"]` elements should update component state.
+
+```js
+async () => {
+ try {
+ const abuseState = __helpers.spyOn(React, 'useState');
+ const script = [...document.querySelectorAll('script')]
+ .find(s => s.dataset.src === 'index.jsx')
+ .innerText.replace('_React.useState', 'abuseState');
+
+ const exports = {};
+ const a = eval(script);
+
+ const s = await __helpers.prepTestComponent(exports.EventRSVPForm);
+ const inp = s.querySelector('input[type="checkbox"]');
+
+ await React.act(async () => {
+ inp.checked = true;
+ const ev = new InputEvent('change', { bubbles: true, cancelable: false });
+ inp[Object.keys(inp).find(k => k.startsWith('__reactProps'))].onChange({
+ ...ev,
+ target: inp
+ });
+ });
+
+ // For all state in `abuseState.returns`, there will be a multiple of two calls - one before the change, and one after.
+ const alteredStates = abuseState.returns;
+ const initialStates = alteredStates.splice(
+ 0,
+ abuseState.returns.length / 2
+ );
+
+ const stateChanged = initialStates.some((s, i) => {
+ try {
+ assert.deepEqual(s[0], alteredStates[i][0]);
+ return false;
+ } catch (e) {
+ return true;
+ }
+ });
+
+ abuseState.restore();
+ assert.isTrue(stateChanged);
+ } catch (e) {
+ console.log(e);
+ throw e;
+ }
+};
+```
+
+Submitting the form should not result in the page reloading.
+
+```js
+async () => {
+ try {
+ // Ideally, `window.onload` would be watched. However, the frame runner disables submissions causing window reloads.
+ // So, the test needs to manually check for the `.preventDefault` call.
+ const script = [...document.querySelectorAll('script')].find(
+ s => s.dataset.src === 'index.jsx'
+ ).innerText;
+
+ const exports = {};
+ const a = eval(script);
+
+ const s = await __helpers.prepTestComponent(exports.EventRSVPForm);
+ const f = s.querySelector('form');
+
+ await React.act(async () => {
+ let c = 0;
+ const mockEv = {
+ ...new SubmitEvent('submit'),
+ preventDefault: () => {
+ c++;
+ }
+ };
+ f[Object.keys(f).find(k => k.startsWith('__reactProps'))].onSubmit(
+ mockEv
+ );
+ assert.equal(c, 1);
+ });
+ } catch (e) {
+ console.log(e);
+ throw e;
+ }
+};
+```
+
+After submission, there should be an element on the page indicating the state of the name `input`.
+
+```js
+async () => {
+ try {
+ const inp = document.querySelector(`input[type="text"]`);
+ assert.exists(inp);
+ const setter = Object.getOwnPropertyDescriptor(
+ window.HTMLInputElement.prototype,
+ 'value'
+ ).set;
+ await React.act(async () => {
+ // In order for form to be submited, all required fields are give values.
+ await adjustAllRequired();
+ setter.call(inp, 'John Doe');
+ inp.dispatchEvent(new Event('input', { bubbles: true }));
+
+ submitForm();
+ });
+
+ const nonInputText = getInnerTextExcept('input,script');
+ assert.include(nonInputText, 'John Doe');
+ } catch (e) {
+ console.log(e);
+ throw e;
+ }
+};
+```
+
+After submission, there should be an element on the page indicating the state of the email `input`.
+
+```js
+async () => {
+ try {
+ const inp = document.querySelector(`input[type="email"]`);
+ assert.exists(inp);
+ const setter = Object.getOwnPropertyDescriptor(
+ window.HTMLInputElement.prototype,
+ 'value'
+ ).set;
+ await React.act(async () => {
+ // In order for form to be submited, all required fields are give values.
+ await adjustAllRequired();
+ setter.call(inp, 'fcc@freecodecamp.org');
+ inp.dispatchEvent(new Event('input', { bubbles: true }));
+
+ submitForm();
+ });
+
+ const nonInputText = getInnerTextExcept('input,script');
+ assert.include(nonInputText, 'fcc@freecodecamp.org');
+ } catch (e) {
+ console.log(e);
+ throw e;
+ }
+};
+```
+
+After submission, there should be an element on the page indicating the state of the number of attendees `input`.
+
+```js
+async () => {
+ try {
+ const inp = document.querySelector(`input[type="number"]`);
+ assert.exists(inp);
+ const setter = Object.getOwnPropertyDescriptor(
+ window.HTMLInputElement.prototype,
+ 'value'
+ ).set;
+ await React.act(async () => {
+ // In order for form to be submited, all required fields are give values.
+ await adjustAllRequired();
+ setter.call(inp, 2);
+ inp.dispatchEvent(new Event('input', { bubbles: true }));
+
+ submitForm();
+ });
+
+ const nonInputText = getInnerTextExcept('input,script');
+ assert.include(nonInputText, '2');
+ } catch (e) {
+ console.log(e);
+ throw e;
+ }
+};
+```
+
+After submission, there should be an element on the page indicating the state of the dietary preferences `input`.
+
+```js
+async () => {
+ try {
+ const inp = document.querySelector(`input[type="text"]:not(:required)`);
+ assert.exists(inp);
+ const setter = Object.getOwnPropertyDescriptor(
+ window.HTMLInputElement.prototype,
+ 'value'
+ ).set;
+ await React.act(async () => {
+ // In order for form to be submited, all required fields are give values.
+ await adjustAllRequired();
+ setter.call(inp, 'diet');
+ inp.dispatchEvent(new Event('input', { bubbles: true }));
+
+ submitForm();
+ });
+
+ const nonInputText = getInnerTextExcept('input,script');
+ assert.include(nonInputText, 'diet');
+ } catch (e) {
+ console.log(e);
+ throw e;
+ }
+};
+```
+
+After submission, there should be an element on the page indicating the state of the additional guests `input`.
+
+```js
+async () => {
+ try {
+ let inp = document.querySelector(`input[type="checkbox"]`);
+ assert.exists(inp);
+ await React.act(async () => {
+ // In order for form to be submited, all required fields are give values.
+ await adjustAllRequired();
+ const setter = Object.getOwnPropertyDescriptor(
+ window.HTMLInputElement.prototype,
+ 'checked'
+ ).set;
+ setter.call(inp, false);
+ inp.dispatchEvent(new Event('click', { bubbles: true }));
+
+ submitForm();
+ });
+
+ // Test submission without checked input, then test submission with checked input - compare for difference.
+ const stateWithFalse = getInnerTextExcept('input,script');
+
+ inp = document.querySelector(`input[type="checkbox"]`);
+ await React.act(async () => {
+ const setter = Object.getOwnPropertyDescriptor(
+ window.HTMLInputElement.prototype,
+ 'checked'
+ ).set;
+ setter.call(inp, true);
+ inp.dispatchEvent(new Event('click', { bubbles: true }));
+
+ submitForm();
+ });
+
+ const stateWithTrue = getInnerTextExcept('input,script');
+
+ assert.notEqual(stateWithFalse, stateWithTrue);
+ } catch (e) {
+ console.log(e);
+ throw e;
+ }
+};
+```
+
+# --before-all--
+
+```js
+// Submit button can either be the only button within a form, or a `button[type="submit"]`.
+function submitForm() {
+ const maybeSubmit = document.querySelectorAll("form button");
+ if (maybeSubmit.length === 1) {
+ return maybeSubmit[0].click();
+ }
+
+ const submitBtn = document.querySelector("button[type='submit']");
+ if (submitBtn) {
+ return submitBtn.click();
+ }
+}
+
+async function adjustAllRequired() {
+ const inps = document.querySelectorAll(
+ 'input[required], select[required], textarea[required]'
+ );
+ inps.forEach(inp => {
+ let setter = Object.getOwnPropertyDescriptor(
+ window.HTMLInputElement.prototype,
+ 'value'
+ ).set;
+ switch (inp.type) {
+ case 'text':
+ setter.call(inp, 'required text');
+ inp.dispatchEvent(new Event('change', { bubbles: true }));
+ break;
+ case 'email':
+ setter.call(inp, 'required-email@freecodecamp.org');
+ inp.dispatchEvent(new Event('change', { bubbles: true }));
+ break;
+ case 'number':
+ setter.call(inp, 1);
+ inp.dispatchEvent(new Event('change', { bubbles: true }));
+ break;
+ case 'checkbox':
+ setter = Object.getOwnPropertyDescriptor(
+ window.HTMLInputElement.prototype,
+ 'checked'
+ ).set;
+ setter.call(inp, true);
+ inp.dispatchEvent(new Event('click', { bubbles: true }));
+ break;
+ }
+ inp.dispatchEvent(new Event('change', { bubbles: true }));
+ });
+}
+
+function getInnerTextExcept(removingSelector) {
+ const body = document.body.cloneNode(true);
+
+ const squareElements = body.querySelectorAll(removingSelector);
+ squareElements.forEach(element => {
+ element.parentNode.removeChild(element);
+ });
+
+ return body.innerText;
+}
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+ Event RSVP
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+```jsx
+const { useState } = React;
+
+export function EventRSVPForm() {}
+```
+
+# --solutions--
+
+```html
+
+
+
+
+ Event RSVP
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```css
+body {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ margin: 0;
+ font-family: 'Helvetica Neue', Arial, sans-serif;
+ background-color: #e0e0e0;
+}
+
+.form-container {
+ background-color: #ffffff;
+ padding: 40px;
+ border-radius: 12px;
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
+ width: 100%;
+ max-width: 450px;
+ text-align: center;
+}
+
+h2 {
+ margin-bottom: 20px;
+ font-size: 28px;
+ color: #333;
+ font-weight: bold;
+}
+
+form {
+ display: flex;
+ flex-direction: column;
+}
+
+.form-group {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 20px;
+}
+
+.form-group label {
+ flex: 1;
+ text-align: left;
+ margin-right: 15px;
+ font-size: 15px;
+ color: #555;
+}
+
+.form-group input[type='text'],
+.form-group input[type='email'],
+.form-group input[type='number'] {
+ flex: 2;
+ padding: 10px 12px;
+ border: 1px solid #ccc;
+ border-radius: 8px;
+ width: 100%;
+ font-size: 15px;
+ transition: border-color 0.3s ease;
+}
+
+.form-group input:focus {
+ border-color: #4caf4f4f;
+}
+
+.form-group input[type='checkbox'] {
+ margin-left: 10px;
+}
+
+button {
+ background-color: #5957e4;
+ color: white;
+ border: none;
+ padding: 12px 25px;
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 16px;
+ font-weight: bold;
+ margin-top: 15px;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #413fb3;
+}
+
+.submitted-message {
+ margin-top: 30px;
+ text-align: left;
+}
+
+.submitted-message h3 {
+ color: #5957e4;
+ margin-bottom: 10px;
+ font-size: 22px;
+}
+
+@media (max-width: 500px) {
+ .form-container {
+ width: 90%;
+ padding: 20px;
+ }
+
+ .form-group {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .form-group label {
+ text-align: left;
+ margin-bottom: 5px;
+ }
+
+ .form-group input[type='text'],
+ .form-group input[type='email'],
+ .form-group input[type='number'] {
+ width: 100%;
+ }
+}
+```
+
+```jsx
+const { useState } = React;
+
+export function EventRSVPForm() {
+ const [formData, setFormData] = useState({
+ name: '',
+ email: '',
+ attendees: '',
+ dietaryPreferences: '',
+ bringingOthers: false
+ });
+
+ const [submitted, setSubmitted] = useState(false);
+
+ function handleChange(event) {
+ const { name, value, type, checked } = event.target;
+ setFormData({
+ ...formData,
+ [name]: type === 'checkbox' ? checked : value
+ });
+ }
+
+ function handleSubmit(event) {
+ event.preventDefault();
+ setSubmitted(true);
+ }
+
+ return (
+
+
Event RSVP Form
+
+ {submitted && (
+
+
RSVP Submitted!
+
+ Name: {formData.name}
+
+
+ Email: {formData.email}
+
+
+ Number of Attendees: {formData.attendees}
+
+
+ Dietary Preferences:{' '}
+ {formData.dietaryPreferences || 'None'}
+
+
+ Bringing Others:{' '}
+ {formData.bringingOthers ? 'Yes' : 'No'}
+
+
+ )}
+
+ );
+}
+```
diff --git a/curriculum/superblock-structure/full-stack.json b/curriculum/superblock-structure/full-stack.json
index 0ef7e341e73..0cff406f7ea 100644
--- a/curriculum/superblock-structure/full-stack.json
+++ b/curriculum/superblock-structure/full-stack.json
@@ -5,7 +5,11 @@
"modules": [
{
"dashedName": "getting-started-with-freecodecamp",
- "blocks": [{ "dashedName": "lecture-welcome-to-freecodecamp" }]
+ "blocks": [
+ {
+ "dashedName": "lecture-welcome-to-freecodecamp"
+ }
+ ]
}
]
},
@@ -15,40 +19,88 @@
{
"dashedName": "basic-html",
"blocks": [
- { "dashedName": "lecture-what-is-html" },
- { "dashedName": "workshop-cat-photo-app" },
- { "dashedName": "lab-recipe-page" },
- { "dashedName": "lecture-html-fundamentals" },
- { "dashedName": "lab-travel-agency-page" },
- { "dashedName": "lecture-working-with-media" },
- { "dashedName": "lab-video-compilation-page" },
- { "dashedName": "lecture-working-with-links" },
- { "dashedName": "review-basic-html" },
- { "dashedName": "quiz-basic-html" }
+ {
+ "dashedName": "lecture-what-is-html"
+ },
+ {
+ "dashedName": "workshop-cat-photo-app"
+ },
+ {
+ "dashedName": "lab-recipe-page"
+ },
+ {
+ "dashedName": "lecture-html-fundamentals"
+ },
+ {
+ "dashedName": "lab-travel-agency-page"
+ },
+ {
+ "dashedName": "lecture-working-with-media"
+ },
+ {
+ "dashedName": "lab-video-compilation-page"
+ },
+ {
+ "dashedName": "lecture-working-with-links"
+ },
+ {
+ "dashedName": "review-basic-html"
+ },
+ {
+ "dashedName": "quiz-basic-html"
+ }
]
},
{
"dashedName": "semantic-html",
"blocks": [
- { "dashedName": "lecture-importance-of-semantic-html" },
- { "dashedName": "workshop-blog-page" },
- { "dashedName": "lab-event-hub" },
- { "dashedName": "review-semantic-html" },
- { "dashedName": "quiz-semantic-html" }
+ {
+ "dashedName": "lecture-importance-of-semantic-html"
+ },
+ {
+ "dashedName": "workshop-blog-page"
+ },
+ {
+ "dashedName": "lab-event-hub"
+ },
+ {
+ "dashedName": "review-semantic-html"
+ },
+ {
+ "dashedName": "quiz-semantic-html"
+ }
]
},
{
"dashedName": "html-forms-and-tables",
"blocks": [
- { "dashedName": "lecture-working-with-forms" },
- { "dashedName": "workshop-hotel-feedback-form" },
- { "dashedName": "lab-survey-form" },
- { "dashedName": "lecture-working-with-tables" },
- { "dashedName": "workshop-final-exams-table" },
- { "dashedName": "lab-book-catalog-table" },
- { "dashedName": "lecture-working-with-html-tools" },
- { "dashedName": "review-html-tables-and-forms" },
- { "dashedName": "quiz-html-tables-and-forms" }
+ {
+ "dashedName": "lecture-working-with-forms"
+ },
+ {
+ "dashedName": "workshop-hotel-feedback-form"
+ },
+ {
+ "dashedName": "lab-survey-form"
+ },
+ {
+ "dashedName": "lecture-working-with-tables"
+ },
+ {
+ "dashedName": "workshop-final-exams-table"
+ },
+ {
+ "dashedName": "lab-book-catalog-table"
+ },
+ {
+ "dashedName": "lecture-working-with-html-tools"
+ },
+ {
+ "dashedName": "review-html-tables-and-forms"
+ },
+ {
+ "dashedName": "quiz-html-tables-and-forms"
+ }
]
},
{
@@ -57,15 +109,25 @@
{
"dashedName": "lecture-importance-of-accessibility-and-good-html-structure"
},
- { "dashedName": "lab-checkout-page" },
- { "dashedName": "review-html-accessibility" },
- { "dashedName": "quiz-html-accessibility" }
+ {
+ "dashedName": "lab-checkout-page"
+ },
+ {
+ "dashedName": "review-html-accessibility"
+ },
+ {
+ "dashedName": "quiz-html-accessibility"
+ }
]
},
{
"moduleType": "review",
"dashedName": "review-html",
- "blocks": [{ "dashedName": "review-html" }]
+ "blocks": [
+ {
+ "dashedName": "review-html"
+ }
+ ]
},
{
"moduleType": "exam",
@@ -84,39 +146,79 @@
{
"dashedName": "lecture-understanding-computer-internet-and-tooling-basics"
},
- { "dashedName": "lecture-working-with-file-systems" },
- { "dashedName": "lecture-browsing-the-web-effectively" },
- { "dashedName": "review-computer-basics" },
- { "dashedName": "quiz-computer-basics" }
+ {
+ "dashedName": "lecture-working-with-file-systems"
+ },
+ {
+ "dashedName": "lecture-browsing-the-web-effectively"
+ },
+ {
+ "dashedName": "review-computer-basics"
+ },
+ {
+ "dashedName": "quiz-computer-basics"
+ }
]
},
{
"dashedName": "basic-css",
"blocks": [
- { "dashedName": "lecture-what-is-css" },
- { "dashedName": "workshop-cafe-menu" },
- { "dashedName": "lab-business-card" },
+ {
+ "dashedName": "lecture-what-is-css"
+ },
+ {
+ "dashedName": "workshop-cafe-menu"
+ },
+ {
+ "dashedName": "lab-business-card"
+ },
{
"dashedName": "lecture-css-specificity-the-cascade-algorithm-and-inheritance"
},
- { "dashedName": "review-basic-css" },
- { "dashedName": "quiz-basic-css" },
- { "dashedName": "lecture-styling-lists-and-links" },
- { "dashedName": "lab-stylized-to-do-list" },
- { "dashedName": "lecture-working-with-backgrounds-and-borders" },
- { "dashedName": "lab-blog-post-card" },
- { "dashedName": "review-css-backgrounds-and-borders" },
- { "dashedName": "quiz-css-backgrounds-and-borders" }
+ {
+ "dashedName": "review-basic-css"
+ },
+ {
+ "dashedName": "quiz-basic-css"
+ },
+ {
+ "dashedName": "lecture-styling-lists-and-links"
+ },
+ {
+ "dashedName": "lab-stylized-to-do-list"
+ },
+ {
+ "dashedName": "lecture-working-with-backgrounds-and-borders"
+ },
+ {
+ "dashedName": "lab-blog-post-card"
+ },
+ {
+ "dashedName": "review-css-backgrounds-and-borders"
+ },
+ {
+ "dashedName": "quiz-css-backgrounds-and-borders"
+ }
]
},
{
"dashedName": "design-for-developers",
"blocks": [
- { "dashedName": "lecture-user-interface-design-fundamentals" },
- { "dashedName": "lecture-user-centered-design" },
- { "dashedName": "lecture-common-design-tools" },
- { "dashedName": "review-design-fundamentals" },
- { "dashedName": "quiz-design-fundamentals" }
+ {
+ "dashedName": "lecture-user-interface-design-fundamentals"
+ },
+ {
+ "dashedName": "lecture-user-centered-design"
+ },
+ {
+ "dashedName": "lecture-common-design-tools"
+ },
+ {
+ "dashedName": "review-design-fundamentals"
+ },
+ {
+ "dashedName": "quiz-design-fundamentals"
+ }
]
},
{
@@ -125,9 +227,15 @@
{
"dashedName": "lecture-working-with-relative-and-absolute-units"
},
- { "dashedName": "lab-event-flyer-page" },
- { "dashedName": "review-css-relative-and-absolute-units" },
- { "dashedName": "quiz-css-relative-and-absolute-units" }
+ {
+ "dashedName": "lab-event-flyer-page"
+ },
+ {
+ "dashedName": "review-css-relative-and-absolute-units"
+ },
+ {
+ "dashedName": "quiz-css-relative-and-absolute-units"
+ }
]
},
{
@@ -136,30 +244,58 @@
{
"dashedName": "lecture-working-with-pseudo-classes-and-pseudo-elements-in-css"
},
- { "dashedName": "workshop-greeting-card" },
- { "dashedName": "lab-job-application-form" },
- { "dashedName": "review-css-pseudo-classes" },
- { "dashedName": "quiz-css-pseudo-classes" }
+ {
+ "dashedName": "workshop-greeting-card"
+ },
+ {
+ "dashedName": "lab-job-application-form"
+ },
+ {
+ "dashedName": "review-css-pseudo-classes"
+ },
+ {
+ "dashedName": "quiz-css-pseudo-classes"
+ }
]
},
{
"dashedName": "css-colors",
"blocks": [
- { "dashedName": "lecture-working-with-colors-in-css" },
- { "dashedName": "workshop-colored-markers" },
- { "dashedName": "lab-colored-boxes" },
- { "dashedName": "review-css-colors" },
- { "dashedName": "quiz-css-colors" }
+ {
+ "dashedName": "lecture-working-with-colors-in-css"
+ },
+ {
+ "dashedName": "workshop-colored-markers"
+ },
+ {
+ "dashedName": "lab-colored-boxes"
+ },
+ {
+ "dashedName": "review-css-colors"
+ },
+ {
+ "dashedName": "quiz-css-colors"
+ }
]
},
{
"dashedName": "styling-forms",
"blocks": [
- { "dashedName": "lecture-best-practices-for-styling-forms" },
- { "dashedName": "workshop-registration-form" },
- { "dashedName": "lab-contact-form" },
- { "dashedName": "review-styling-forms" },
- { "dashedName": "quiz-styling-forms" }
+ {
+ "dashedName": "lecture-best-practices-for-styling-forms"
+ },
+ {
+ "dashedName": "workshop-registration-form"
+ },
+ {
+ "dashedName": "lab-contact-form"
+ },
+ {
+ "dashedName": "review-styling-forms"
+ },
+ {
+ "dashedName": "quiz-styling-forms"
+ }
]
},
{
@@ -168,30 +304,58 @@
{
"dashedName": "lecture-working-with-css-transforms-overflow-and-filters"
},
- { "dashedName": "workshop-rothko-painting" },
- { "dashedName": "lab-confidential-email-page" },
- { "dashedName": "review-css-layout-and-effects" },
- { "dashedName": "quiz-css-layout-and-effects" }
+ {
+ "dashedName": "workshop-rothko-painting"
+ },
+ {
+ "dashedName": "lab-confidential-email-page"
+ },
+ {
+ "dashedName": "review-css-layout-and-effects"
+ },
+ {
+ "dashedName": "quiz-css-layout-and-effects"
+ }
]
},
{
"dashedName": "css-flexbox",
"blocks": [
- { "dashedName": "lecture-working-with-css-flexbox" },
- { "dashedName": "workshop-flexbox-photo-gallery" },
- { "dashedName": "lab-page-of-playing-cards" },
- { "dashedName": "review-css-flexbox" },
- { "dashedName": "quiz-css-flexbox" }
+ {
+ "dashedName": "lecture-working-with-css-flexbox"
+ },
+ {
+ "dashedName": "workshop-flexbox-photo-gallery"
+ },
+ {
+ "dashedName": "lab-page-of-playing-cards"
+ },
+ {
+ "dashedName": "review-css-flexbox"
+ },
+ {
+ "dashedName": "quiz-css-flexbox"
+ }
]
},
{
"dashedName": "css-typography",
"blocks": [
- { "dashedName": "lecture-working-with-css-fonts" },
- { "dashedName": "workshop-nutritional-label" },
- { "dashedName": "lab-newspaper-article" },
- { "dashedName": "review-css-typography" },
- { "dashedName": "quiz-css-typography" }
+ {
+ "dashedName": "lecture-working-with-css-fonts"
+ },
+ {
+ "dashedName": "workshop-nutritional-label"
+ },
+ {
+ "dashedName": "lab-newspaper-article"
+ },
+ {
+ "dashedName": "review-css-typography"
+ },
+ {
+ "dashedName": "quiz-css-typography"
+ }
]
},
{
@@ -200,10 +364,18 @@
{
"dashedName": "lecture-best-practices-for-accessibility-and-css"
},
- { "dashedName": "workshop-accessibility-quiz" },
- { "dashedName": "lab-tribute-page" },
- { "dashedName": "review-css-accessibility" },
- { "dashedName": "quiz-css-accessibility" }
+ {
+ "dashedName": "workshop-accessibility-quiz"
+ },
+ {
+ "dashedName": "lab-tribute-page"
+ },
+ {
+ "dashedName": "review-css-accessibility"
+ },
+ {
+ "dashedName": "quiz-css-accessibility"
+ }
]
},
{
@@ -212,20 +384,38 @@
{
"dashedName": "lecture-understanding-how-to-work-with-floats-and-positioning-in-css"
},
- { "dashedName": "workshop-cat-painting" },
- { "dashedName": "lab-house-painting" },
- { "dashedName": "review-css-positioning" },
- { "dashedName": "quiz-css-positioning" }
+ {
+ "dashedName": "workshop-cat-painting"
+ },
+ {
+ "dashedName": "lab-house-painting"
+ },
+ {
+ "dashedName": "review-css-positioning"
+ },
+ {
+ "dashedName": "quiz-css-positioning"
+ }
]
},
{
"dashedName": "attribute-selectors",
"blocks": [
- { "dashedName": "lecture-working-with-attribute-selectors" },
- { "dashedName": "workshop-balance-sheet" },
- { "dashedName": "lab-book-inventory-app" },
- { "dashedName": "review-css-attribute-selectors" },
- { "dashedName": "quiz-css-attribute-selectors" }
+ {
+ "dashedName": "lecture-working-with-attribute-selectors"
+ },
+ {
+ "dashedName": "workshop-balance-sheet"
+ },
+ {
+ "dashedName": "lab-book-inventory-app"
+ },
+ {
+ "dashedName": "review-css-attribute-selectors"
+ },
+ {
+ "dashedName": "quiz-css-attribute-selectors"
+ }
]
},
{
@@ -234,50 +424,100 @@
{
"dashedName": "lecture-best-practices-for-responsive-web-design"
},
- { "dashedName": "workshop-piano" },
- { "dashedName": "lab-technical-documentation-page" },
- { "dashedName": "review-responsive-web-design" },
- { "dashedName": "quiz-responsive-web-design" }
+ {
+ "dashedName": "workshop-piano"
+ },
+ {
+ "dashedName": "lab-technical-documentation-page"
+ },
+ {
+ "dashedName": "review-responsive-web-design"
+ },
+ {
+ "dashedName": "quiz-responsive-web-design"
+ }
]
},
{
"dashedName": "css-variables",
"blocks": [
- { "dashedName": "lecture-working-with-css-variables" },
- { "dashedName": "workshop-city-skyline" },
- { "dashedName": "lab-availability-table" },
- { "dashedName": "review-css-variables" },
- { "dashedName": "quiz-css-variables" }
+ {
+ "dashedName": "lecture-working-with-css-variables"
+ },
+ {
+ "dashedName": "workshop-city-skyline"
+ },
+ {
+ "dashedName": "lab-availability-table"
+ },
+ {
+ "dashedName": "review-css-variables"
+ },
+ {
+ "dashedName": "quiz-css-variables"
+ }
]
},
{
"dashedName": "css-grid",
"blocks": [
- { "dashedName": "lecture-working-with-css-grid" },
- { "dashedName": "workshop-magazine" },
- { "dashedName": "lab-magazine-layout" },
- { "dashedName": "lecture-debugging-css" },
- { "dashedName": "lab-product-landing-page" },
- { "dashedName": "review-css-grid" },
- { "dashedName": "quiz-css-grid" }
+ {
+ "dashedName": "lecture-working-with-css-grid"
+ },
+ {
+ "dashedName": "workshop-magazine"
+ },
+ {
+ "dashedName": "lab-magazine-layout"
+ },
+ {
+ "dashedName": "lecture-debugging-css"
+ },
+ {
+ "dashedName": "lab-product-landing-page"
+ },
+ {
+ "dashedName": "review-css-grid"
+ },
+ {
+ "dashedName": "quiz-css-grid"
+ }
]
},
{
"dashedName": "css-animations",
"blocks": [
- { "dashedName": "lecture-animations-and-accessibility" },
- { "dashedName": "workshop-ferris-wheel" },
- { "dashedName": "lab-moon-orbit" },
- { "dashedName": "workshop-flappy-penguin" },
- { "dashedName": "lab-personal-portfolio" },
- { "dashedName": "review-css-animations" },
- { "dashedName": "quiz-css-animations" }
+ {
+ "dashedName": "lecture-animations-and-accessibility"
+ },
+ {
+ "dashedName": "workshop-ferris-wheel"
+ },
+ {
+ "dashedName": "lab-moon-orbit"
+ },
+ {
+ "dashedName": "workshop-flappy-penguin"
+ },
+ {
+ "dashedName": "lab-personal-portfolio"
+ },
+ {
+ "dashedName": "review-css-animations"
+ },
+ {
+ "dashedName": "quiz-css-animations"
+ }
]
},
{
"moduleType": "review",
"dashedName": "review-css",
- "blocks": [{ "dashedName": "review-css" }]
+ "blocks": [
+ {
+ "dashedName": "review-css"
+ }
+ ]
},
{
"moduleType": "exam",
@@ -293,24 +533,50 @@
{
"dashedName": "code-editors",
"blocks": [
- { "dashedName": "lecture-working-with-code-editors-and-ides" }
+ {
+ "dashedName": "lecture-working-with-code-editors-and-ides"
+ }
]
},
{
"dashedName": "javascript-variables-and-strings",
"blocks": [
- { "dashedName": "lecture-introduction-to-javascript" },
- { "dashedName": "workshop-greeting-bot" },
- { "dashedName": "lab-javascript-trivia-bot" },
- { "dashedName": "lab-sentence-maker" },
- { "dashedName": "lecture-working-with-data-types" },
- { "dashedName": "review-javascript-variables-and-data-types" },
- { "dashedName": "quiz-javascript-variables-and-data-types" },
- { "dashedName": "lecture-working-with-strings-in-javascript" },
- { "dashedName": "workshop-teacher-chatbot" },
- { "dashedName": "lecture-working-with-common-string-methods" },
- { "dashedName": "review-javascript-strings" },
- { "dashedName": "quiz-javascript-strings" }
+ {
+ "dashedName": "lecture-introduction-to-javascript"
+ },
+ {
+ "dashedName": "workshop-greeting-bot"
+ },
+ {
+ "dashedName": "lab-javascript-trivia-bot"
+ },
+ {
+ "dashedName": "lab-sentence-maker"
+ },
+ {
+ "dashedName": "lecture-working-with-data-types"
+ },
+ {
+ "dashedName": "review-javascript-variables-and-data-types"
+ },
+ {
+ "dashedName": "quiz-javascript-variables-and-data-types"
+ },
+ {
+ "dashedName": "lecture-working-with-strings-in-javascript"
+ },
+ {
+ "dashedName": "workshop-teacher-chatbot"
+ },
+ {
+ "dashedName": "lecture-working-with-common-string-methods"
+ },
+ {
+ "dashedName": "review-javascript-strings"
+ },
+ {
+ "dashedName": "quiz-javascript-strings"
+ }
]
},
{
@@ -319,61 +585,119 @@
{
"dashedName": "lecture-working-with-numbers-booleans-and-the-math-object"
},
- { "dashedName": "workshop-mathbot" },
- { "dashedName": "lab-fortune-teller" },
+ {
+ "dashedName": "workshop-mathbot"
+ },
+ {
+ "dashedName": "lab-fortune-teller"
+ },
{
"dashedName": "lecture-working-with-numbers-and-common-number-methods"
},
- { "dashedName": "review-javascript-math" },
- { "dashedName": "quiz-javascript-math" },
+ {
+ "dashedName": "review-javascript-math"
+ },
+ {
+ "dashedName": "quiz-javascript-math"
+ },
{
"dashedName": "lecture-understanding-comparisons-and-conditionals"
},
- { "dashedName": "review-javascript-comparisons-and-conditionals" },
- { "dashedName": "quiz-javascript-comparisons-and-conditionals" }
+ {
+ "dashedName": "review-javascript-comparisons-and-conditionals"
+ },
+ {
+ "dashedName": "quiz-javascript-comparisons-and-conditionals"
+ }
]
},
{
"dashedName": "javascript-functions",
"blocks": [
- { "dashedName": "lecture-working-with-functions" },
- { "dashedName": "workshop-calculator" },
- { "dashedName": "lab-email-masker" },
- { "dashedName": "workshop-loan-qualification-checker" },
- { "dashedName": "lab-leap-year-calculator" },
- { "dashedName": "review-javascript-functions" },
- { "dashedName": "quiz-javascript-functions" }
+ {
+ "dashedName": "lecture-working-with-functions"
+ },
+ {
+ "dashedName": "workshop-calculator"
+ },
+ {
+ "dashedName": "lab-email-masker"
+ },
+ {
+ "dashedName": "workshop-loan-qualification-checker"
+ },
+ {
+ "dashedName": "lab-leap-year-calculator"
+ },
+ {
+ "dashedName": "review-javascript-functions"
+ },
+ {
+ "dashedName": "quiz-javascript-functions"
+ }
]
},
{
"dashedName": "javascript-arrays",
"blocks": [
- { "dashedName": "lecture-working-with-arrays" },
- { "dashedName": "workshop-shopping-list" },
- { "dashedName": "lab-lunch-picker-program" },
- { "dashedName": "lecture-working-with-common-array-methods" },
- { "dashedName": "review-javascript-arrays" },
- { "dashedName": "quiz-javascript-arrays" }
+ {
+ "dashedName": "lecture-working-with-arrays"
+ },
+ {
+ "dashedName": "workshop-shopping-list"
+ },
+ {
+ "dashedName": "lab-lunch-picker-program"
+ },
+ {
+ "dashedName": "lecture-working-with-common-array-methods"
+ },
+ {
+ "dashedName": "review-javascript-arrays"
+ },
+ {
+ "dashedName": "quiz-javascript-arrays"
+ }
]
},
{
"dashedName": "javascript-objects",
"blocks": [
- { "dashedName": "lecture-working-with-objects" },
- { "dashedName": "workshop-recipe-tracker" },
- { "dashedName": "lab-quiz-game" },
- { "dashedName": "review-javascript-objects" },
- { "dashedName": "quiz-javascript-objects" }
+ {
+ "dashedName": "lecture-working-with-objects"
+ },
+ {
+ "dashedName": "workshop-recipe-tracker"
+ },
+ {
+ "dashedName": "lab-quiz-game"
+ },
+ {
+ "dashedName": "review-javascript-objects"
+ },
+ {
+ "dashedName": "quiz-javascript-objects"
+ }
]
},
{
"dashedName": "javascript-loops",
"blocks": [
- { "dashedName": "lecture-working-with-loops" },
- { "dashedName": "workshop-sentence-analyzer" },
- { "dashedName": "lab-factorial-calculator" },
- { "dashedName": "review-javascript-loops" },
- { "dashedName": "quiz-javascript-loops" }
+ {
+ "dashedName": "lecture-working-with-loops"
+ },
+ {
+ "dashedName": "workshop-sentence-analyzer"
+ },
+ {
+ "dashedName": "lab-factorial-calculator"
+ },
+ {
+ "dashedName": "review-javascript-loops"
+ },
+ {
+ "dashedName": "quiz-javascript-loops"
+ }
]
},
{
@@ -382,16 +706,30 @@
{
"dashedName": "lecture-understanding-core-javascript-fundamentals"
},
- { "dashedName": "lab-pyramid-generator" },
- { "dashedName": "lab-gradebook-app" },
- { "dashedName": "lecture-the-var-keyword-and-hoisting" },
- { "dashedName": "lab-inventory-management-program" },
+ {
+ "dashedName": "lab-pyramid-generator"
+ },
+ {
+ "dashedName": "lab-gradebook-app"
+ },
+ {
+ "dashedName": "lecture-the-var-keyword-and-hoisting"
+ },
+ {
+ "dashedName": "lab-inventory-management-program"
+ },
{
"dashedName": "lecture-understanding-modules-imports-and-exports"
},
- { "dashedName": "lab-password-generator" },
- { "dashedName": "review-javascript-fundamentals" },
- { "dashedName": "quiz-javascript-fundamentals" }
+ {
+ "dashedName": "lab-password-generator"
+ },
+ {
+ "dashedName": "review-javascript-fundamentals"
+ },
+ {
+ "dashedName": "quiz-javascript-fundamentals"
+ }
]
},
{
@@ -400,10 +738,18 @@
{
"dashedName": "lecture-working-with-higher-order-functions-and-callbacks"
},
- { "dashedName": "workshop-library-manager" },
- { "dashedName": "lab-book-organizer" },
- { "dashedName": "review-javascript-higher-order-functions" },
- { "dashedName": "quiz-javascript-higher-order-functions" }
+ {
+ "dashedName": "workshop-library-manager"
+ },
+ {
+ "dashedName": "lab-book-organizer"
+ },
+ {
+ "dashedName": "review-javascript-higher-order-functions"
+ },
+ {
+ "dashedName": "quiz-javascript-higher-order-functions"
+ }
]
},
{
@@ -412,16 +758,30 @@
{
"dashedName": "lecture-working-with-the-dom-click-events-and-web-apis"
},
- { "dashedName": "workshop-storytelling-app" },
- { "dashedName": "lab-favorite-icon-toggler" },
+ {
+ "dashedName": "workshop-storytelling-app"
+ },
+ {
+ "dashedName": "lab-favorite-icon-toggler"
+ },
{
"dashedName": "lecture-understanding-the-event-object-and-event-delegation"
},
- { "dashedName": "workshop-music-instrument-filter" },
- { "dashedName": "lab-real-time-counter" },
- { "dashedName": "lab-lightbox-viewer" },
- { "dashedName": "workshop-rps-game" },
- { "dashedName": "lab-football-team-cards" },
+ {
+ "dashedName": "workshop-music-instrument-filter"
+ },
+ {
+ "dashedName": "lab-real-time-counter"
+ },
+ {
+ "dashedName": "lab-lightbox-viewer"
+ },
+ {
+ "dashedName": "workshop-rps-game"
+ },
+ {
+ "dashedName": "lab-football-team-cards"
+ },
{
"dashedName": "review-dom-manipulation-and-click-events-with-javascript"
},
@@ -433,61 +793,121 @@
{
"dashedName": "debugging-javascript",
"blocks": [
- { "dashedName": "lecture-debugging-techniques" },
- { "dashedName": "lab-random-background-color-changer" },
- { "dashedName": "review-debugging-javascript" },
- { "dashedName": "quiz-debugging-javascript" }
+ {
+ "dashedName": "lecture-debugging-techniques"
+ },
+ {
+ "dashedName": "lab-random-background-color-changer"
+ },
+ {
+ "dashedName": "review-debugging-javascript"
+ },
+ {
+ "dashedName": "quiz-debugging-javascript"
+ }
]
},
{
"dashedName": "basic-regex",
"blocks": [
- { "dashedName": "lecture-working-with-regular-expressions" },
- { "dashedName": "workshop-spam-filter" },
- { "dashedName": "lab-palindrome-checker" },
- { "dashedName": "lab-markdown-to-html-converter" },
- { "dashedName": "lab-regex-sandbox" },
- { "dashedName": "review-javascript-regular-expressions" },
- { "dashedName": "quiz-javascript-regular-expressions" }
+ {
+ "dashedName": "lecture-working-with-regular-expressions"
+ },
+ {
+ "dashedName": "workshop-spam-filter"
+ },
+ {
+ "dashedName": "lab-palindrome-checker"
+ },
+ {
+ "dashedName": "lab-markdown-to-html-converter"
+ },
+ {
+ "dashedName": "lab-regex-sandbox"
+ },
+ {
+ "dashedName": "review-javascript-regular-expressions"
+ },
+ {
+ "dashedName": "quiz-javascript-regular-expressions"
+ }
]
},
{
"dashedName": "form-validation",
"blocks": [
- { "dashedName": "lecture-understanding-form-validation" },
- { "dashedName": "workshop-calorie-counter" },
- { "dashedName": "lab-customer-complaint-form" },
- { "dashedName": "review-form-validation-with-javascript" },
- { "dashedName": "quiz-form-validation-with-javascript" }
+ {
+ "dashedName": "lecture-understanding-form-validation"
+ },
+ {
+ "dashedName": "workshop-calorie-counter"
+ },
+ {
+ "dashedName": "lab-customer-complaint-form"
+ },
+ {
+ "dashedName": "review-form-validation-with-javascript"
+ },
+ {
+ "dashedName": "quiz-form-validation-with-javascript"
+ }
]
},
{
"dashedName": "javascript-dates",
"blocks": [
- { "dashedName": "lecture-working-with-dates" },
- { "dashedName": "lab-date-conversion" },
- { "dashedName": "review-javascript-dates" },
- { "dashedName": "quiz-javascript-dates" }
+ {
+ "dashedName": "lecture-working-with-dates"
+ },
+ {
+ "dashedName": "lab-date-conversion"
+ },
+ {
+ "dashedName": "review-javascript-dates"
+ },
+ {
+ "dashedName": "quiz-javascript-dates"
+ }
]
},
{
"dashedName": "audio-and-video-events",
"blocks": [
- { "dashedName": "lecture-working-with-audio-and-video" },
- { "dashedName": "workshop-music-player" },
- { "dashedName": "lab-drum-machine" },
- { "dashedName": "review-javascript-audio-and-video" },
- { "dashedName": "quiz-javascript-audio-and-video" }
+ {
+ "dashedName": "lecture-working-with-audio-and-video"
+ },
+ {
+ "dashedName": "workshop-music-player"
+ },
+ {
+ "dashedName": "lab-drum-machine"
+ },
+ {
+ "dashedName": "review-javascript-audio-and-video"
+ },
+ {
+ "dashedName": "quiz-javascript-audio-and-video"
+ }
]
},
{
"dashedName": "maps-and-sets",
"blocks": [
- { "dashedName": "lecture-working-with-maps-and-sets" },
- { "dashedName": "workshop-plant-nursery-catalog" },
- { "dashedName": "lab-voting-system" },
- { "dashedName": "review-javascript-maps-and-sets" },
- { "dashedName": "quiz-javascript-maps-and-sets" }
+ {
+ "dashedName": "lecture-working-with-maps-and-sets"
+ },
+ {
+ "dashedName": "workshop-plant-nursery-catalog"
+ },
+ {
+ "dashedName": "lab-voting-system"
+ },
+ {
+ "dashedName": "review-javascript-maps-and-sets"
+ },
+ {
+ "dashedName": "quiz-javascript-maps-and-sets"
+ }
]
},
{
@@ -496,10 +916,18 @@
{
"dashedName": "lecture-working-with-client-side-storage-and-crud-operations"
},
- { "dashedName": "workshop-todo-app" },
- { "dashedName": "lab-bookmark-manager-app" },
- { "dashedName": "review-local-storage-and-crud" },
- { "dashedName": "quiz-local-storage-and-crud" }
+ {
+ "dashedName": "workshop-todo-app"
+ },
+ {
+ "dashedName": "lab-bookmark-manager-app"
+ },
+ {
+ "dashedName": "review-local-storage-and-crud"
+ },
+ {
+ "dashedName": "quiz-local-storage-and-crud"
+ }
]
},
{
@@ -508,11 +936,21 @@
{
"dashedName": "lecture-understanding-how-to-work-with-classes-in-javascript"
},
- { "dashedName": "workshop-shopping-cart" },
- { "dashedName": "lab-project-idea-board" },
- { "dashedName": "lab-bank-account-manager" },
- { "dashedName": "review-javascript-classes" },
- { "dashedName": "quiz-javascript-classes" }
+ {
+ "dashedName": "workshop-shopping-cart"
+ },
+ {
+ "dashedName": "lab-project-idea-board"
+ },
+ {
+ "dashedName": "lab-bank-account-manager"
+ },
+ {
+ "dashedName": "review-javascript-classes"
+ },
+ {
+ "dashedName": "quiz-javascript-classes"
+ }
]
},
{
@@ -521,37 +959,71 @@
{
"dashedName": "lecture-understanding-recursion-and-the-call-stack"
},
- { "dashedName": "workshop-decimal-to-binary-converter" },
- { "dashedName": "lab-permutation-generator" },
- { "dashedName": "review-recursion" },
- { "dashedName": "quiz-recursion" }
+ {
+ "dashedName": "workshop-decimal-to-binary-converter"
+ },
+ {
+ "dashedName": "lab-permutation-generator"
+ },
+ {
+ "dashedName": "review-recursion"
+ },
+ {
+ "dashedName": "quiz-recursion"
+ }
]
},
{
"dashedName": "functional-programming",
"blocks": [
- { "dashedName": "lecture-understanding-functional-programming" },
- { "dashedName": "workshop-recipe-ingredient-converter" },
- { "dashedName": "lab-sorting-visualizer" },
- { "dashedName": "review-javascript-functional-programming" },
- { "dashedName": "quiz-javascript-functional-programming" }
+ {
+ "dashedName": "lecture-understanding-functional-programming"
+ },
+ {
+ "dashedName": "workshop-recipe-ingredient-converter"
+ },
+ {
+ "dashedName": "lab-sorting-visualizer"
+ },
+ {
+ "dashedName": "review-javascript-functional-programming"
+ },
+ {
+ "dashedName": "quiz-javascript-functional-programming"
+ }
]
},
{
"dashedName": "asynchronous-javascript",
"blocks": [
- { "dashedName": "lecture-understanding-asynchronous-programming" },
- { "dashedName": "workshop-fcc-authors-page" },
- { "dashedName": "lab-fcc-forum-leaderboard" },
- { "dashedName": "lab-weather-app" },
- { "dashedName": "review-asynchronous-javascript" },
- { "dashedName": "quiz-asynchronous-javascript" }
+ {
+ "dashedName": "lecture-understanding-asynchronous-programming"
+ },
+ {
+ "dashedName": "workshop-fcc-authors-page"
+ },
+ {
+ "dashedName": "lab-fcc-forum-leaderboard"
+ },
+ {
+ "dashedName": "lab-weather-app"
+ },
+ {
+ "dashedName": "review-asynchronous-javascript"
+ },
+ {
+ "dashedName": "quiz-asynchronous-javascript"
+ }
]
},
{
"moduleType": "review",
"dashedName": "review-javascript",
- "blocks": [{ "dashedName": "review-javascript" }]
+ "blocks": [
+ {
+ "dashedName": "review-javascript"
+ }
+ ]
},
{
"moduleType": "exam",
@@ -561,7 +1033,6 @@
}
]
},
-
{
"dashedName": "frontend-libraries",
"modules": [
@@ -571,13 +1042,27 @@
{
"dashedName": "lecture-introduction-to-javascript-libraries-and-frameworks"
},
- { "dashedName": "workshop-reusable-mega-navbar" },
- { "dashedName": "lab-reusable-footer" },
- { "dashedName": "lecture-working-with-data-in-react" },
- { "dashedName": "workshop-reusable-profile-card-component" },
- { "dashedName": "lab-mood-board" },
- { "dashedName": "review-react-basics" },
- { "dashedName": "quiz-react-basics" }
+ {
+ "dashedName": "workshop-reusable-mega-navbar"
+ },
+ {
+ "dashedName": "lab-reusable-footer"
+ },
+ {
+ "dashedName": "lecture-working-with-data-in-react"
+ },
+ {
+ "dashedName": "workshop-reusable-profile-card-component"
+ },
+ {
+ "dashedName": "lab-mood-board"
+ },
+ {
+ "dashedName": "review-react-basics"
+ },
+ {
+ "dashedName": "quiz-react-basics"
+ }
]
},
{
@@ -590,9 +1075,18 @@
{
"dashedName": "lecture-understanding-effects-and-referencing-values-in-react"
},
- { "dashedName": "review-react-state-and-hooks" },
- { "dashedName": "quiz-react-state-and-hooks" },
- { "dashedName": "lecture-working-with-forms-in-react" },
+ {
+ "dashedName": "review-react-state-and-hooks"
+ },
+ {
+ "dashedName": "quiz-react-state-and-hooks"
+ },
+ {
+ "dashedName": "lecture-working-with-forms-in-react"
+ },
+ {
+ "dashedName": "lab-event-rsvp"
+ },
{
"dashedName": "lab-currency-converter"
},
@@ -602,9 +1096,15 @@
{
"dashedName": "lecture-routing-react-frameworks-and-dependency-management-tools"
},
- { "dashedName": "lecture-react-strategies-and-debugging" },
- { "dashedName": "review-react-forms-data-fetching-and-routing" },
- { "dashedName": "quiz-react-forms-data-fetching-and-routing" }
+ {
+ "dashedName": "lecture-react-strategies-and-debugging"
+ },
+ {
+ "dashedName": "review-react-forms-data-fetching-and-routing"
+ },
+ {
+ "dashedName": "quiz-react-forms-data-fetching-and-routing"
+ }
]
},
{
@@ -614,8 +1114,12 @@
{
"dashedName": "lecture-understanding-performance-in-web-applications"
},
- { "dashedName": "review-web-performance" },
- { "dashedName": "quiz-web-performance" }
+ {
+ "dashedName": "review-web-performance"
+ },
+ {
+ "dashedName": "quiz-web-performance"
+ }
]
},
{
@@ -625,8 +1129,12 @@
{
"dashedName": "lecture-working-with-css-libraries-and-frameworks"
},
- { "dashedName": "review-css-libraries-and-frameworks" },
- { "dashedName": "quiz-css-libraries-and-frameworks" }
+ {
+ "dashedName": "review-css-libraries-and-frameworks"
+ },
+ {
+ "dashedName": "quiz-css-libraries-and-frameworks"
+ }
]
},
{
@@ -644,16 +1152,26 @@
"dashedName": "typescript-fundamentals",
"comingSoon": true,
"blocks": [
- { "dashedName": "lecture-introduction-to-typescript" },
- { "dashedName": "review-typescript" },
- { "dashedName": "quiz-typescript" }
+ {
+ "dashedName": "lecture-introduction-to-typescript"
+ },
+ {
+ "dashedName": "review-typescript"
+ },
+ {
+ "dashedName": "quiz-typescript"
+ }
]
},
{
"moduleType": "review",
"comingSoon": true,
"dashedName": "review-front-end-libraries",
- "blocks": [{ "dashedName": "review-front-end-libraries" }]
+ "blocks": [
+ {
+ "dashedName": "review-front-end-libraries"
+ }
+ ]
},
{
"moduleType": "exam",
@@ -670,63 +1188,119 @@
{
"dashedName": "bash-fundamentals",
"blocks": [
- { "dashedName": "workshop-bash-boilerplate" },
- { "dashedName": "review-bash-commands" },
- { "dashedName": "quiz-bash-commands" }
+ {
+ "dashedName": "workshop-bash-boilerplate"
+ },
+ {
+ "dashedName": "review-bash-commands"
+ },
+ {
+ "dashedName": "quiz-bash-commands"
+ }
]
},
{
"dashedName": "relational-databases",
"blocks": [
- { "dashedName": "workshop-database-of-video-game-characters" },
- { "dashedName": "lab-celestial-bodies-database" },
- { "dashedName": "review-relational-database" },
- { "dashedName": "quiz-relational-database" }
+ {
+ "dashedName": "workshop-database-of-video-game-characters"
+ },
+ {
+ "dashedName": "lab-celestial-bodies-database"
+ },
+ {
+ "dashedName": "review-relational-database"
+ },
+ {
+ "dashedName": "quiz-relational-database"
+ }
]
},
{
"dashedName": "bash-scripting",
"blocks": [
- { "dashedName": "workshop-bash-five-programs" },
- { "dashedName": "review-bash-scripting" },
- { "dashedName": "quiz-bash-scripting" }
+ {
+ "dashedName": "workshop-bash-five-programs"
+ },
+ {
+ "dashedName": "review-bash-scripting"
+ },
+ {
+ "dashedName": "quiz-bash-scripting"
+ }
]
},
{
"dashedName": "sql-and-bash",
"blocks": [
- { "dashedName": "workshop-sql-student-database-part-1" },
- { "dashedName": "workshop-sql-student-database-part-2" },
- { "dashedName": "lab-world-cup-database" },
- { "dashedName": "workshop-kitty-ipsum-translator" },
- { "dashedName": "workshop-bike-rental-shop" },
- { "dashedName": "lab-salon-appointment-scheduler" },
- { "dashedName": "review-bash-and-sql" },
- { "dashedName": "quiz-bash-and-sql" }
+ {
+ "dashedName": "workshop-sql-student-database-part-1"
+ },
+ {
+ "dashedName": "workshop-sql-student-database-part-2"
+ },
+ {
+ "dashedName": "lab-world-cup-database"
+ },
+ {
+ "dashedName": "workshop-kitty-ipsum-translator"
+ },
+ {
+ "dashedName": "workshop-bike-rental-shop"
+ },
+ {
+ "dashedName": "lab-salon-appointment-scheduler"
+ },
+ {
+ "dashedName": "review-bash-and-sql"
+ },
+ {
+ "dashedName": "quiz-bash-and-sql"
+ }
]
},
{
"dashedName": "nano",
"blocks": [
- { "dashedName": "workshop-castle" },
- { "dashedName": "review-nano" },
- { "dashedName": "quiz-nano" }
+ {
+ "dashedName": "workshop-castle"
+ },
+ {
+ "dashedName": "review-nano"
+ },
+ {
+ "dashedName": "quiz-nano"
+ }
]
},
{
"dashedName": "git",
"blocks": [
- { "dashedName": "workshop-sql-reference-object" },
- { "dashedName": "lab-periodic-table-database" },
- { "dashedName": "lab-number-guessing-game" },
- { "dashedName": "review-git" },
- { "dashedName": "quiz-git" }
+ {
+ "dashedName": "workshop-sql-reference-object"
+ },
+ {
+ "dashedName": "lab-periodic-table-database"
+ },
+ {
+ "dashedName": "lab-number-guessing-game"
+ },
+ {
+ "dashedName": "review-git"
+ },
+ {
+ "dashedName": "quiz-git"
+ }
]
},
{
"moduleType": "review",
"dashedName": "review-relational-databases",
- "blocks": [{ "dashedName": "review-relational-databases" }]
+ "blocks": [
+ {
+ "dashedName": "review-relational-databases"
+ }
+ ]
}
]
},
@@ -875,7 +1449,11 @@
{
"moduleType": "exam",
"dashedName": "exam-certified-full-stack-developer",
- "blocks": [{ "dashedName": "exam-certified-full-stack-developer" }]
+ "blocks": [
+ {
+ "dashedName": "exam-certified-full-stack-developer"
+ }
+ ]
}
]
}