feat(curriculum): add labs-react-forms (#59320)

Co-authored-by: Jessica Wilkins <67210629+jdwilkin4@users.noreply.github.com>
Co-authored-by: Tom <20648924+moT01@users.noreply.github.com>
This commit is contained in:
Shaun Hamilton
2025-04-18 19:26:30 +02:00
committed by GitHub
parent a3fff8e66f
commit 4b3f04f12f
5 changed files with 1913 additions and 312 deletions

View File

@@ -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": [""]

View File

@@ -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 <code>React.useState</code> to manage a form in a React component.

View File

@@ -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"
}

View File

@@ -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
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Event RSVP</title>
<link rel="stylesheet" href="styles.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.development.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.development.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.26.5/babel.min.js"></script>
<script
data-plugins="transform-modules-umd"
type="text/babel"
src="index.jsx"
></script>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div id="root"></div>
<script
data-plugins="transform-modules-umd"
type="text/babel"
data-presets="react"
data-type="module"
>
import { EventRSVPForm } from './index.jsx';
ReactDOM.createRoot(document.getElementById('root')).render(
<EventRSVPForm />
);
</script>
</body>
</html>
```
```css
```
```jsx
const { useState } = React;
export function EventRSVPForm() {}
```
# --solutions--
```html
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Event RSVP</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.development.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.development.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.26.5/babel.min.js"></script>
<script
data-plugins="transform-modules-umd"
type="text/babel"
src="index.jsx"
></script>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div id="root"></div>
<script
data-plugins="transform-modules-umd"
type="text/babel"
data-presets="react"
data-type="module"
>
import { EventRSVPForm } from './index.jsx';
ReactDOM.createRoot(document.getElementById('root')).render(
<EventRSVPForm />
);
</script>
</body>
</html>
```
```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 (
<div className='form-container'>
<h2>Event RSVP Form</h2>
<form onSubmit={handleSubmit}>
<div className='form-group'>
<label>
Name:
<input
type='text'
name='name'
value={formData.name}
onChange={handleChange}
placeholder='Your Name'
required
/>
</label>
</div>
<div className='form-group'>
<label>
Email:
<input
type='email'
name='email'
value={formData.email}
onChange={handleChange}
placeholder='Your Email'
required
/>
</label>
</div>
<div className='form-group'>
<label>
Number of Attendees:
<input
type='number'
name='attendees'
value={formData.attendees || ''}
onChange={handleChange}
min='1'
placeholder='Number of Attendees'
required
/>
</label>
</div>
<div className='form-group'>
<label>
Dietary Preferences:
<input
type='text'
name='dietaryPreferences'
value={formData.dietaryPreferences}
onChange={handleChange}
placeholder='Dietary Preferences (Optional)'
/>
</label>
</div>
<div className='form-group'>
<label>
Bringing additional guests?
<input
type='checkbox'
name='bringingOthers'
checked={formData.bringingOthers}
onChange={handleChange}
/>
</label>
</div>
<button type='submit'>Submit RSVP</button>
</form>
{submitted && (
<div className='submitted-message'>
<h3>RSVP Submitted!</h3>
<p>
<strong>Name:</strong> {formData.name}
</p>
<p>
<strong>Email:</strong> {formData.email}
</p>
<p>
<strong>Number of Attendees:</strong> {formData.attendees}
</p>
<p>
<strong>Dietary Preferences:</strong>{' '}
{formData.dietaryPreferences || 'None'}
</p>
<p>
<strong>Bringing Others:</strong>{' '}
{formData.bringingOthers ? 'Yes' : 'No'}
</p>
</div>
)}
</div>
);
}
```

File diff suppressed because it is too large Load Diff