Technologies Used
**Frontend:
- React.js: Used to build the user interface and handle dynamic page rendering.
- Chakra UI: A component library that provides pre-built, accessible UI components, allowing for easy styling and responsive design.
- React Router: For navigating between pages (e.g., Home page, Create page).
- React Hooks: Specifically, useState, useEffect, and useContext are used to manage state, side effects, and application data flow.
- Backend:
- Node.js: Server-side runtime for handling API requests.
- Express.js: Web framework for Node.js to build APIs for product management (CRUD operations).
- MongoDB: NoSQL database used for storing product information (name, price, image, description).
- Additional Libraries:
- Chakra UI: For styling and UI components.
- React Icons: For adding icons like edit and delete.
- React Toastify: For showing toast notifications to alert the user after performing actions.
- Axios: To make HTTP requests for interacting with the backend API.
BACKEND SETUP
INTRODUCTION
Purpose of the Project: Introduce the project as a product store API that allows for managing a collection of products, including features like product listing, creating, updating, and deleting items.
Tech Stack: Mention that the backend is built with Express, Node.js, and MongoDB, with the use of dotenv for environment configuration, cors for cross-origin requests, and mongoose for handling database operations.
Dependencies and Project Structure
Dependencies: Briefly explain why each dependency is essential.
- express: For setting up server routes and handling requests.
- dotenv: For managing environment variables like database URIs.
- mongoose: For connecting to MongoDB and creating a data model.
- cors: To handle cross-origin requests, allowing the frontend to access backend APIs.
- cross-env: To set environment variables consistently across different operating systems.
- nodemon: For hot-reloading in development, making it easier to test changes.
- Folder Structure:
Highlight key folders and files like server.js for server logic, product.model.js for the MongoDB schema, dbConfig.js for the database connection, and product.route.js for routing requests.
Database Configuration``
dbConfig.js
Describe the connectDB function:
This function uses mongoose.connect() to connect to MongoDB, utilizing the MONGODB_URI from an .env file.
Error handling ensures the server exits if the database connection fails, maintaining server stability.
Environment Configuration: Mention the importance of using .env files to securely manage sensitive information like database URIs.
`
import mongoose from "mongoose";
export const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGODB_URI);
console.log(Connected to MongoDB :${conn.connection.host});
} catch (error) {
console.error(error : ${error.message});
process.exit(1);
}
};
`
Setting Up the Product Schema
Product Model (product.model.js)
Describe the Product schema using Mongoose:
Fields: Includes fields like name, price, image, and description, each with required validation.
Timestamps: The schema uses timestamps, which automatically adds createdAt and updatedAt fields, useful for tracking data changes.
`import mongoose from "mongoose";
const productSchema = new mongoose.Schema({
name: { type: String, required: true },
price: { type: Number, required: true },
image: { type: String, required: true },
description: { type: String, required: true },
}, {
timestamps: true,
});
const Product = mongoose.model("Product", productSchema);
export default Product;
`
Server Configuration and API Endpoints
Server Setup (index.js)
Explain how the server is initialized:
Express App: Creates an instance of Express, with middleware for parsing JSON and handling CORS.
Basic Route: A root route (/) to verify server health, which responds with a welcome message.
Product Routes: Attaches product-related routes (/api/products), allowing CRUD operations for product data.
Static File Serving: (For production) Configured to serve static files, ensuring smooth integration with frontend build files.
`import express from "express";
import dotenv from "dotenv";
import { connectDB } from "./config/db.js";
import router from "./routes/product.route.js";
import cors from "cors";
dotenv.config();
const app = express();
app.use(express.json());
app.use(cors());
app.get("/", (req, res) => {
res.send("hello krishna how are you");
});
app.use("/api/products", router);
app.listen(3000, () => {
connectDB();
console.log("Server is running on ",
newProduct
);
set((state) => ({ products: [...state.products, data.data] }));
return { success: true, message: "Product created successfully." };
},
fetchProducts: async () => {
const data = await axios.get("http://localhost:3000/api/products");
set({ products: data.data.data });
},
deleteProduct: async (pid) => {
try {
const { data } = await axios.delete(
http://localhost:3000/api/products/${pid}
);
set((state) => ({
products: state.products.filter((product) => product._id !== pid),
}));
return { success: true, message: data.message };
} catch (error) {
return {
success: false,
message: error.response?.data?.message || "Failed to delete product",
};
}
},
updateProduct: async (pid, updatedProduct) => {
try {
const response = await axios.put(
http://localhost:3000/api/products/${pid},
updatedProduct
);
const data = response.data;
if (!data.success) {
return { success: false, message: data.message || "Update failed." };
}
set((state) => ({
products: state.products.map((product) =>
product._id === pid ? data.data : product
),
}));
return { success: true, message: data.message || "Product updated successfully." };
} catch (error) {
return {
success: false,
message: error.response?.data?.message || "Failed to update product",
};
}
}
}));
`
Displaying Products on the Homepage
The Homepage component is responsible for rendering the list of products retrieved from the backend and displaying them in an attractive grid layout. Chakra UI's SimpleGrid component is used to manage the responsive grid of product cards, making it easy to adjust the layout for different screen sizes.
Fetching and Displaying Products
On page load, the component fetches products using the fetchProducts function from the Zustand store, which asynchronously retrieves the products from the backend.
`import { Container, VStack, Text, SimpleGrid } from "@chakra-ui/react";
import { Link } from "react-router-dom";
import React, { useEffect } from "react";
import { useProductStore } from "../store/product";
import ProductCard from "../components/ProductCard";
const Homepage = () => {
const { fetchProducts, products } = useProductStore();
useEffect(() => {
fetchProducts();
console.log(products);
}, [fetchProducts]);
return (
fontSize={"30"}
fontWeight={"bold"}
textTransform={"uppercase"}
bgClip={"text"}
bgGradient={"linear(to-r,cyan.400,blue.500)"}
>
Current Products 🛍️
<SimpleGrid
columns={{
base: 1,
md: 2,
lg: 3,
}}
spacing={10}
w={"full"}
>
{ products.map((product) => (
<ProductCard key={product._id} product={product} />
))}
</SimpleGrid>
{
products.length === 0 && (
<Text
fontSize="xl"
textAlign={"center"}
fontWeight={"bold"}
color={"gray.500"}
>
No products found 🥲
<Link to={"/create"}>
<Text
as={"span"}
color={"blue.500"}
_hover={{ textDecoration: "underline" }}
>
Create a new product
</Text>
</Link>
</Text>
)
}
</VStack>
</Container>
);
};
export default Homepage;
`
Navbar Component Breakdown
Main Features:
Branding: The store’s name is displayed using a gradient text, and clicking on it navigates to the homepage.
Product Creation Link: A button with a PlusSquareIcon is used to navigate to the product creation page (/create).
Color Mode Toggle: A button toggles between light and dark themes using Chakra UI's useColorMode hook.
Product Count: Although not visible in the current code, you have access to the list of products (useProductStore), which can be used to display product counts or other related information.
Chakra UI Styling:
Flexbox Layout: The layout of the navbar is designed using Chakra UI’s Flex component. It adjusts for different screen sizes (base and sm breakpoints), stacking the elements in a column on smaller screens (mobile-first design).
Responsive Design: The text size changes based on the screen size, and the button layout changes from row (for larger screens) to column (for smaller screens).
`import React from "react";
import {
Container,
Flex,
Text,
HStack,
Button,
useColorMode,
useColorModeValue,
} from "@chakra-ui/react";
// import { FaPlusSquare } from "react-icons/fa";
import { Link } from "react-router-dom";
import { PlusSquareIcon } from "@chakra-ui/icons";
import {IoMoon} from "react-icons/io5"
import {LuSun} from "react-icons/lu"
import { useProductStore } from "../store/product";
const Navbar = () => {
const { colorMode, toggleColorMode } = useColorMode();
const {products}=useProductStore()
return (
h={16}
alignItems={"center"}
justifyContent={"space-between"}
flexDirection={{
base: "column",
sm: "row",
}}
>
fontSize={{
base: "22",
sm: "28",
}}
fontWeight={"bold"}
textTransform={"uppercase"}
bgClip={"text"}
bgGradient={"linear(to-r,cyan.400,blue.500)"}
>
Common Store 🛒
<HStack spacing={2} alignItems={"center"}>
<Link to={"/create"}>
<Button>
<PlusSquareIcon fontSize={20} />
</Button>
</Link>
<Button onClick={toggleColorMode}>
{colorMode === "light" ? <IoMoon/> : <LuSun/>}
</Button>
</HStack>
</Flex>
</Container>
);
};
export default Navbar;
`
Creating a New Product
The CreatePage component is responsible for allowing users to add new products to the store. It contains a form with input fields for the product's name, price, image, and description. When the form is submitted, it sends the new product data to the backend via the createProduct function, which is managed by Zustand. Here's a breakdown of how it works:
State Management for New Product
The component uses React's useState hook to manage the input fields for the new product. The newProduct object holds the values for name, price, image, and description, which are updated as the user types in the input fields.
const [newProduct, setNewProduct] = useState({
name: "",
price: "",
image: "",
description: "",
});
- newProduct: The state object storing the new product data.
- setNewProduct: The function used to update the state when the user types in the form fields.
Handling Product Creation
When the user clicks the "Add product" button, the handleAddProduct function is called. This function sends the newProduct data to the createProduct function from Zustand, which handles the API request to add the new product.
const handleAddProduct = async () => {
const { success, message } = await createProduct(newProduct);
if (success) {
toast({
title: "Product created",
description: message,
status: "success",
duration: 2000,
});
} else {
toast({
title: "Error",
description: message,
status: "error",
duration: 2000,
});
}
};
- createProduct: This function from the Zustand store makes a POST request to the backend API to add the product.
- toast: A Chakra UI Toast notification is displayed based on whether the product creation was successful or not.
Resetting Form After Submission
After the product is created successfully or if there is an error, the form fields are reset by updating the newProduct state to its initial empty values.
setNewProduct({
name: "",
price: "",
image: "",
description: "",
});
Form UI
The form uses Chakra UI components for a clean and responsive layout. The input fields are styled with Chakra UI's Input component, and the form buttons are styled with the Button component.
<VStack spacing={4}>
<Input
placeholder="Product Name"
name="name"
value={newProduct.name}
onChange={(e) =>
setNewProduct({ ...newProduct, name: e.target.value })
}
/>
<Input
placeholder="Product Price"
name="price"
value={newProduct.price}
onChange={(e) =>
setNewProduct({ ...newProduct, price: e.target.value })
}
/>
<Input
placeholder="Product Image"
name="image"
value={newProduct.image}
onChange={(e) =>
setNewProduct({ ...newProduct, image: e.target.value })
}
/>
<Input
placeholder="Product Description"
name="description"
value={newProduct.description}
onChange={(e) =>
setNewProduct({ ...newProduct, description: e.target.value })
}
/>
<Button colorScheme={"blue"} onClick={handleAddProduct}>
Add product
</Button>
</VStack>
- Input: Each input field is controlled using React’s value prop, which is bound to the corresponding property in the newProduct state.
- Button: The button triggers the handleAddProduct function, which handles the product creation process.
Navigation and UI Design
The Back to Home button uses React Router's Link component to navigate the user back to the homepage after they create a product.
Chakra UI's VStack and Box components are used for vertical stacking and layout, and useColorModeValue is used to manage light/dark theme styling.
<Link to={"/"}>
<Button colorScheme={"gray"} mt={4}>
Back to Home
</Button>
</Link>
ProductCard Component Breakdown
Main Features:
View Product Details: Displays product information like name, price, and image, providing a clear view of the product.
Edit Product: Opens a modal where users can update the product's name, price, image, and description.
Delete Product: Allows users to delete the product with a confirmation toast message.
Responsive Design: Uses Chakra UI components for responsive design, making it mobile-friendly and adaptable to different screen sizes.
Toast Notifications: Provides feedback to the user with success or error messages after performing actions like update or delete.
`import {
Box,
Heading,
HStack,
IconButton,
Image,
Modal,
ModalBody,
ModalCloseButton,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
Text,
useColorModeValue,
useDisclosure,
useToast,
VStack,
Input,
Button,
} from "@chakra-ui/react";
import { DeleteIcon, EditIcon } from "@chakra-ui/icons";
import React, { useState } from "react";
import { useProductStore } from "../store/product";
const ProductCard = ({ product }) => {
const textColor = useColorModeValue("gray.600", "gray.200");
const bg = useColorModeValue("white", "gray.800");
const { deleteProduct, updateProduct } = useProductStore();
const { isOpen, onClose, onOpen } = useDisclosure();
const toast = useToast();
const [updatedProduct, setUpdatedProduct] = useState(product);
const handleDeleteProduct = async (pid) => {
const { success, message } = await deleteProduct(pid);
toast({
title: success ? "Product deleted" : "Error deleting product",
description: message,
status: success ? "success" : "error",
duration: 2000,
isClosable: true,
});
};
const handleUpdateProduct = async (pid, updatedProduct) => {
const { success, message } = await updateProduct(pid, updatedProduct);
toast({
title: success ? "Product updated" : "Error updating product",
description: message,
status: success ? "success" : "error",
duration: 2000,
isClosable: true,
});
if (success) onClose();
};
return (
shadow="lg"
rounded="lg"
overflow="hidden"
transform="all 0.3s"
_hover={{ transform: "translateY(-5px)", shadow: "xl" }}
bg={bg}
>
{product.name}
{product.price}
} onClick={onOpen} colorScheme="blue" />
icon={}
colorScheme="red"
onClick={() => handleDeleteProduct(product._id)}
/>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Update Product</ModalHeader>
<ModalCloseButton />
<ModalBody>
<VStack spacing={4}>
<Input
placeholder="Product Name"
name="name"
value={updatedProduct.name || ""}
onChange={(e) =>
setUpdatedProduct((prev) => ({ ...prev, name: e.target.value }))
}
/>
<Input
placeholder="Product Price"
name="price"
value={updatedProduct.price || ""}
onChange={(e) =>
setUpdatedProduct((prev) => ({ ...prev, price: e.target.value }))
}
/>
<Input
placeholder="Product Image"
name="image"
value={updatedProduct.image || ""}
onChange={(e) =>
setUpdatedProduct((prev) => ({ ...prev, image: e.target.value }))
}
/>
<Input
placeholder="Product Description"
name="description"
value={updatedProduct.description || ""}
onChange={(e) =>
setUpdatedProduct((prev) => ({ ...prev, description: e.target.value }))
}
/>
</VStack>
</ModalBody>
<ModalFooter>
<Button
colorScheme="blue"
mr={3}
onClick={() => handleUpdateProduct(product._id, updatedProduct)}
>
Update
</Button>
<Button colorScheme="gray" onClick={onClose}>
Cancel
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</Box>
);
};
export default ProductCard;
`
Flow of the Application
- Homepage (View Products):
- When the app loads, the Homepage fetches the list of products from the server via an API call.
- Products are displayed in a grid, and users can interact with them via buttons to edit or delete.
- If no products exist, a message is displayed with a link to create a new product.
- Create New Product:
- Users can navigate to the CreatePage, where they fill in a form with product details (name, price, image, description).
- After submitting the form, the app makes a POST request to the backend to save the new product in the database.
- On success, a success message appears via a toast notification.
- Edit and Delete Product:
- Each product displayed on the homepage has options to edit or delete.
- When the user clicks edit, a modal opens pre-filled with the current product details. The user can update the fields and submit the changes.
- When the user clicks delete, a delete request is made to the backend, and the product is removed from the database. A toast notification confirms the action.
SOCIAL SHARE CARD GENERATOR