Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/main.js

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions docs/main.js.LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @license React
* react-dom-client.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* @license React
* react-dom.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* @license React
* react.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* @license React
* scheduler.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "webpack --mode production",
"start-dev": "webpack serve --mode development --open"
"build": "webpack --mode production",
"start": "webpack serve --mode development --open"
},
"keywords": [],
"author": "",
Expand Down
19 changes: 16 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import React from "react";
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import "./style.css";
import AddBook from "./components/AddBook";
import BookList from "./components/BookList";

const App = () => {
// State to keep track of all added books
const [books, setBooks] = useState([]);

// Function to add a new book to the books array
const handleAddBook = (newBook) => {
setBooks([...books, newBook]);
};

return (
<div className="app">
{/* Main title of the app */}
<h1 className="title">React Forms! 📝</h1>
<AddBook />
<BookList />

{/* AddBook component to hadle adding new books */}
<AddBook onAddBook={handleAddBook} />

{/* BookList to display the list of books */}
<BookList books={books} />
</div>
);
};
Expand Down
172 changes: 155 additions & 17 deletions src/components/AddBook.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,158 @@
import React from "react";

/**
* A book should have the following fields:
* - title (required)
* - author (required)
* - image (optional, url)
* - publishedDate (optional, datetime)
* - description (optional, text)
* - rating (number, 1-5)
* - category (optional, dropdown with options: fiction, non-fiction, poetry, drama, biography, history, science, technology, art, music, travel, cooking, gardening, etc.)
* - isRead (boolean, default false)
* - isFavorite (boolean, default false)
*/

const AddBook = () => {
return <div>AddBook</div>;
import React, { useState } from "react";

const AddBook = ({ onAddBook }) => {
const [formData, setFormData] = useState({
title: "",
author: "",
image: "",
publishedDate: "",
description: "",
rating: "",
category: "",
isRead: false,
isFavorite: false,
});

const [errors, setErrors] = useState([]);

const categories = [
"fiction", "non-fiction", "poetry", "drama", "biography",
"history", "science", "technology", "art", "music",
"travel", "cooking", "gardening"
];

const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData({
...formData,
[name]: type === "checkbox" ? checked : value
});
};

const validate = () => {
const newErrors = [];
if (!formData.title.trim()) newErrors.push("Title is required.");
if (!formData.author.trim()) newErrors.push("Author is required.");
if (formData.rating) {
const num = Number(formData.rating);
if (isNaN(num) || num < 1 || num > 5) {
newErrors.push("Rating must be a number between 1 and 5.");
}
}
setErrors(newErrors);
return newErrors.length === 0;
};

const handleSubmit = (e) => {
e.preventDefault();
if (!validate()) return;
onAddBook(formData);
// Reset form
setFormData({
title: "",
author: "",
image: "",
publishedDate: "",
description: "",
rating: "",
category: "",
isRead: false,
isFavorite: false,
});
};

return (
<form onSubmit={handleSubmit}>
<h2>Add a Book</h2>

{errors.length > 0 && (
<ul style={{ color: "red" }}>
{errors.map((err, idx) => <li key={idx}>{err}</li>)}
</ul>
)}

<input
type="text"
name="title"
placeholder="Title"
value={formData.title}
onChange={handleChange}
required
/>

<input
type="text"
name="author"
placeholder="Author"
value={formData.author}
onChange={handleChange}
required
/>

<input
type="url"
name="image"
placeholder="Image URL"
value={formData.image}
onChange={handleChange}
/>

<input
type="date"
name="publishedDate"
value={formData.publishedDate}
onChange={handleChange}
/>

<textarea
name="description"
placeholder="Description"
value={formData.description}
onChange={handleChange}
/>

<input
type="number"
name="rating"
placeholder="Rating (1-5)"
value={formData.rating}
onChange={handleChange}
/>

<select
name="category"
value={formData.category}
onChange={handleChange}
>
<option value="">Select category</option>
{categories.map((cat) => (
<option key={cat} value={cat}>{cat}</option>
))}
</select>

<label>
<input
type="checkbox"
name="isRead"
checked={formData.isRead}
onChange={handleChange}
/>
Read
</label>

<label>
<input
type="checkbox"
name="isFavorite"
checked={formData.isFavorite}
onChange={handleChange}
/>
Favorite
</label>

<button type="submit">Add Book</button>
</form>
);
};

export default AddBook;
44 changes: 42 additions & 2 deletions src/components/BookCard.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,47 @@
import React from "react";

const BookCard = () => {
return <div>BookCard</div>;
// BookCard component receives a single 'book' object as a prop
const BookCard = ({ book }) => {
return (
// Container div with some simple inline styling for border and spacing
<div className="book-card" style={{ border: "1px solid #ccc", padding: "1rem", marginBottom: "1rem" }}>

{/* If book.image exists, display the book cover */}
{book.image && (
<img
src={book.image}
alt={`${book.title} cover`} // alt text for accessibility
style={{ width: "100px", height: "auto", marginBottom: "0.5rem" }}
/>
)}

{/* Display the book title */}
<h3>{book.title}</h3>

{/* Display the author */}
<p><strong>Author:</strong> {book.author}</p>

{/* Display published date if it exists, formatted nicely */}
{book.publishedDate && (
<p><strong>Published:</strong> {new Date(book.publishedDate).toLocaleDateString()}</p>
)}

{/* Display description if it exists */}
{book.description && <p>{book.description}</p>}

{/* Display rating if it exists */}
{book.rating && <p><strong>Rating:</strong> {book.rating} / 5</p>}

{/* Display category if it exists */}
{book.category && <p><strong>Category:</strong> {book.category}</p>}

{/* Show if the book is read or not */}
<p><strong>Read:</strong> {book.isRead ? "Yes" : "No"}</p>

{/* Show if the book is marked as favorite */}
<p><strong>Favorite:</strong> {book.isFavorite ? "Yes" : "No"}</p>
</div>
);
};

export default BookCard;
20 changes: 18 additions & 2 deletions src/components/BookList.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import React from "react";
import BookCard from "./BookCard";

const BookList = () => {
return <div>BookList</div>;
const BookList = ({ books }) => {
return (
<div className="book-list">
{/* Heading for the book list section */}
<h2>Book List</h2>

{/* If no books, show a message */}
{books.length === 0 ? (
<p>No books added yet.</p>
) : (
/* Otherwise, map through books a BookCrd for each */
books.map((book, index) => (
<BookCard key={index} book={book} />
))
)}
</div>
);
};

export default BookList;
Loading