As we are done with ๐ auth and private route setup . Let's continue further to complete frontend part of our project following the below requirements.
1. Create a Dashboard page pages/Dashboard.jsx
I wanted a dashboard that provides all my required components are displayed on the screen.
As, dashboard has to be a private route i.e, after login only user r allowed to access.
Fetching user details from AuthContext.
If the user is not available, provides a loading screen.
Displaying a dark-themed dashboard with a Navbar.
Showing a welcome message with user.name.
Providing three navigation links to different sections using a grid layout with appropriate styling.
import { useContext } from "react";
import { AuthContext } from "../context/AuthContext";
import { Link } from "react-router-dom";
import Navbar from "../components/Navbar";
const Dashboard = () => {
const { user } = useContext(AuthContext);
if (!user) {
return <div className="h-screen flex justify-center items-center text-white text-lg">Loading...</div>;
}
return (
<div className="min-h-screen bg-gray-900 text-white flex flex-col">
{/* Navbar */}
<Navbar />
{/* Dashboard Content */}
<div className="flex-grow flex flex-col items-center justify-center px-6">
<h1 className="text-3xl font-semibold mb-4">Welcome, {user.name}!</h1>
<p className="text-gray-400 mb-8">Manage your stock efficiently with real-time insights.</p>
{/* Dashboard Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8 w-full max-w-4xl">
<Link to="/products" className="p-6 bg-white/10 backdrop-blur-lg rounded-xl text-center hover:bg-white/20 transition">
<h2 className="text-xl font-semibold">Manage Products</h2>
</Link>
<Link to="/stock-overview" className="p-6 bg-white/10 backdrop-blur-lg rounded-xl text-center hover:bg-white/20 transition">
<h2 className="text-xl font-semibold">Stock Overview</h2>
</Link>
<Link to="/analytics" className="p-6 bg-white/10 backdrop-blur-lg rounded-xl text-center hover:bg-white/20 transition">
<h2 className="text-xl font-semibold">Analytics Dashboard</h2>
</Link>
</div>
</div>
</div>
);
};
export default Dashboard;
Let's implement the components that we defined above.
2. components/ProductManagement.jsx
- Setting up the required imports.
import { useEffect, useState } from "react"; //for statemanagement and sideeffects.
import { toast } from "react-toastify"; //for notification pop-ups
import { Trash2, Edit3 } from "lucide-react"; //Icons for delete and edit operations.
import api from "../utils/api"; //for api calls.
import Navbar from "./Navbar"; //navbar component
import { useNavigate } from "react-router-dom"; //React router navigation function.
- State Variables
- products: storing the list of products.
form: Managing product form data for adding/editing.
sellForm: Storing sales data.
showAddForm, showSellForm: Toggle forms for adding/selling products.
editingProduct: Stores the ID of the product being edited.
const [products, setProducts] = useState([]);
const [form, setForm] = useState({ name: "", category: "", price: "", quantityInStock: "" });
const [sellForm, setSellForm] = useState({ productId: "", quantity: "" });
const [showAddForm, setShowAddForm] = useState(false);
const [showSellForm, setShowSellForm] = useState(false);
const [editingProduct, setEditingProduct] = useState(null);
- Fetching the Products Fetching the products data when the component loads from the API,then updating the state with API response.
useEffect(() => {
fetchProducts();
}, []);
const fetchProducts = async () => {
try {
const { data } = await api.get("/products");
setProducts(data);
} catch (error) {
console.error(error);
toast.error("Failed to fetch products. Please try again.");
}
};
- Handling Input Changes
Updates form state dynamically as user types.
const handleChange = (e) => setForm({ ...form, [e.target.name]: e.target.value });
const handleSellChange = (e) => {
const { name, value } = e.target;
setSellForm((prev) => ({
...prev,
[name]: name === "quantity" ? Number(value) : value, // Ensures quantity is a number
}));
};
- Handling the product editing
const handleEdit = (product) => {
setEditingProduct(product._id); // Store product ID
setForm({ ...product }); // Populate form with product data
setShowAddForm(true); // Open form modal
};
- Adding or updating the products 1.If editingProduct exists โ Update product (PUT /products/:id).
2.Else โ Create new product (POST /products).
const handleSubmit = async (e) => {
e.preventDefault();
if (!form.name || !form.category || !form.price || !form.quantityInStock) {
toast.warn("Please fill in all fields before submitting.");
return;
}
try {
if (editingProduct) {
await api.put(`/products/${editingProduct}`, form);
toast.success("Product updated successfully");
setEditingProduct(null);
} else {
await api.post("/products", form);
toast.success("Product added successfully");
}
fetchProducts();
setForm({ name: "", category: "", price: "", quantityInStock: "" });
setShowAddForm(false);
} catch (error) {
toast.error(error.response?.data?.message || "Error adding product.");
}
};
- Selling a Product 1.Sends a sale request (POST /sales/sell).
2.Reduces stock count when a product is sold.
const handleSellSubmit = async (e) => {
e.preventDefault();
if (!sellForm.productId || !sellForm.quantity || sellForm.quantity <= 0) {
toast.warn("Please select a product and enter a valid quantity.");
return;
}
try {
await api.post("/sales/sell", sellForm);
toast.success("Product sold successfully! ๐ฐ");
fetchProducts();
setSellForm({ productId: "", quantity: "" });
setShowSellForm(false);
} catch (error) {
toast.error(error.response?.data?.message || "Error selling product.");
}
};
- Deleting a Product
const handleDelete = async (id) => {
try {
await api.delete(`/products/${id}`);
toast.success("Product deleted successfully! ๐๏ธ");
fetchProducts();
} catch (error) {
console.error(error);
toast.error("Error deleting product. Please try again.");
}
};
- CSV Import
- Allows users to upload a CSV file.
- Sends a multipart/form-data request to /csv/import-csv.
const handleImportCSV = async (e) => {
e.preventDefault();
const file = e.target.files[0];
if (!file) {
toast.warn("Please select a CSV file to import.");
return;
}
const formData = new FormData();
formData.append("file", file);
try {
await api.post("/csv/import-csv", formData, { headers: { "Content-Type": "multipart/form-data" } });
toast.success("CSV imported successfully! ๐");
fetchProducts();
} catch (error) {
console.error(error);
toast.error("Error importing CSV. Please check the file format, and duplicates before Import.");
}
};
- CSV Export 1.Fetches CSV data from /csv/export-csv.
2.downloads a file for the user.
const handleExportCSV = async () => {
try {
const response = await api.get("/csv/export-csv", { responseType: "blob" });
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "products.csv");
document.body.appendChild(link);
link.click();
toast.success("CSV exported successfully! ๐ค");
} catch (error) {
console.error(error);
toast.error("Error exporting CSV. Please try again.");
}
};
- Component Structure in return statement.
- Adding new products
Selling products
Importing/exporting CSV files
Editing and deleting products
Displaying a list of products
- Header Section Back to Dashboard button โ Navigates back to the dashboard.
Title: "Product Management" โ Displays the section title.
CSV Import & Export โ Allows importing a CSV file.
โ Exports the product list as a CSV.
{/* Header */}
<div className="flex items-center justify-between mb-6">
<button
onClick={() => navigate("/dashboard")}
className="flex items-center gap-2 bg-gray-600 hover:bg-gray-700 text-white py-2 px-4 rounded-lg shadow-md transition duration-300"
>
Back to Dashboard
</button>
<h1 className="text-2xl font-bold flex-grow text-center">Product Management</h1>
<div className="flex gap-4">
<input type="file" accept=".csv" onChange={handleImportCSV} className="hidden" id="importCSV" />
<label htmlFor="importCSV" className="bg-yellow-500 hover:bg-yellow-600 text-white py-2 px-4 rounded cursor-pointer">Import CSV</label>
<button onClick={handleExportCSV} className="bg-purple-500 hover:bg-purple-600 text-white py-2 px-4 rounded">Export CSV</button>
</div>
</div>
- Action Buttons 1."Add Product" Button โ Toggles the add/edit product form.
2."Sell Product" Button โ Toggles the sell product form.
{/* Action Buttons */}
<div className="flex gap-4 mb-4">
<button onClick={() => setShowAddForm(!showAddForm)} className="bg-green-500 hover:bg-green-600 text-white py-2 px-4 rounded-lg">
{showAddForm ? "Close" : "Add Product"}
</button>
<button onClick={() => setShowSellForm(!showSellForm)} className="bg-yellow-500 hover:bg-yellow-600 text-white py-2 px-4 rounded-lg">
{showSellForm ? "Close" : "Sell Product"}
</button>
</div>
- Add/Edit Product form 1.Displays a form when adding a new product or editing an existing one.
2.Fields:Product Name,Category,Price,Stock Quantity
3.Submit button โ Adds or updates the product.
4.Cancel button โ Closes the form and resets the state.
{/* Add Product Form */}
{showAddForm && (
<form onSubmit={handleSubmit} className="bg-gray-800 p-6 rounded-xl shadow-lg grid gap-4 w-full max-w-md mx-auto">
<h2 className="text-xl font-semibold text-white text-center">{editingProduct ? "Edit Product" : "Add New Product"}</h2>
<input type="text" name="name" placeholder="Product Name" value={form.name} onChange={handleChange} required className="p-3 bg-gray-700 text-white rounded-lg border border-gray-600" />
<input type="text" name="category" placeholder="Category" value={form.category} onChange={handleChange} required className="p-3 bg-gray-700 text-white rounded-lg border border-gray-600" />
<input type="number" name="price" placeholder="Price" value={form.price} onChange={handleChange} required className="p-3 bg-gray-700 text-white rounded-lg border border-gray-600" />
<input type="number" name="quantityInStock" placeholder="Stock Quantity" value={form.quantityInStock} onChange={handleChange} required className="p-3 bg-gray-700 text-white rounded-lg border border-gray-600" />
<div className="flex justify-between">
<button type="submit" className="bg-green-500 hover:bg-green-600 text-white py-2 px-4 rounded-lg">{editingProduct ? "Update Product" : "Add Product"}</button>
<button type="button" onClick={() => { setShowAddForm(false); setEditingProduct(null); }} className="bg-red-500 hover:bg-red-600 text-white py-2 px-4 rounded-lg">Cancel</button>
</div>
</form>
)}
- Sell Product form 1.Allows selecting a product and entering the quantity to sell.
2.Fields:
Dropdown: Selects a product from available ones.
Quantity input: Specifies the number of units to sell.
Submit button โ Processes the sale and updates stock.
{/* Sell Product Form */}
{showSellForm && (
<form onSubmit={handleSellSubmit} className="bg-gray-800 p-6 rounded-xl shadow-lg grid gap-4 mt-4 w-full max-w-md mx-auto">
<h2 className="text-xl font-semibold text-white text-center">Sell Product</h2>
<select name="productId" value={sellForm.productId} onChange={handleSellChange} required className="p-3 bg-gray-700 text-white rounded-lg border border-gray-600">
<option value="">Select Product</option>
{products.map((product) => (
<option key={product._id} value={product._id}>{product.name} (Stock: {product.quantityInStock})</option>
))}
</select>
<input type="number" name="quantity" placeholder="Enter Quantity" value={sellForm.quantity} onChange={handleSellChange} required className="p-3 bg-gray-700 text-white rounded-lg border border-gray-600" />
<button type="submit" className="bg-yellow-500 hover:bg-yellow-600 text-white py-2 px-4 rounded-lg">Sell Product</button>
</form>
)}
- Product List Table
1.Displays all available products in a table.
2.Columns:
Product Name, Category, Price, Stock Quantity, Actions
Edit button โ Opens the edit form with pre-filled details.
Delete button โ Deletes the selected product.
{/* Product List */}
<h3 className="text-xl font-bold mb-4 mt-6">Product List</h3>
<div className="bg-gray-800 p-4 rounded-lg shadow-md">
<table className="w-full text-left">
<thead>
<tr className="border-b border-gray-700 text-gray-300">
<th className="p-3">Product</th>
<th>Category</th>
<th>Price</th>
<th>Stock</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{products.map((product) => (
<tr key={product._id} className="border-b border-gray-700 text-gray-200">
<td className="p-3">{product.name}</td>
<td>{product.category}</td>
<td>${product.price}</td>
<td>{product.quantityInStock}</td>
<td className="flex gap-2">
<button onClick={() => handleEdit(product)} className="text-blue-400 hover:text-blue-600 p-2">
<Edit3 size={20} />
</button>
<button onClick={() => handleDelete(product._id)} className="text-red-500 hover:text-red-700 p-2">
<Trash2 size={20} />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
3. Stock Overview Component components/StockOverview.jsx
- Importing Dependencies
import { useEffect, useState } from "react"; //Manages state and API calls.
import { toast } from "react-toastify"; //Displays error messages if API calls fail.
import api from "../utils/api"; //Axios instance for API requests.
import { useNavigate } from "react-router-dom"; //Enables navigation between pages.
- Component Initialization & State Management
1.stockData โ Stores the stock details retrieved from the API.
loading โ Tracks whether data is still being fetched.
const navigate = useNavigate();
const [stockData, setStockData] = useState(null);
const [loading, setLoading] = useState(true);
- Fetching the Stock Data
useEffect(() => {
fetchStockOverview();
}, []);
- Fetching Stock Overview from API
1.Calls the API (/stock-overview) to retrieve stock details.
2.Handles errors (logs error + displays a toast notification).
3.Sets loading to false after fetching data.
const fetchStockOverview = async () => {
try {
const { data } = await api.get("/stock-overview");
setStockData(data);
} catch (error) {
console.error(error);
toast.error("Failed to fetch stock overview");
} finally {
setLoading(false);
}
};
- UI or Component Return Statement
1.Dashboard Navigation
<button
onClick={() => navigate("/dashboard")}
className="flex items-center gap-2 bg-gray-600 hover:bg-gray-700 text-white py-2 px-4 rounded-lg shadow-md transition duration-300"
>
<span>Back to Dashboard</span>
</button>
- Stock Overview section
<h2 className="text-3xl font-bold text-gray-100 mb-6 text-center w-full">
Stock Overview
</h2>
- Stock Data Card Displays 3 key metrics: Total Items, Total Sold, Total Revenue
Skeleton Loading Effect: While fetching data, it displays a placeholder using animate-pulse.
<div className="grid grid-cols-3 gap-6 text-center">
{loading ? (
[...Array(3)].map((_, i) => (
<div key={i} className="p-6 bg-gray-800 rounded-lg shadow-md animate-pulse">
<div className="h-6 bg-gray-700 rounded w-3/4 mx-auto mb-2"></div>
<div className="h-8 bg-gray-700 rounded w-1/2 mx-auto"></div>
</div>
))
) : (
<>
<div className="p-6 bg-gray-800 rounded-lg shadow-md">
<h3 className="text-lg font-semibold text-gray-300">Total Items</h3>
<p className="text-2xl font-bold text-white">{stockData.totalItems}</p>
</div>
<div className="p-6 bg-gray-800 rounded-lg shadow-md">
<h3 className="text-lg font-semibold text-gray-300">Total Sold</h3>
<p className="text-2xl font-bold text-white">{stockData.totalSold}</p>
</div>
<div className="p-6 bg-gray-800 rounded-lg shadow-md">
<h3 className="text-lg font-semibold text-gray-300">Total Revenue</h3>
<p className="text-2xl font-bold text-green-400">
${stockData.totalRevenue.toFixed(2)}
</p>
</div>
</>
)}
</div>
- Sold Items List
Displays sold items in a list.
Shows a skeleton loading effect if data is still being fetched.
Handles empty data gracefully (No items sold yet.)
<div className="mt-8 bg-gray-800 p-6 rounded-lg shadow-md">
<h3 className="text-xl font-bold text-gray-100 mb-4">Sold Items</h3>
<ul className="divide-y divide-gray-700">
{loading ? (
[...Array(5)].map((_, i) => (
<li key={i} className="p-4 flex justify-between animate-pulse">
<div className="h-4 bg-gray-700 rounded w-1/3"></div>
<div className="h-4 bg-gray-700 rounded w-1/4"></div>
<div className="h-4 bg-gray-700 rounded w-1/6"></div>
<div className="h-4 bg-gray-700 rounded w-1/6"></div>
</li>
))
) : stockData.soldItems.length > 0 ? (
stockData.soldItems.map((item, index) => (
<li key={index} className="p-4 flex justify-between">
<span className="font-semibold text-white">{item.name}</span>
<span className="text-gray-400">{item.category}</span>
<span className="text-green-500">Sold: {item.quantitySold}</span>
<span className="text-blue-400">
Revenue: ${item.revenueGenerated.toFixed(2)}
</span>
</li>
))
) : (
<p className="text-gray-500 text-center">No items sold yet.</p>
)}
</ul>
</div>
4. Analytics Component components/Analytics.jsx
- Imports and Initial Setup.
import { useEffect, useState } from "react";
import { Bar } from "react-chartjs-2"; //Registers bar chart components.
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from "chart.js";
import api from "../utils/api"; // Using baseURL configured API
import { useNavigate } from "react-router-dom";
- State Management
const [stockData, setStockData] = useState([]); //Stores fetched product stock data
const [chartData, setChartData] = useState(null); // Stores formatted data for Chart.js
const [category, setCategory] = useState(""); //Stores user-input filter for product category
const [sortBy, setSortBy] = useState("totalRevenue"); //Stores sorting criteria (totalRevenue or totalSold)
const [order, setOrder] = useState("desc"); //Determines sort order (asc or desc)
const [loading, setLoading] = useState(true); //Tracks data fetching state
const [error, setError] = useState(null); //Stores error messages
- Data fetching with UseEffect Calls fetchStockData and fetchChartData whenever category, sortBy, or order changes.
useEffect(() => {
fetchStockData();
fetchChartData();
}, [category, sortBy, order]);
- Fetching Stock Data 1.Sends API request to /analytics/stock to get stock details.
2.Filters products based on category (case-insensitive).
3.Updates stockData with filtered results.
const fetchStockData = async () => {
setLoading(true);
try {
const response = await api.get(`/analytics/stock`, {
params: { sortBy, order },
});
let filteredData = response.data;
// Apply case-insensitive filtering on the frontend
if (category.trim() !== "") {
const searchQuery = category.toLowerCase();
filteredData = filteredData.filter(product =>
product.category.toLowerCase().includes(searchQuery) ||
product.name.toLowerCase().includes(searchQuery)
);
}
setStockData(filteredData);
} catch (error) {
setError("Error fetching stock data");
console.error("Error fetching stock data:", error);
} finally {
setLoading(false);
}
};
- Fetching Chart Data
1.Calls API /analytics/chart-data to get total revenue & total sold per category.
2.Converts response into chartData for Chart.js bar graph.
const fetchChartData = async () => {
try {
const response = await api.get(`/analytics/chart-data`);
const data = response.data;
setChartData({
labels: data.map((item) => item.category),
datasets: [
{
label: "Total Revenue",
data: data.map((item) => item.totalRevenue || 0),
backgroundColor: "rgba(54, 162, 235, 0.6)",
},
{
label: "Total Sold",
data: data.map((item) => item.totalSold || 0),
backgroundColor: "rgba(255, 99, 132, 0.6)",
},
],
});
} catch (error) {
setError("Error fetching chart data");
console.error("Error fetching chart data:", error);
}
};
- UI Rendering
- Navigation Button
<button
onClick={() => navigate("/dashboard")}
className="flex items-center gap-2 bg-gray-600 hover:bg-gray-700 text-white py-2 px-4 rounded-lg shadow-md transition duration-300"
>
<span>Back to Dashboard</span>
</button>
- Filters and Sorting Options
- Text Input: Filters by category.
- Dropdowns: 1.Sort by (totalRevenue / totalSold). 2.Sort order (asc / desc).
<div className="flex gap-4 mb-4">
<input
type="text"
placeholder="Filter by category"
value={category}
onChange={(e) => setCategory(e.target.value)}
className="p-2 bg-gray-800 border border-gray-600 rounded"
/>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="p-2 bg-gray-800 border border-gray-600 rounded"
>
<option value="totalRevenue">Sort by Revenue</option>
<option value="totalSold">Sort by Items Sold</option>
</select>
<select
value={order}
onChange={(e) => setOrder(e.target.value)}
className="p-2 bg-gray-800 border border-gray-600 rounded"
>
<option value="desc">Descending</option>
<option value="asc">Ascending</option>
</select>
</div>
- Table for Stock Data
Displays product, category, sales count, and revenue.
Uses stockData to generate rows.
<table className="w-full border-collapse border border-gray-700">
<thead>
<tr className="bg-gray-800">
<th className="p-2 border border-gray-700">Product</th>
<th className="p-2 border border-gray-700">Category</th>
<th className="p-2 border border-gray-700">Items Sold</th>
<th className="p-2 border border-gray-700">Revenue</th>
</tr>
</thead>
<tbody>
{stockData.map((product) => (
<tr key={product._id} className="text-center bg-gray-700">
<td className="p-2 border border-gray-600">{product.name}</td>
<td className="p-2 border border-gray-600">{product.category}</td>
<td className="p-2 border border-gray-600">{product.itemsSold}</td>
<td className="p-2 border border-gray-600">
${product.totalRevenue ? product.totalRevenue.toFixed(2) : "0.00"}
</td>
</tr>
))}
</tbody>
</table>
- Chart.js Bar Chart
- If chartData exists, renders bar chart.
{chartData && (
<div className="mt-6 bg-gray-800 p-4 rounded-lg">
<h2 className="text-xl font-bold">Sales & Revenue Overview</h2>
<Bar data={chartData} />
</div>
)}
5. Landing Page pages/LandingPage.jsx
- Handling imports
import { useNavigate } from "react-router-dom";
import { ShoppingCart, BarChart3, FileText, LogIn } from "lucide-react";
import { AuthContext } from "../context/AuthContext";
import { useContext, useState } from "react";
- Component definition and AuthChecks
1.Extracts user and logout from AuthContext to manage authentication.
2.isOpen controls the profile dropdown visibility.
3.isAuthenticated checks if the user is logged in using localStorage token.
const LandingPage = () => {
const { user, logout } = useContext(AuthContext);
const [isOpen, setIsOpen] = useState(false);
const navigate = useNavigate();
const handleLogout = () => {
logout();
navigate("/");
setIsOpen(false);
};
const isAuthenticated = !!localStorage.getItem("token");
- Handling Profile Click Toggles the profile dropdown when clicked.
const handleProfileClick = async () => {
if (!isOpen) {
await user;
}
setIsOpen(!isOpen);
};
- Handling Navigation
const handleNavigation = (path) => {
navigate(isAuthenticated ? path : "/login");
};
- Landing page layout // Dark mode styling.
return (
<div className="min-h-screen bg-gray-900 text-white flex flex-col items-center justify-center px-6 relative">
- Navbar section 1.If the user is logged in, show a profile button.
2.Clicking Profile reveals a dropdown with name, email, and a logout button.
3.If not logged in, show Login & Register buttons.
{user ? (
<div className="absolute top-6 right-6">
<button onClick={handleProfileClick} className="px-4 py-2 bg-gray-700 bg-opacity-70 hover:bg-opacity-100 rounded-md transition">
Profile
</button>
{isOpen && (
<div className="absolute right-0 mt-2 w-56 bg-black text-gray-800 rounded-lg shadow-lg border border-white/20 transition-all duration-300">
<div className="p-4 border-b border-white/20">
<p className="font-semibold text-white">{user.name}</p>
<p className="text-sm text-gray-300">{user.email}</p>
</div>
<button onClick={handleLogout} className="block w-full text-left px-4 py-2 text-white hover:bg-white/20 rounded-b-lg transition">
Logout
</button>
</div>
)}
</div>
) : (
<div className="absolute top-6 right-6 flex gap-4">
<button onClick={() => navigate("/login")} className="text-white hover:text-blue-400 transition duration-300">Login</button>
<span className="text-gray-500">|</span>
<button onClick={() => navigate("/auth/register")} className="text-white hover:text-green-400 transition duration-300">Register</button>
</div>
)}
- Hero Section Title & Description for the landing page.
<header className="text-center max-w-3xl">
<h1 className="text-4xl font-bold mb-4">Stock Management System</h1>
<p className="text-gray-300">
A powerful tool to track inventory, manage stock levels, and generate reports efficiently.
</p>
</header>
- Features Section
1.Displays 3 feature cards (Product Management, Stock Overview, Analysis).
2.If user is not authenticated, an extra card for Secure Login appears.
<section className="mt-12 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 text-center">
{[
{
path: "/products",
icon: <ShoppingCart size={40} className="text-yellow-400 mx-auto mb-3" />,
title: "Product Management",
description: "Easily add, edit, and delete products in stock.",
},
{
path: "/stock-overview",
icon: <BarChart3 size={40} className="text-green-400 mx-auto mb-3" />,
title: "Stock Overview",
description: "Monitor available stock, items sold, and revenue trends.",
},
{
path: "/analytics",
icon: <FileText size={40} className="text-purple-400 mx-auto mb-3" />,
title: "Analysis",
description: "Generate reports in charts.",
},
].map((feature, index) => (
<div key={index} onClick={() => handleNavigation(feature.path)}
className="bg-gray-800 p-6 rounded-lg shadow-md cursor-pointer hover:bg-gray-700 transition duration-300">
{feature.icon}
<h3 className="text-xl font-semibold">{feature.title}</h3>
<p className="text-gray-400 mt-2">{feature.description}</p>
</div>
))}
{!isAuthenticated && (
<div onClick={() => navigate("/login")}
className="bg-gray-800 p-6 rounded-lg shadow-md cursor-pointer hover:bg-gray-700 transition duration-300">
<LogIn size={40} className="text-red-400 mx-auto mb-3" />
<h3 className="text-xl font-semibold">Secure Login</h3>
<p className="text-gray-400 mt-2">Ensure security with JWT-based authentication.</p>
</div>
)}
</section>
- Footer Section.
<footer className="mt-12 text-gray-500 text-center">
© {new Date().getFullYear()} Stock Management System By Shaik Reshma
</footer>
Checkout my project source code ๐github and the you'll find the deployed link, Test it out there.
This is all about frontend setup code and run the application npm run dev.For any queries react out in comment section.
Happy Developing๐
Let's grow together!
Top comments (0)