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" + } + ] } ] }