Capstone -3 Folder adde

master
Ron Reciproco 11 months ago
parent a05b76ecd2
commit e6dac5deee

@ -0,0 +1 @@
Subproject commit b54176304080ae526f9aaa954701bb443d7d4ecc

@ -0,0 +1,2 @@
REACT_APP_API_URL=http://localhost:4000
SECRET_SAUCE=cornhub

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
The page will reload when you make changes.\
You may also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

File diff suppressed because it is too large Load Diff

@ -0,0 +1,56 @@
{
"name": "csp3-reciproco",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.3",
"bootstrap": "^5.3.2",
"bootstrap-icons": "^1.11.2",
"jwt-decode": "^4.0.0",
"react": "^18.2.0",
"react-bootstrap": "^2.9.1",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.11",
"react-icons": "^4.12.0",
"react-redux": "^9.0.4",
"react-responsive": "^9.0.2",
"react-router-dom": "^6.20.1",
"react-scripts": "5.0.1",
"react-slick": "^0.29.0",
"react-toastify": "^9.1.3",
"redux": "^5.0.1",
"sass": "^1.69.5",
"slick-carousel": "^1.8.1",
"typeface-montserrat": "^1.1.13",
"typeface-roboto": "^1.1.13",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"prestart": "sass src/_custom-bootstrap-theme.scss:src/_custom-bootstrap-theme.css --no-source-map"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.18.0/font/bootstrap-icons.css" integrity="sha384-oXqNBxTFd42y8S9ZXyoUQ5dAQz0x1Sk0zE8PC+R2Q/kT0WllMzBTYM+no2XH2BwF" crossorigin="anonymous">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

@ -0,0 +1,35 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Roboto', sans-serif;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Montserrat', sans-serif;
/* color: #1fcad4; /* Primary color for headlines */
} */
p {
color: #333; /* Dark gray color for paragraphs */
}
/* Add this style in your CSS file or style section */
.button-style {
background-color: #1fcad4; /* Primary color for the button */
color: #fff; /* White text color */
border: none; /* Remove border */
padding: 10px 20px; /* Add padding */
font-size: 16px; /* Set font size */
cursor: pointer; /* Add a pointer cursor on hover */
border-radius: 5px; /* Add a slight border radius */
transition: background-color 0.3s ease; /* Smooth transition on background color change */
/* Hover effect */
&:hover {
background-color: #149094; /* Darker shade on hover */
}
}

@ -0,0 +1,122 @@
import React, { useState, useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
// import { Container } from 'react-bootstrap';
import { ErrorBoundary } from 'react-error-boundary';
import NavBar from './components/NavBar';
import Home from './pages/Home';
import About from './pages/About';
import Login from './pages/Login';
import Logout from './pages/Logout';
import Profile from './pages/Profile';
import Register from './pages/Register';
import CartPage from './pages/CartPage';
import TokenExpired from './pages/TokenExpired';
import TokenChecker from './components/TokenChecker';
import Banner from './components/Banner';
import AnnouncementBar from './components/Announcement-bar';
import UpdateProfile from './components/UpdateProfile';
import AdminDashboard from './pages/AdminDashboard';
import ProductPage from './pages/ProductPage';
import CheckoutPage from './pages/CheckoutPage';
import Error from './pages/Error'; // Import the NotFound component
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { UserProvider } from './UserContext';
function ErrorFallback({ error }) {
return (
<div>
<h1>Something went wrong!</h1>
<p>{error.message}</p>
</div>
);
}
function App() {
const [user, setUser] = useState({
id: null,
isAdmin: null,
isVerified: true,
});
const unsetUser = () => {
localStorage.clear();
};
useEffect(() => {
const checkTokenExpiration = async () => {
const token = localStorage.getItem('token');
if (token) {
try {
const response = await fetch(`${process.env.REACT_APP_API_URL}/user/details`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.ok) {
const data = await response.json();
if (typeof data._id !== 'undefined') {
setUser({
id: data._id,
isAdmin: data.isAdmin,
});
} else {
setUser({
id: null,
isAdmin: null,
});
unsetUser();
}
} else {
// Handle non-OK responses
console.error('Non-OK response:', response.status, response.statusText);
}
} catch (error) {
// Handle fetch errors
console.error('Fetch error:', error);
}
}
};
checkTokenExpiration();
}, []);
return (
<UserProvider value={{ user, setUser, unsetUser }}>
<Router>
<TokenChecker />
<ErrorBoundary FallbackComponent={ErrorFallback}>
<AnnouncementBar />
<NavBar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/token-expired" element={<TokenExpired />} />
<Route path="/register" element={<Register />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={<AdminDashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/logout" element={<Logout />} />
<Route path="/about" element={<About />} />
<Route path="/banner" element={<Banner />} />
<Route path="/cart" element={<CartPage />} />
<Route path="/update-profile" element={<UpdateProfile />} />
<Route path="/404" element={<Error />} />
<Route path="/product/:productId" element={<ProductPage />} />
<Route path="/checkout" element={<CheckoutPage />} />
{/* Catch-all route that redirects to the home page */}
<Route path="*" element={<Navigate to="/404" />} />
</Routes>
<ToastContainer />
</ErrorBoundary>
</Router>
</UserProvider>
);
}
export default App;

@ -0,0 +1,90 @@
import React, { createContext, useContext, useReducer, useEffect } from 'react';
// Define the initial state and reducer
const initialState = {
cart: [],
cartDetails: null,
};
const cartReducer = (state, action) => {
switch (action.type) {
case 'SET_CART':
return { ...state, cart: action.payload };
case 'SET_CART_DETAILS':
return { ...state, cartDetails: action.payload };
// Add other cases as needed
default:
return state;
}
};
// Create the context
const CartContext = createContext();
// Create the provider component
export const CartProvider = ({ children }) => {
const [state, dispatch] = useReducer(cartReducer, initialState);
// Action to set the cart data
const setCart = (cartData) => {
dispatch({ type: 'SET_CART', payload: cartData });
};
// Action to set cart details
const setCartDetails = (details) => {
dispatch({ type: 'SET_CART_DETAILS', payload: details });
};
// Add other actions as needed
// Fetch cart details from the server
// Fetch cart details from the server
const fetchCartDetails = async () => {
try {
const token = localStorage.getItem('token');
const response = await fetch(`${process.env.REACT_APP_API_URL}/cart/cart-details`, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
if (response.ok) {
setCart(data.items); // Assuming the response has an 'items' array
setCartDetails(data); // Set the cart details
}
} catch (error) {
console.error('Error fetching cart details:', error);
}
};
// Fetch cart details when the component mounts or when the user changes
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
fetchCartDetails();
}
}, []); // The empty dependency array ensures that this effect runs only once when the component mounts
return (
<CartContext.Provider value={{ state, setCart, setCartDetails, fetchCartDetails }}>
{children}
</CartContext.Provider>
);
};
// Custom hook to use the cart context
export const useCart = () => {
const context = useContext(CartContext);
if (!context) {
throw new Error('useCart must be used within a CartProvider');
}
return context;
};

@ -0,0 +1,11 @@
import React from 'react';
// Creates a Context object
// A context object as the name states is a data type of an object that can be used to store information that can be shared to other components within the app
// The context object is a different approach to passing information between components and allows easier access by avoiding the use of prop-drilling
const UserContext = React.createContext();
// The "Provider" component allows other components to consume/use the context object and supply the necessary information needed to the context object
export const UserProvider = UserContext.Provider;
export default UserContext;

@ -0,0 +1,4 @@
// src/_custom-bootstrap-theme.scss
// $primary: #1fcad4; // Your primary color
// $secondary: #f5f5f5; // Your secondary color

@ -0,0 +1,15 @@
// src/_custom-styles.scss
// Your custom styles go here
body {
margin: 0;
padding: 0;
}
.custom-container {
max-width: 1200px;
margin: 0 auto;
}
// Add more styles as needed

@ -0,0 +1,86 @@
// AddProductForm.js
import React, { useState } from 'react';
import { Form, Button } from 'react-bootstrap';
const AddProductForm = ({ onSave }) => {
const [product, setProduct] = useState({
name: '',
description: '',
price: 0,
tags: '',
});
const handleInputChange = (e) => {
const { name, value } = e.target;
setProduct((prevProduct) => ({
...prevProduct,
[name]: value,
}));
};
const handleSave = () => {
onSave(product);
setProduct({
name: '',
description: '',
price: 0,
tags: '',
});
};
return (
<Form>
<Form.Group controlId="formName">
<Form.Label>Name</Form.Label>
<Form.Control
type="text"
placeholder="Enter product name"
name="name"
value={product.name}
onChange={handleInputChange}
/>
</Form.Group>
<Form.Group controlId="formDescription">
<Form.Label>Description</Form.Label>
<Form.Control
as="textarea"
rows={3}
placeholder="Enter product description"
name="description"
value={product.description}
onChange={handleInputChange}
/>
</Form.Group>
<Form.Group controlId="formPrice">
<Form.Label>Price</Form.Label>
<Form.Control
type="number"
placeholder="Enter product price"
name="price"
value={product.price}
onChange={handleInputChange}
/>
</Form.Group>
<Form.Group controlId="formTags">
<Form.Label>Tags</Form.Label>
<Form.Control
type="text"
placeholder="Enter product tags"
name="tags"
value={product.tags}
onChange={handleInputChange}
/>
</Form.Group>
<Button variant="primary" onClick={handleSave}>
Save
</Button>
</Form>
);
};
export default AddProductForm;

@ -0,0 +1,516 @@
// src/components/AllProducts.js
import React, { useState, useEffect, useContext, useRef } from "react";
import {
Table,
Spinner,
Alert,
Button,
Modal,
Dropdown,
} from "react-bootstrap";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import UserContext from "../UserContext";
const AllProducts = React.forwardRef((props, ref) => {
const { user } = useContext(UserContext);
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [productIdToDelete, setProductIdToDelete] = useState(null);
const [refreshProducts, setRefreshProducts] = useState(false);
const [filter, setFilter] = useState("all"); // 'all', 'available', 'notAvailable'
const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 4;
const prevRefreshRef = useRef();
useEffect(() => {
prevRefreshRef.current = refreshProducts;
}, [refreshProducts]);
const refreshRef = useRef();
refreshRef.current = refreshProducts;
useEffect(() => {
const fetchProducts = async () => {
try {
if (user && user.id) {
const token = localStorage.getItem("token");
const response = await fetch(
`${process.env.REACT_APP_API_URL}/product/all`,
{
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const data = await response.json();
if (response.ok) {
setProducts(data);
setError(null);
} else {
setError("Failed to fetch products");
}
}
} catch (error) {
console.error("Error fetching products:", error);
setError("An unexpected error occurred");
} finally {
setLoading(false);
}
};
fetchProducts();
}, [user, refreshRef.current]);
const handleDelete = async (productId) => {
try {
const token = localStorage.getItem("token");
const response = await fetch(
`${process.env.REACT_APP_API_URL}/product/delete/${productId}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (response.ok) {
setProducts((prevProducts) =>
prevProducts.filter((product) => product._id !== productId)
);
setError(null);
toast.success("Product deleted successfully", {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
setRefreshProducts((prev) => !prev);
} else {
const data = await response.json();
setError(data.message || "Failed to delete product");
toast.error("Failed to delete product");
}
} catch (error) {
console.error("Error deleting product:", error);
setError("An unexpected error occurred");
toast.error("An unexpected error occurred");
} finally {
setShowDeleteModal(false);
}
};
const handleSetAvailability = async (productId, isActive) => {
try {
const token = localStorage.getItem("token");
const endpoint = isActive ? "activate" : "archive";
const response = await fetch(
`${process.env.REACT_APP_API_URL}/product/${productId}/${endpoint}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
}
);
if (response.ok) {
setProducts((prevProducts) =>
prevProducts.map((product) =>
product._id === productId ? { ...product, isActive } : product
)
);
setError(null);
toast.success(
`Product ${isActive ? "activated" : "archived"} successfully`,
{
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
}
);
setRefreshProducts((prev) => !prev);
} else {
const data = await response.json();
setError(
data.message ||
`Failed to ${isActive ? "activate" : "archive"} product`
);
toast.error(`Failed to ${isActive ? "activate" : "archive"} product`);
}
} catch (error) {
console.error(
`Error ${isActive ? "activating" : "archiving"} product:`,
error
);
setError("An unexpected error occurred");
toast.error("An unexpected error occurred");
}
};
const openDeleteModal = (productId) => {
setProductIdToDelete(productId);
setShowDeleteModal(true);
};
const closeDeleteModal = () => {
setShowDeleteModal(false);
setProductIdToDelete(null);
};
const handleFilterChange = (selectedFilter) => {
setFilter(selectedFilter);
setCurrentPage(1);
};
const filteredProducts = () => {
if (filter === "available") {
return products.filter((product) => product.isActive);
} else if (filter === "notAvailable") {
return products.filter((product) => !product.isActive);
} else {
return products;
}
};
const totalPages = Math.ceil(filteredProducts().length / itemsPerPage);
const paginatedProducts = () => {
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const filtered = filteredProducts();
console.log("Filtered Products:", filtered); // Add this line for debugging
return filtered.slice(startIndex, endIndex);
};
const handleNextPage = () => {
if (currentPage < totalPages) {
setCurrentPage((prevPage) => prevPage + 1);
}
};
const handlePrevPage = () => {
if (currentPage > 1) {
setCurrentPage((prevPage) => prevPage - 1);
}
};
const [showEditModal, setShowEditModal] = useState(false);
const [editedProduct, setEditedProduct] = useState(null);
const openEditModal = (product) => {
setEditedProduct(product);
setShowEditModal(true);
};
const closeEditModal = () => {
setEditedProduct(null);
setShowEditModal(false);
};
const handleEdit = (product) => {
openEditModal(product);
};
const handleSaveEdit = async () => {
// Add logic to save the edited product
try {
const token = localStorage.getItem("token");
const response = await fetch(
`${process.env.REACT_APP_API_URL}/product/edit/${editedProduct._id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(editedProduct),
}
);
if (response.ok) {
// Update the state with the edited product
setProducts((prevProducts) =>
prevProducts.map((p) =>
p._id === editedProduct._id ? editedProduct : p
)
);
setError(null);
toast.success("Product edited successfully", {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
setRefreshProducts((prev) => !prev);
closeEditModal();
} else {
const data = await response.json();
setError(data.message || "Failed to edit product");
toast.error("Failed to edit product");
}
} catch (error) {
console.error("Error editing product:", error);
setError("An unexpected error occurred");
toast.error("An unexpected error occurred");
}
};
React.useImperativeHandle(ref, () => ({
refreshProducts: async () => {
try {
setRefreshProducts((prev) => !prev);
} catch (error) {
console.error("Error refreshing products:", error);
}
},
}));
if (loading) {
return (
<Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner>
);
}
if (error) {
return <Alert variant="danger">{error}</Alert>;
}
return (
<>
<div className="d-flex justify-content-between align-items-center mb-3">
<h2>Product List</h2>
<Dropdown onSelect={(selectedFilter) => handleFilterChange(selectedFilter)}>
<Dropdown.Toggle variant="success" id="dropdown-basic">
Filter
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item eventKey="all">All Products</Dropdown.Item>
<Dropdown.Item eventKey="available">Available</Dropdown.Item>
<Dropdown.Item eventKey="notAvailable">Not Available</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>
<div className="row">
{paginatedProducts().map((product, index) => (
<div key={product._id} className={`col-md-6 mb-3${index % 2 === 0 ? ' pr-md-2' : ' pl-md-2'}`}>
<Table striped bordered hover responsive>
<thead>
<tr>
<th colSpan="2">Product Details</th>
</tr>
</thead>
<tbody>
<tr>
<td>Image</td>
<td>
{product.image ? (
<a
href={product.image}
target="_blank"
rel="noopener noreferrer"
>
View Image
</a>
) : (
"No Link"
)}
</td>
</tr>
<tr>
<td>Name</td>
<td className="text-truncate" style={{ maxWidth: '150px' }}>{product.name}</td>
</tr>
<tr>
<td>Description</td>
<td className="text-truncate" style={{ maxWidth: '150px' }}>{product.description}</td>
</tr>
<tr>
<td>Price</td>
<td>{product.price}</td>
</tr>
<tr>
<td>Tags</td>
<td>{product.tags}</td>
</tr>
<tr>
<td>Availability</td>
<td>
<span
className={product.isActive ? "text-success" : "text-danger"}
>
{product.isActive ? "Available" : "Not Available"}
</span>
</td>
</tr>
<tr>
<td>Action</td>
<td>
<Button variant="info" onClick={() => handleEdit(product)}>
Edit
</Button>{" "}
<Button
variant="danger"
onClick={() => openDeleteModal(product._id)}
>
Delete
</Button>
</td>
</tr>
<tr>
<td>Set Availability</td>
<td>
<Button
variant={product.isActive ? "danger" : "success"}
onClick={() =>
handleSetAvailability(product._id, !product.isActive)
}
>
{product.isActive ? "Archive" : "Activate"}
</Button>
</td>
</tr>
</tbody>
</Table>
</div>
))}
</div>
<div className="d-flex justify-content-between align-items-center mt-3">
<div>
Page {currentPage} of {totalPages}
</div>
<div>
<Button
variant="secondary"
onClick={handlePrevPage}
disabled={currentPage === 1}
>
Back
</Button>{" "}
<Button
variant="secondary"
onClick={handleNextPage}
disabled={currentPage === totalPages}
>
Next
</Button>
</div>
</div>
<Modal show={showEditModal} onHide={closeEditModal} centered>
<Modal.Header closeButton>
<Modal.Title>Edit Product</Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<div className="form-group">
<label htmlFor="productName">Name:</label>
<input
type="text"
id="productName"
className="form-control"
value={editedProduct?.name || ''}
onChange={(e) =>
setEditedProduct((prev) => ({ ...prev, name: e.target.value }))
}
/>
</div>
<div className="form-group">
<label htmlFor="productDescription">Description:</label>
<textarea
id="productDescription"
className="form-control"
value={editedProduct?.description || ''}
onChange={(e) =>
setEditedProduct((prev) => ({ ...prev, description: e.target.value }))
}
/>
</div>
<div className="form-group">
<label htmlFor="productPrice">Price:</label>
<input
type="number"
id="productPrice"
className="form-control"
value={editedProduct?.price || ''}
onChange={(e) =>
setEditedProduct((prev) => ({ ...prev, price: e.target.value }))
}
/>
</div>
<div className="form-group">
<label htmlFor="productTags">Tags:</label>
<input
type="text"
id="productTags"
className="form-control"
value={editedProduct?.tags || ''}
onChange={(e) =>
setEditedProduct((prev) => ({ ...prev, tags: e.target.value }))
}
/>
</div>
{/* Add other form fields as needed */}
</form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={closeEditModal}>
Cancel
</Button>
<Button variant="primary" onClick={handleSaveEdit}>
Save
</Button>
</Modal.Footer>
</Modal>
<Modal show={showDeleteModal} onHide={closeDeleteModal}>
<Modal.Header closeButton>
<Modal.Title>Delete Confirmation</Modal.Title>
</Modal.Header>
<Modal.Body>Are you sure you want to delete this product?</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={closeDeleteModal}>
Cancel
</Button>
<Button
variant="danger"
onClick={() => handleDelete(productIdToDelete)}
>
Delete
</Button>
</Modal.Footer>
</Modal>
<ToastContainer />
</>
);
});
export default AllProducts;

@ -0,0 +1,14 @@
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap-icons/font/bootstrap-icons.css';
const AnnouncementBar = () => {
return (
<div className="alert alert-info mb-0 text-center">
Announcement: <strong>GET 15% OFF ON YOUR FIRST PURCHASE! </strong>
<i class="bi bi-gift-fill mx-1"></i>
</div>
);
};
export default AnnouncementBar;

@ -0,0 +1,49 @@
// src/components/Banner.js
import React, { useState, useEffect } from 'react';
import { Carousel, Row, Col } from 'react-bootstrap';
const Banner = ({ data }) => {
const { images } = data;
const [index, setIndex] = useState(0);
const handleSelect = (selectedIndex, e) => {
setIndex(selectedIndex);
};
useEffect(() => {
const intervalId = setInterval(() => {
setIndex((prevIndex) => (prevIndex + 1) % images.length);
}, 2000);
return () => clearInterval(intervalId);
}, [images.length]);
return (
<Carousel
activeIndex={index}
onSelect={handleSelect}
interval={null}
className="carousel-slide"
>
{images.map((image, idx) => (
<Carousel.Item key={idx}>
<img
className="d-block w-100"
src={image.src}
alt={`Slide ${idx}`}
style={{ height: 'auto', objectFit: 'contain' }} // Adjust height and styling here
/>
<Carousel.Caption>
<Row className="justify-content-center align-items-center h-100">
<Col className="text-center">
{/* Your caption content */}
</Col>
</Row>
</Carousel.Caption>
</Carousel.Item>
))}
</Carousel>
);
};
export default Banner;

@ -0,0 +1,162 @@
import React, { useState } from 'react';
import { Form, Button } from 'react-bootstrap';
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
const CreateProduct = () => {
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const [price, setPrice] = useState('');
const [image, setImage] = useState(null);
const [isActive, setIsActive] = useState(true);
const [tags, setTags] = useState([]); // Changed to array for tags
const [isSubmitting, setIsSubmitting] = useState(false);
const handleImageChange = (e) => {
const selectedImage = e.target.files[0];
setImage(selectedImage);
};
const handleSubmit = async (e) => {
e.preventDefault();
try {
setIsSubmitting(true);
const formData = new FormData();
formData.append('name', name);
formData.append('description', description);
formData.append('price', price);
formData.append('tags', JSON.stringify(tags)); // Convert tags array to JSON string
formData.append('isActive', isActive);
formData.append('image', image);
const token = localStorage.getItem('token');
const response = await fetch(
`${process.env.REACT_APP_API_URL}/product/create`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
body: formData,
}
);
if (response.ok) {
toast.success('Product created successfully', {
position: 'bottom-right',
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
// Redirect to AllProducts page
} else {
toast.error('Failed to create product. Please check your input and try again.', {
position: 'bottom-right',
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
}
} catch (error) {
console.error('Error creating product:', error);
toast.error('An unexpected error occurred. Please try again later.', {
position: 'bottom-right',
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
} finally {
setIsSubmitting(false);
}
};
return (
<Form onSubmit={handleSubmit}>
<h1 className="my-5 text-center">Create Product</h1>
<Form.Group controlId="productName">
<Form.Label>Name</Form.Label>
<Form.Control
type="text"
placeholder="Enter product name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</Form.Group>
<Form.Group controlId="productDescription">
<Form.Label>Description</Form.Label>
<Form.Control
as="textarea"
placeholder="Enter product description"
value={description}
onChange={(e) => setDescription(e.target.value)}
required
/>
</Form.Group>
<Form.Group controlId="productPrice">
<Form.Label>Price</Form.Label>
<Form.Control
type="number"
placeholder="Enter product price"
value={price}
onChange={(e) => setPrice(e.target.value)}
required
/>
</Form.Group>
<Form.Group controlId="productTag">
<Form.Label>Tag</Form.Label>
<Form.Control
type="text"
placeholder="Enter product tag"
value={tags.join(', ')} // Display tags as a comma-separated string
onChange={(e) => setTags(e.target.value.split(',').map(tag => tag.trim()))} // Convert string to array
/>
</Form.Group>
<Form.Group controlId="productStatus">
<Form.Check
type="checkbox"
label="Set Product to Active"
checked={isActive}
onChange={() => setIsActive(!isActive)}
/>
</Form.Group>
<Form.Group controlId="productImage">
<Form.Label>Image</Form.Label>
<Form.Control
type="file"
accept="image/*"
name="image"
onChange={handleImageChange}
required
/>
</Form.Group>
<Button
variant="primary"
type="submit"
disabled={isSubmitting}
>
{isSubmitting ? 'Creating...' : 'Create Product'}
</Button>
</Form>
);
};
export default CreateProduct;

@ -0,0 +1,68 @@
import React from 'react';
import { Container, Row, Col, Form, Button } from 'react-bootstrap';
const Footer = () => {
return (
<footer className="bg-dark text-white py-4">
<Container>
<Row>
{/* Store Information */}
<Col md={3}>
<h3 className="mb-4">STORE INFORMATION</h3>
<p className='text-white'><i className="bi bi-chevron-right"></i> 3F RBM Building</p>
<p className='text-white'><i className="bi bi-chevron-right"></i> +639052169035</p>
<p className='text-white'><i className="bi bi-chevron-right"></i> customercare@benstore.com.ph</p>
</Col>
{/* Our Company */}
<Col md={3}>
<h3 className="mb-4">OUR COMPANY</h3>
<ul className="list-unstyled">
<li className='pb-2'><a href="#" className="text-decoration-none text-white"><i className="bi bi-chevron-right"></i> Terms and conditions of use</a></li>
<li className='pb-2'><a href="#" className="text-decoration-none text-white"><i className="bi bi-chevron-right"></i> About us</a></li>
<li className='pb-2'><a href="#" className="text-decoration-none text-white"><i className="bi bi-chevron-right"></i> Contact us</a></li>
<li className='pb-2'><a href="#" className="text-decoration-none text-white"><i className="bi bi-chevron-right"></i> Stores</a></li>
</ul>
</Col>
{/* Your Account */}
<Col md={3}>
<h3 className="mb-4">YOUR ACCOUNT</h3>
<ul className="list-unstyled">
<li><a href="#" className="text-decoration-none text-white"><i className="bi bi-chevron-right"></i> Orders</a></li>
<li><a href="#" className="text-decoration-none text-white"><i className="bi bi-chevron-right"></i> Login</a></li>
</ul>
</Col>
{/* Newsletter */}
<Col md={3}>
<h3 className="mb-4">NEWSLETTER</h3>
<Form>
<Form.Group controlId="emailForm" className="mb-2">
<Form.Control type="email" placeholder="Enter your email" />
</Form.Group>
<Button variant="light" type="submit" className="d-flex align-items-center">
<i className="bi bi-chevron-right me-2"></i>Subscribe
</Button>
<p className="mt-2 text-white">Sign up and get the latest deals, offers & updates from our store. You may unsubscribe at any moment. For that purpose, please find our contact info in the legal notice.</p>
</Form>
</Col>
</Row>
{/* Social Media Icons */}
<Row className="mt-4 text-center">
<Col>
<h3>CONNECT WITH US</h3>
<p className="mb-0">
<a href="#" className="text-white me-3"><i className="bi bi-facebook"></i></a>
<a href="#" className="text-white me-3"><i className="bi bi-twitter"></i></a>
<a href="#" className="text-white"><i className="bi bi-instagram"></i></a>
</p>
</Col>
</Row>
</Container>
</footer>
);
};
export default Footer;

@ -0,0 +1,65 @@
import React, { useContext } from 'react';
import { Navbar, Nav, Container, Form, FormControl, Button } from 'react-bootstrap';
import { NavLink, Link } from 'react-router-dom';
import UserContext from '../UserContext';
export default function NavBar() {
const { user } = useContext(UserContext);
const handleNavLinkClick = () => {
const navbarToggle = document.getElementById('basic-navbar-nav');
if (navbarToggle) {
navbarToggle.classList.remove('show');
}
};
return (
<Navbar bg="light" expand="lg" className="my-4 shadow-sm">
<Container>
<Navbar.Brand as={Link} to="" onClick={handleNavLinkClick}>
iStore
</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav" onSelect={handleNavLinkClick}>
<Nav className="mx-auto">
<Form inline className="d-flex">
<FormControl type="text" placeholder="Search" className="mr-sm-2" size="sm" />
<Button className="ml-2">
<i className="bi bi-search"></i>
</Button>
</Form>
</Nav>
<Nav className="ml-lg-auto">
{user && user.id !== null ? (
<>
<Nav.Link as={NavLink} to="/profile" exact onClick={handleNavLinkClick}>
Profile
</Nav.Link>
<Nav.Link as={NavLink} to="/logout" exact onClick={handleNavLinkClick}>
Logout
</Nav.Link>
{user.isAdmin && (
<Nav.Link as={NavLink} to="/dashboard" exact onClick={handleNavLinkClick}>
Admin Dashboard
</Nav.Link>
)}
<Nav.Link as={NavLink} to="/cart" onClick={handleNavLinkClick}>
Cart
</Nav.Link>
</>
) : (
<>
<Nav.Link as={NavLink} to="/login" exact onClick={handleNavLinkClick}>
Login
</Nav.Link>
<Nav.Link as={NavLink} to="/register" exact onClick={handleNavLinkClick}>
Register
</Nav.Link>
</>
)}
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
);
}

@ -0,0 +1,9 @@
// PrivateRoute.js
import React from 'react';
import { Navigate } from 'react-router-dom';
const PrivateRoute = ({ element, isVerified }) => {
return isVerified ? element : <Navigate to="/404" />;
};
export default PrivateRoute;

@ -0,0 +1,169 @@
import React, { useState, useEffect } from "react";
import { Container, Col, Button, Row } from "react-bootstrap";
import Slider from "react-slick";
import { useMediaQuery } from "react-responsive";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import "./productCatalog.css";
const ProductCatalog = () => {
const [taggedProducts, setTaggedProducts] = useState([]);
const [activeFilter, setActiveFilter] = useState("onsale");
const [error, setError] = useState(null);
const isMobile = useMediaQuery({ maxWidth: 767 });
useEffect(() => {
const fetchTaggedProducts = async () => {
try {
const token = localStorage.getItem("token");
const response = await fetch(
`${process.env.REACT_APP_API_URL}/product/product-tag?tag=${activeFilter}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const data = await response.json();
if (Array.isArray(data)) {
setTaggedProducts(data);
setError(null);
} else {
setError("Token is invalid. please login.");
}
} catch (error) {
console.error("Error fetching tagged products:", error);
setError("Error fetching tagged products. Please try again.");
}
};
fetchTaggedProducts();
}, [activeFilter]);
const handleAddToCart = (productId) => {
console.log(`Product with ID ${productId} added to cart`);
};
const sliderSettings = {
dots: true,
infinite: true,
speed: 500,
slidesToShow: 1,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 3000,
};
return (
<Container className="my-5 text-center">
<h2>Top Products</h2>
<div className="mb-3 d-flex justify-content-center">
<Button
variant={activeFilter === "onsale" ? "primary" : "outline-primary"}
onClick={() => setActiveFilter("onsale")}
className="rounded-pill px-3 py-2 mx-2 btn-transition"
>
On Sale
</Button>
<Button
variant={
activeFilter === "bestselling" ? "primary" : "outline-primary"
}
onClick={() => setActiveFilter("bestselling")}
className="rounded-pill px-3 py-2 mx-2 btn-transition"
>
Best Selling
</Button>
</div>
{isMobile ? (
<Slider {...sliderSettings}>
{error ? (
<div>
<p>{error}</p>
</div>
) : (
taggedProducts.map((product) => (
<div key={product._id}>
<img
src={product.image || "placeholder-image-url"}
alt={product.name}
className="img-fluid rounded"
/>
<div
className="mb-4"
style={{ marginTop: "-70px", marginBottom: "-100px" }}
>
<h6>{product.name}</h6>
<p>
{new Intl.NumberFormat("en-PH", {
style: "currency",
currency: "PHP",
}).format(product.price)}
</p>
<Button
variant={
activeFilter === "bestselling"
? "primary"
: "outline-primary"
}
onClick={() => handleAddToCart(product._id)}
className="rounded-pill px-3 py-2 btn-transition"
>
Add to Cart
</Button>
</div>
</div>
))
)}
</Slider>
) : (
<Row>
{error ? (
<Col>
<p>{error}</p>
</Col>
) : (
taggedProducts.map((product) => (
<Col key={product._id} className="mb-2">
<div>
<img
src={product.image || "placeholder-image-url"}
alt={product.name}
className="img-fluid rounded"
/>
<h6>{product.name}</h6>
<p>
{new Intl.NumberFormat("en-PH", {
style: "currency",
currency: "PHP",
}).format(product.price)}
</p>
<Button
variant={
activeFilter === "bestselling"
? "primary"
: "outline-primary"
}
onClick={() => handleAddToCart(product._id)}
className="rounded-pill px-3 py-2 btn-transition"
>
Add to Cart
</Button>
</div>
</Col>
))
)}
</Row>
)}
</Container>
);
};
export default ProductCatalog;

@ -0,0 +1,229 @@
// ProductCatalog.js
import React, { useState, useEffect } from 'react';
import { Container, Col, Button, Row } from 'react-bootstrap';
import Slider from 'react-slick';
import { useMediaQuery } from 'react-responsive';
// import { toast, ToastContainer } from 'react-toastify';
import { Link } from 'react-router-dom'; // Import Link from react-router-dom
import 'react-toastify/dist/ReactToastify.css';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
import './productCatalog.css';
// import { jwtDecode } from 'jwt-decode';
const ProductCatalog = () => {
const [taggedProducts, setTaggedProducts] = useState([]);
const [activeFilter, setActiveFilter] = useState('onsale');
const [error, setError] = useState(null);
const isMobile = useMediaQuery({ maxWidth: 767 });
useEffect(() => {
const fetchTaggedProducts = async () => {
try {
const token = localStorage.getItem('token');
const response = await fetch(
`${process.env.REACT_APP_API_URL}/product/product-tag?tag=${activeFilter}`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const data = await response.json();
if (Array.isArray(data)) {
setTaggedProducts(data);
setError(null);
} else {
setError('Token is invalid. please login.');
}
} catch (error) {
console.error('Error fetching tagged products:', error);
setError('Error fetching tagged products. Please try again.');
}
};
fetchTaggedProducts();
}, [activeFilter]);
// const handleAddToCart = async (productId) => {
// try {
// const token = localStorage.getItem('token');
// // Decode the token to get the userId
// const decodedToken = jwtDecode(token);
// const userId = decodedToken.userId;
// const response = await fetch(`${process.env.REACT_APP_API_URL}/cart/add-to-cart`, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// Authorization: `Bearer ${token}`,
// },
// body: JSON.stringify({
// userId,
// productId,
// quantity: 1, // You can adjust the quantity as needed
// }),
// });
// const data = await response.json();
// if (response.ok) {
// // Product successfully added to the cart
// toast.success(`Product with ID ${productId} added to cart`, {
// position: 'bottom-right',
// autoClose: 2000,
// hideProgressBar: true,
// closeOnClick: true,
// pauseOnHover: true,
// draggable: true,
// });
// } else {
// // Handle error response from the server
// toast.error(`Failed to add product to cart: ${data.message}`, {
// position: 'bottom-right',
// autoClose: 2000,
// hideProgressBar: true,
// closeOnClick: true,
// pauseOnHover: true,
// draggable: true,
// });
// }
// } catch (error) {
// console.error('Error adding product to cart:', error);
// // Handle other errors, such as network issues
// toast.error('Error adding product to cart. Please try again later.', {
// position: 'bottom-right',
// autoClose: 2000,
// hideProgressBar: true,
// closeOnClick: true,
// pauseOnHover: true,
// draggable: true,
// });
// }
// };
const sliderSettings = {
dots: true,
infinite: true,
speed: 500,
slidesToShow: 1,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 3000,
};
return (
<Container className="my-5 text-center">
<h2>Top Products</h2>
<div className="mb-3 d-flex justify-content-center">
<Button
variant={activeFilter === 'onsale' ? 'primary' : 'outline-primary'}
onClick={() => setActiveFilter('onsale')}
className="rounded-pill px-3 py-2 mx-2 btn-transition"
>
On Sale
</Button>
<Button
variant={activeFilter === 'bestselling' ? 'primary' : 'outline-primary'}
onClick={() => setActiveFilter('bestselling')}
className="rounded-pill px-3 py-2 mx-2 btn-transition"
>
Best Selling
</Button>
</div>
{isMobile ? (
<Slider {...sliderSettings}>
{error ? (
<div>
<p>{error}</p>
</div>
) : (
taggedProducts.map((product) => (
<div key={product._id}>
{/* Use Link to make product name clickable */}
<Link
to={`/product/${product._id}`}
className="custom-link d-block text-decoration-none text-dark"
>
<img
src={product.image || 'placeholder-image-url'}
alt={product.name}
className="img-fluid rounded"
/>
<div className="mb-4" style={{ marginTop: '-70px', marginBottom: '-100px' }}>
<h6>{product.name}</h6>
<p>
{new Intl.NumberFormat('en-PH', {
style: 'currency',
currency: 'PHP',
}).format(product.price)}
</p>
{/* <Button
variant={activeFilter === 'bestselling' ? 'primary' : 'outline-primary'}
onClick={() => handleAddToCart(product._id)}
className="rounded-pill px-3 py-2 btn-transition"
>
Add to Cart
</Button> */}
</div>
</Link>
</div>
))
)}
</Slider>
) : (
<Row>
{error ? (
<Col>
<p>{error}</p>
</Col>
) : (
taggedProducts.map((product) => (
<Col key={product._id} className="mb-2">
<div>
{/* Use Link to make product name clickable */}
<Link
to={`/product/${product._id}`}
className="custom-link d-block text-decoration-none text-dark"
>
<img
src={product.image || 'placeholder-image-url'}
alt={product.name}
className="img-fluid rounded"
/>
<h6>{product.name}</h6>
<p>
{new Intl.NumberFormat('en-PH', {
style: 'currency',
currency: 'PHP',
}).format(product.price)}
</p>
{/* <Button
variant={activeFilter === 'bestselling' ? 'primary' : 'outline-primary'}
onClick={() => handleAddToCart(product._id)}
className="rounded-pill px-3 py-2 btn-transition"
>
Add to Cart
</Button> */}
</Link>
</div>
</Col>
))
)}
</Row>
)}
{/* <ToastContainer /> */}
</Container>
);
};
export default ProductCatalog;

@ -0,0 +1,49 @@
// components/ProtectedRoute.js
import React, { useEffect, useState } from 'react';
import { Route, Navigate, useLocation, useNavigate } from 'react-router-dom';
import { jwtDecode } from 'jwt-decode';
const isTokenValidFunction = (token) => {
try {
if (!token) {
return false;
}
const decodedToken = jwtDecode(token);
const currentTime = Date.now() / 1000;
return decodedToken.exp > currentTime;
} catch (error) {
console.error('Error decoding token:', error);
return false;
}
};
const ProtectedRoute = ({ element: Element, ...rest }) => {
const [isTokenValid, setIsTokenValid] = useState(true);
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
const checkTokenValidity = () => {
const token = localStorage.getItem('token');
const isValid = isTokenValidFunction(token);
setIsTokenValid(isValid);
if (!isValid && !location.state?.expired) {
navigate('/login', { state: { expired: true } });
}
};
checkTokenValidity();
}, [location.state, navigate]);
return (
<Route
{...rest}
element={isTokenValid ? <Element /> : <Navigate to="/login" state={{ expired: true }} />}
/>
);
};
export default ProtectedRoute;

@ -0,0 +1,155 @@
// src/components/RegisterForm.js
import React, { useState } from 'react';
import { Form, Button, Container, Row } from 'react-bootstrap';
import { toast, ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { useNavigate } from 'react-router-dom';
const RegisterForm = () => {
const navigate = useNavigate();
const [formData, setFormData] = useState({
email: '',
password: '',
firstName: '',
lastName: '',
});
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};
const handleSubmit = async (e) => {
e.preventDefault();
try {
const apiUrl = `${process.env.REACT_APP_API_URL}/user/register`;
// Check if the email already exists
const existingUserResponse = await fetch(`${process.env.REACT_APP_API_URL}/user/check-email`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email: formData.email }),
});
const existingUserResult = await existingUserResponse.json();
if (existingUserResult.exists) {
toast.error('This email is already registered. Please use a different email.', {
position: 'bottom-right',
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
return;
}
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
if (response.ok) {
// Registration successful
toast.success('Registration successful!', {
position: 'bottom-right',
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
// Redirect to the login page
navigate('/login');
} else {
// Registration failed
toast.error('Registration failed. Please try again.', {
position: 'bottom-right',
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
}
} catch (error) {
console.error('Registration error:', error);
}
};
return (
<Container className="mt-5 shadow p-5 rounded">
<h1 className="text-center mb-4">Register</h1>
<Row className="justify-content-center">
<Form onSubmit={handleSubmit}>
<Form.Group controlId="formEmail">
<Form.Label>Email address</Form.Label>
<Form.Control
type="email"
placeholder="Enter email"
name="email"
value={formData.email}
onChange={handleChange}
required
/>
</Form.Group>
<Form.Group controlId="formPassword">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
placeholder="Password"
name="password"
value={formData.password}
onChange={handleChange}
required
/>
</Form.Group>
{/* <Form.Group controlId="formFirstName">
<Form.Label>First Name</Form.Label>
<Form.Control
type="text"
placeholder="First Name"
name="firstName"
value={formData.firstName}
onChange={handleChange}
/>
</Form.Group>
<Form.Group controlId="formLastName">
<Form.Label>Last Name</Form.Label>
<Form.Control
type="text"
placeholder="Last Name"
name="lastName"
value={formData.lastName}
onChange={handleChange}
/>
</Form.Group> */}
<Button variant="primary" type="submit">
Register
</Button>
</Form>
</Row>
{/* Toast container for notifications */}
<ToastContainer position="bottom-right" />
</Container>
);
};
export default RegisterForm;

@ -0,0 +1,81 @@
import React, { useState } from 'react';
const ResetPassword = () => {
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [message, setMessage] = useState('');
const handleResetPassword = async (e) => {
e.preventDefault();
if (password !== confirmPassword) {
setMessage('Passwords do not match');
return;
}
try {
const resetToken = // Retrieve the reset token from your URL or state
new URLSearchParams(window.location.search).get('resetToken'); // Example: ?resetToken=yourResetToken
const response = await fetch(`${process.env.REACT_APP_API_URL}/user/reset-password`, {
method: 'POST', // Assuming your backend uses POST for reset password
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ newPassword: password, resetToken }),
});
if (response.ok) {
setMessage('Password reset successfully');
setPassword('');
setConfirmPassword('');
} else {
const errorData = await response.json();
setMessage(errorData.message);
}
} catch (error) {
setMessage('An error occurred. Please try again.');
console.error(error);
}
};
return (
<div className="container">
<h2>Reset Password</h2>
<form onSubmit={handleResetPassword}>
<div className="mb-3">
<label htmlFor="password" className="form-label">
New Password
</label>
<input
type="password"
className="form-control"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<div className="mb-3">
<label htmlFor="confirmPassword" className="form-label">
Confirm Password
</label>
<input
type="password"
className="form-control"
id="confirmPassword"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
/>
</div>
{message && <div className="alert alert-danger">{message}</div>}
<button type="submit" className="btn btn-primary">
Reset Password
</button>
</form>
</div>
);
};
export default ResetPassword;

@ -0,0 +1,70 @@
// TokenChecker.js
import { useEffect } from 'react';
const TOKEN_CHECK_INTERVAL = 30000; // 30 seconds in milliseconds
const TokenChecker = ({ setUser, unsetUser }) => {
useEffect(() => {
const checkTokenExpiration = async () => {
const token = localStorage.getItem('token');
const lastCheckTimestamp = localStorage.getItem('lastTokenCheck');
if (token) {
// Check if enough time has passed since the last token check
const currentTime = new Date().getTime();
if (lastCheckTimestamp && currentTime - lastCheckTimestamp < TOKEN_CHECK_INTERVAL) {
return;
}
try {
const response = await fetch(`${process.env.REACT_APP_API_URL}/user/details`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.ok) {
const data = await response.json();
if (typeof data._id !== 'undefined') {
setUser({
id: data._id,
isVerified: data.isVerified,
isAdmin: data.isAdmin,
});
} else {
setUser({
id: null,
isVerified: null,
isAdmin: null,
});
unsetUser();
}
} else {
// Handle token expiration by redirecting to the login page or the expired token page
window.location.href = '/token-expired'; // Change this line as needed
}
} catch (error) {
console.error('Error during token check:', error);
}
// Update the last check timestamp in local storage
localStorage.setItem('lastTokenCheck', currentTime);
}
};
// Call the token check function
checkTokenExpiration();
// Set up an interval for periodic token checks (adjust as needed)
const intervalId = setInterval(checkTokenExpiration, TOKEN_CHECK_INTERVAL);
// Cleanup: clear the interval when the component is unmounted
return () => clearInterval(intervalId);
}, [setUser, unsetUser]);
// The rest of your component logic goes here...
// Note: You don't need a return statement here.
};
export default TokenChecker;

@ -0,0 +1,166 @@
// UpdateProfile.js
import React, { useContext, useEffect, useState } from 'react';
import { Form, Button, Alert, Container, Row, Col } from 'react-bootstrap';
import { toast } from 'react-toastify';
import UserContext from '../UserContext';
export default function UpdateProfile() {
const { user } = useContext(UserContext);
const [formData, setFormData] = useState({
userId: user.id,
newEmail: user.email,
newFirstName: user.firstName,
newLastName: user.lastName,
newPassword: '',
newMobileNo: user.mobileNo || '',
});
const [loading, setLoading] = useState(false);
const [updateError, setUpdateError] = useState(null);
const handleUpdateProfile = async (e) => {
e.preventDefault();
try {
setLoading(true);
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.getItem('token')}`,
};
const response = await fetch(`${process.env.REACT_APP_API_URL}/user/update`, {
method: 'PUT',
headers,
body: JSON.stringify(formData),
});
if (response.ok) {
const updatedUserData = await response.json();
console.log('Updated User Data:', updatedUserData);
// Handle the updated user data as needed
toast.success('Profile updated successfully!', {
position: 'bottom-right',
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
} else {
console.error('Failed to update user profile:', response.status);
const errorData = await response.json();
setUpdateError(errorData.message);
toast.error('Failed to update profile. Please try again.', {
position: 'bottom-right',
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
}
} catch (error) {
console.error('Error updating user profile:', error);
setUpdateError('An unexpected error occurred. Please try again later.');
toast.error('An unexpected error occurred. Please try again later.', {
position: 'bottom-right',
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
} finally {
setLoading(false);
}
};
useEffect(() => {
// Update form data when user data changes
setFormData({
userId: user.id,
newEmail: user.email,
newFirstName: user.firstName,
newLastName: user.lastName,
newPassword: '',
newMobileNo: user.mobileNo || '',
});
}, [user]);
return (
<Container className="mt-5 shadow p-5 rounded">
<Row className="justify-content-center">
<Col lg={6} offset={3}>
<Form onSubmit={handleUpdateProfile}>
<h1 className="my-5 text-center">Update Profile</h1>
{updateError && <Alert variant="danger">{updateError}</Alert>}
<Form.Group controlId="newEmail">
<Form.Label>Email address</Form.Label>
<Form.Control
type="email"
placeholder="Enter email"
value={formData.newEmail}
onChange={(e) => setFormData({ ...formData, newEmail: e.target.value })}
/>
</Form.Group>
<Form.Group controlId="newFirstName">
<Form.Label className="mt-2">First Name</Form.Label>
<Form.Control
type="text"
placeholder="Enter first name"
value={formData.newFirstName}
onChange={(e) => setFormData({ ...formData, newFirstName: e.target.value })}
/>
</Form.Group>
<Form.Group controlId="newLastName">
<Form.Label className="mt-2">Last Name</Form.Label>
<Form.Control
type="text"
placeholder="Enter last name"
value={formData.newLastName}
onChange={(e) => setFormData({ ...formData, newLastName: e.target.value })}
/>
</Form.Group>
<Form.Group controlId="newMobileNo">
<Form.Label className="mt-2">Mobile Number</Form.Label>
<Form.Control
type="text"
placeholder="Enter mobile number"
value={formData.newMobileNo}
onChange={(e) => setFormData({ ...formData, newMobileNo: e.target.value })}
/>
</Form.Group>
<Form.Group controlId="newPassword">
<Form.Label className="mt-2">New Password</Form.Label>
<Form.Control
type="password"
placeholder="Enter new password"
value={formData.newPassword}
onChange={(e) => setFormData({ ...formData, newPassword: e.target.value })}
/>
</Form.Group>
{loading ? (
<Button className="mt-3" variant="primary" type="submit" disabled>
Updating...
</Button>
) : (
<Button className="mt-3" variant="primary" type="submit">
Save Changes
</Button>
)}
</Form>
</Col>
</Row>
</Container>
);
}

@ -0,0 +1,17 @@
.banner-content {
text-align: center;
color: #fff;
background-color: rgba(0, 0, 0, 0.5); /* Background color with low opacity */
padding: 20px;
max-width: 600px; /* Adjust the max-width as needed */
}
/* src/components/Banner.css */
.carousel-fade .carousel-inner .carousel-item {
opacity: 0;
transition: opacity 1s ease-in-out; /* Adjust the duration and easing function as needed */
}
.carousel-fade .carousel-inner .carousel-item.active {
opacity: 1;
}

@ -0,0 +1,37 @@
.product-container {
height: 600px; /* Set your preferred height */
width: 100%; /* Adjust as needed */
overflow: hidden; /* Hide overflow content */
}
.transition-container {
position: relative;
overflow: hidden;
}
.slide-left {
animation: slideLeft 0.5s ease-out;
}
.slide-right {
animation: slideRight 0.5s ease-out;
}
@keyframes slideLeft {
from {
left: 100%;
}
to {
left: 0;
}
}
@keyframes slideRight {
from {
left: -100%;
}
to {
left: 0;
}
}

@ -0,0 +1,49 @@
// useCart.js
import { useState, useEffect } from 'react';
const useCart = () => {
const [cartState, setCartState] = useState({
// ... your initial cart state
});
const fetchCartDetails = async () => {
try {
const token = localStorage.getItem('token');
const response = await fetch(`${process.env.REACT_APP_API_URL}/cart/cart-details`, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.ok) {
const data = await response.json();
setCartState(data);
} else {
console.error('Failed to fetch cart details');
}
} catch (error) {
console.error('Error fetching cart details:', error);
}
};
useEffect(() => {
const token = localStorage.getItem('token');
// Assuming you have user context and user.id
const userId = ''; // Replace with the actual user.id from your user context
if (userId && token) {
fetchCartDetails(userId);
}
}, []); // The empty dependency array ensures that this effect runs only once when the component mounts
return {
state: cartState,
fetchCartDetails,
// ... other functions and state as needed
};
};
export { useCart };

@ -0,0 +1,22 @@
import React from 'react';
import { createRoot } from 'react-dom';
import { Provider } from 'react-redux'; // Import the Provider
import App from './App';
import store from './redux/store'; // Import your Redux store
import 'typeface-montserrat';
import 'typeface-roboto';
import 'bootstrap/dist/css/bootstrap.min.css';
import './_custom-bootstrap-theme.scss'; // Import the custom Bootstrap theme
import './_custom-styles.scss'; // Import your custom styles
import './App.css'; // Import your app-specific styles
import 'bootstrap-icons/font/bootstrap-icons.css';
const root = createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
{/* Wrap your App component with the Provider and pass the store */}
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -0,0 +1,8 @@
// src/pages/About.js
import React from 'react';
const About = () => {
return <div>About Page</div>;
};
export default About;

@ -0,0 +1,32 @@
// AdminDashboard.js
import React, { useRef } from 'react';
import { Tabs, Tab, Container } from 'react-bootstrap';
import CreateProduct from '../components/CreateProduct';
import AllProducts from '../components/AllProducts';
const AdminDashboard = () => {
const allProductsRef = useRef();
const prevRefreshRef = () => {
if (allProductsRef.current) {
allProductsRef.current.refreshProducts();
}
};
return (
<Container>
<Tabs defaultActiveKey="view-all" id="admin-dashboard-tabs" onSelect={(key) => key === 'create' && prevRefreshRef()}>
<Tab eventKey="view-all" title="View All Products">
<AllProducts ref={allProductsRef} />
</Tab>
<Tab eventKey="create" title="Create Product">
<CreateProduct />
</Tab>
{/* Add more tabs for other actions if needed */}
</Tabs>
</Container>
);
};
export default AdminDashboard;

@ -0,0 +1,356 @@
import React, { useState, useEffect } from "react";
import { Container, Row, Col, Table, Button, Form } from "react-bootstrap";
import { jwtDecode } from "jwt-decode";
import { ToastContainer, toast } from "react-toastify";
import { useNavigate } from "react-router-dom";
import "react-toastify/dist/ReactToastify.css";
const CartPage = () => {
const [cartItems, setCartItems] = useState([]);
const [productDetails, setProductDetails] = useState({});
const [totalPrice, setTotalPrice] = useState(0);
const [shippingFee, setShippingFee] = useState(0);
const [promoCode, setPromoCode] = useState("");
const navigate = useNavigate();
const fetchCartItems = async () => {
try {
const token = localStorage.getItem("token");
const decoded = jwtDecode(token);
const response = await fetch(
`${process.env.REACT_APP_API_URL}/cart/user/${decoded.userId}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const data = await response.json();
if (Array.isArray(data.items)) {
setCartItems(data.items);
}
} catch (error) {
console.error("Error fetching cart items:", error);
}
};
useEffect(() => {
fetchCartItems();
}, []);
useEffect(() => {
const fetchProductDetails = async () => {
try {
const token = localStorage.getItem("token");
const productIds = cartItems.map((item) => item.product);
const promises = productIds.map(async (productId) => {
const response = await fetch(
`${process.env.REACT_APP_API_URL}/product/${productId}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (!response.ok) {
console.error(
`Failed to fetch product details for ID ${productId}: ${response.status}`
);
return null;
}
const productData = await response.json();
return { [productId]: productData };
});
const productDetailsArray = await Promise.all(promises);
const productDetailsObject = productDetailsArray.reduce(
(acc, product) => ({ ...acc, ...product }),
{}
);
setProductDetails(productDetailsObject);
} catch (error) {
console.error("Error fetching product details:", error);
}
};
if (cartItems.length > 0) {
fetchProductDetails();
}
}, [cartItems]);
useEffect(() => {
const calculateTotalPrice = () => {
const totalPrice = cartItems.reduce((total, item) => {
const product = productDetails[item.product];
if (product) {
return total + product.price * item.quantity;
}
return total;
}, 0);
setTotalPrice(totalPrice);
};
const calculateShippingFee = () => {
// Leave it blank for now, add your logic to calculate shipping fee here
};
calculateTotalPrice();
calculateShippingFee();
}, [cartItems, productDetails]);
const updateCart = async () => {
try {
const token = localStorage.getItem("token");
const decoded = jwtDecode(token);
const updates = cartItems.map((item) => ({
product: item.product,
quantity: item.quantity,
}));
const response = await fetch(
`${process.env.REACT_APP_API_URL}/cart/update-cart`,
{
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
userId: decoded.userId,
updates,
}),
}
);
const data = await response.json();
if (response.ok) {
toast.success(data.message, {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
fetchCartItems();
} else {
toast.error(data.error || "Error updating cart", {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
}
} catch (error) {
console.error("Error updating cart:", error);
}
};
const handleQuantityChange = (productId, newQuantity) => {
setCartItems((prevItems) =>
prevItems.map((item) =>
item.product === productId
? { ...item, quantity: parseInt(newQuantity, 10) }
: item
)
);
};
const handleProceedToCheckout = () => {
// Add logic to handle the checkout process
// You can navigate to the checkout page or display a modal for further steps
navigate("/checkout");
// This is a placeholder, you need to implement the actual checkout logic
toast.success("Proceeding to Checkout...", {
position: "bottom-right",
autoClose: 2000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
};
return (
<Container className="my-5 text-center">
<h2>Shopping Cart</h2>
{cartItems.length === 0 ? (
<p>Your cart is empty.</p>
) : (
<>
<Row>
{/* Left side - Product Details */}
<Col xs={12} md={8}>
<h3>Product Details</h3>
{cartItems.map((item) => (
<Table
key={item.product}
striped
bordered
hover
className="mb-4"
>
<tbody>
<tr>
<td colSpan="2">
<strong>Product Image</strong>
</td>
</tr>
<tr>
<td colSpan="2">
<img
src={
(productDetails[item.product] &&
productDetails[item.product].image) ||
"placeholder-image-url"
}
alt={
(productDetails[item.product] &&
productDetails[item.product].name) ||
"Product Image"
}
style={{ width: "200px", height: "200px" }}
/>
</td>
</tr>
<tr>
<td>
<strong>Name</strong>
</td>
<td>{productDetails[item.product]?.name}</td>
</tr>
<tr>
<td>
<strong>Description</strong>
</td>
<td>{productDetails[item.product]?.description}</td>
</tr>
<tr>
<td>
<strong>Price</strong>
</td>
<td>
<strong>
{productDetails[item.product]?.price * item.quantity}
</strong>
</td>
</tr>
<tr>
<td>
<strong>Quantity</strong>
</td>
<td>
<input
type="number"
min="1"
value={item.quantity}
onChange={(e) =>
handleQuantityChange(item.product, e.target.value)
}
/>
</td>
</tr>
</tbody>
</Table>
))}
</Col>
{/* Right side - Order Summary */}
<Col xs={12} md={4}>
<h3>Order Summary</h3>
<Table striped bordered hover>
<thead>
<tr>
<th>Order Summary</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Total Items</td>
{cartItems.reduce(
(total, item) => total + item.quantity,
0
)}
</tr>
<tr>
<td>Shipping Fee</td>
<td>{shippingFee}</td>
</tr>
<tr>
<td>Total</td>
<td>
<strong>{totalPrice + shippingFee}</strong>
</td>
</tr>
<tr>
<td colSpan="2">
<Form.Group controlId="promoCode">
<Form.Label>Promo Code</Form.Label>
<Form.Control
type="text"
placeholder="Enter promo code"
value={promoCode}
onChange={(e) => setPromoCode(e.target.value)}
style={{ backgroundColor: "#f8f9fa" }}
/>
</Form.Group>
</td>
</tr>
</tbody>
</Table>
<div>
<Button variant="primary" onClick={updateCart} className="mt-3">
Update Cart
</Button>
<Button
variant="primary"
onClick={handleProceedToCheckout}
className="mx-3 mt-3"
>
Proceed to Checkout
</Button>
</div>
<tr>
<td
colSpan="2"
className="text-start"
style={{
fontSize: "12px",
opacity: "0.5",
lineHeight: "5px",
}}
>
<p className="mt-4">
We accept cash, COD, bank deposit, credit card, Dragonpay,
and Paypal.
</p>
<p>(Cash on Delivery available within Metro Manila only)</p>
<p>Shipped through trusted couriers</p>
<p>7 days store replacement w/ warranty</p>
</td>
</tr>
</Col>
</Row>
</>
)}
<ToastContainer />
</Container>
);
};
export default CartPage;

@ -0,0 +1,75 @@
import React, { useState, useEffect } from 'react';
import { Container, Row, Col, Table, Button } from 'react-bootstrap';
import { jwtDecode } from 'jwt-decode';
const CheckoutPage = () => {
const [orders, setOrders] = useState([]);
useEffect(() => {
// Fetch orders when the component mounts
fetchOrders();
}, []);
const fetchOrders = async () => {
try {
const token = localStorage.getItem('token');
const decoded = jwtDecode(token);
const response = await fetch(`${process.env.REACT_APP_API_URL}/order/get-order`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId: decoded.userId,
}),
});
if (!response.ok) {
throw new Error('Failed to fetch orders');
}
const data = await response.json();
setOrders(data.orderedProducts || []);
} catch (error) {
console.error('Error fetching orders:', error);
// Handle error, show a message, etc.
}
};
return (
<Container className="my-5">
<h2 className="text-center">Checkout</h2>
{/* Display orders in a table */}
<Row>
<Col>
<Table striped bordered hover>
<thead>
<tr>
<th className="text-left">Product</th>
<th className="text-left">Quantity</th>
<th className="text-left">Price</th>
</tr>
</thead>
<tbody>
{orders.map((order) => (
<tr key={order.productId}>
<td className="text-left">{order.productName}</td>
<td className="text-left">{order.quantity}</td>
<td className="text-left">{order.price}</td>
</tr>
))}
</tbody>
</Table>
</Col>
</Row>
<Row className="justify-content-end">
<Col xs="auto">
<Button variant="primary">Confirm and Pay</Button>
</Col>
</Row>
</Container>
);
};
export default CheckoutPage;

@ -0,0 +1,16 @@
// NotFound.js
import React from 'react';
import { Container, Row, Col } from 'react-bootstrap';
const NotFound = () => (
<Container className="mt-5">
<Row>
<Col className="text-center">
<h1>404 - Not Found</h1>
<p>The page you are looking for does not exist.</p>
</Col>
</Row>
</Container>
);
export default NotFound;

@ -0,0 +1,58 @@
import React from 'react';
import { Container, Row, Col } from 'react-bootstrap';
import Banner from '../components/Banner';
import Footer from '../components/Footer';
import ProductCatalog from '../components/ProductCatalog';
const Home = () => {
const bannerData = {
images: [
{ src: '/assets/image1.jpg' },
{ src: '/assets/image2.png' },
],
};
return (
<>
<Banner data={bannerData} />
{/* Features Section */}
<Container className="mt-5">
<Row className="d-flex justify-content-center align-items-center">
{/* Trusted Couriers */}
<Col md={3} className="text-center mb-4">
<i className="bi bi-truck text-primary mb-3" style={{ fontSize: '4rem' }}></i>
<div className="text-uppercase" style={{ fontSize: '1.4rem' }}>Trusted Couriers</div>
<div className='text-opacity-10'>We ship nationwide</div>
</Col>
{/* 7 Days Replacement */}
<Col md={3} className="text-center mb-4">
<i className="bi bi-arrow-return-left text-primary mb-3" style={{ fontSize: '4rem' }}></i>
<div className="text-uppercase" style={{ fontSize: '1.4rem' }}>7 Days Replacement</div>
<div>If manufacturer defective</div>
</Col>
{/* Best Prices */}
<Col md={3} className="text-center mb-4">
<i className="bi bi-currency-dollar text-primary mb-3" style={{ fontSize: '4rem' }}></i>
<div className="text-uppercase" style={{ fontSize: '1.4rem' }}>Best Prices</div>
<div>Huge selections of items</div>
</Col>
{/* Trusted Store */}
<Col md={3} className="text-center mb-4">
<i className="bi bi-check-circle text-primary mb-3" style={{ fontSize: '4rem' }}></i>
<div className="text-uppercase" style={{ fontSize: '1.4rem' }}>Trusted Store</div>
<div>Tons of positive feedbacks</div>
</Col>
</Row>
<ProductCatalog />
</Container>
<Footer />
</>
);
};
export default Home;

@ -0,0 +1,160 @@
// Login.js
import React, { useState, useEffect, useContext } from 'react';
import { Form, Button, Alert, Container, Col } from 'react-bootstrap';
import { toast } from 'react-toastify';
import { Navigate, useNavigate } from 'react-router-dom';
import UserContext from '../UserContext';
export default function Login() {
const { user, setUser } = useContext(UserContext);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isActive, setIsActive] = useState(false);
const [loginError, setLoginError] = useState(null);
const navigate = useNavigate();
const authenticate = async (e) => {
e.preventDefault();
try {
const response = await fetch(`${process.env.REACT_APP_API_URL}/user/authenticate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email,
password: password,
}),
});
const data = await response.json();
if (response.ok && data.token) {
localStorage.setItem('token', data.token);
retrieveUserDetails(data.token);
setLoginError(null);
setIsActive(false);
toast.success('Login successful!', {
position: 'bottom-right',
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
if (data.isAdmin) {
navigate('/dashboard');
} else {
navigate('/');
}
} else {
setLoginError('Authentication failed. Check your login details and try again.');
toast.error('Authentication failed. Check your login details and try again.', {
position: 'bottom-right',
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
}
} catch (error) {
console.error('Error during login:', error);
setLoginError('An unexpected error occurred. Please try again later.');
toast.error('An unexpected error occurred. Please try again later.', {
position: 'bottom-right',
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
}
setEmail('');
setPassword('');
};
const retrieveUserDetails = async (token) => {
try {
const response = await fetch(`${process.env.REACT_APP_API_URL}/user/details`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
if (response.ok) {
setUser({
id: data._id,
isAdmin: data.isAdmin,
});
} else {
console.error('Error retrieving user details:', data);
}
} catch (error) {
console.error('Error during user details retrieval:', error);
}
};
useEffect(() => {
setIsActive(email !== '' && password !== '');
}, [email, password]);
return (
user.id !== null ? (
<Navigate to="/" />
) : (
<Container className="my-5">
<Col md={{ span: 6, offset: 3 }}>
<Form onSubmit={(e) => authenticate(e)} className="shadow p-5 mb-5 bg-white rounded">
<h1 className="text-center mb-4">Login</h1>
{loginError && <Alert variant="danger">{loginError}</Alert>}
<Form.Group controlId="userEmail">
<Form.Label>Email address</Form.Label>
<Form.Control
type="email"
placeholder="Enter email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</Form.Group>
<Form.Group controlId="password">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</Form.Group>
{isActive ? (
<Button variant="primary" type="submit" id="submitBtn">
Submit
</Button>
) : (
<Button variant="danger" type="submit" id="submitBtn" disabled>
Submit
</Button>
)}
</Form>
</Col>
</Container>
)
);
}

@ -0,0 +1,22 @@
import { useContext, useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import UserContext from '../UserContext';
export default function Logout() {
const { unsetUser, setUser } = useContext(UserContext);
unsetUser();
useEffect(() => {
setUser({
id: null,
isAdmin: null
})
})
// Redirect back to login
return (
<Navigate to='/login' />
)
}

@ -0,0 +1,157 @@
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { Container, Row, Col, Image, Button } from 'react-bootstrap';
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { BsDash, BsPlus } from 'react-icons/bs';
import './productPage.css';
import { jwtDecode } from 'jwt-decode';
const ProductPage = () => {
const { productId } = useParams();
const [product, setProduct] = useState(null);
const [quantity, setQuantity] = useState(1);
const [error, setError] = useState(null);
useEffect(() => {
const fetchProduct = async () => {
try {
const response = await fetch(`${process.env.REACT_APP_API_URL}/product/${productId}`);
if (!response.ok) {
throw new Error('Failed to fetch product');
}
const data = await response.json();
setProduct(data);
setError(null);
} catch (error) {
console.error('Error fetching product by ID:', error);
setError('Error fetching product. Please try again.');
}
};
fetchProduct();
}, [productId]);
const handleAddToCart = async () => {
try {
const token = localStorage.getItem("token");
const decoded = jwtDecode(token);
const response = await fetch(
`${process.env.REACT_APP_API_URL}/cart/add-to-cart`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
userId: decoded.userId,
productId,
quantity,
}),
}
);
const data = await response.json();
if (response.ok) {
toast.success(data.message, {
position: "bottom-right",
autoClose: 2000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
} else {
toast.error(data.error || "Error adding to cart", {
position: "bottom-right",
autoClose: 2000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
}
} catch (error) {
console.error("Error adding to cart:", error);
}
};
const handleQuantityDecrease = () => {
if (quantity > 1) {
setQuantity(quantity - 1);
}
};
const handleQuantityIncrease = () => {
setQuantity(quantity + 1);
};
if (error) {
return (
<Container>
<Row>
<Col>
<h2>Error</h2>
<p>{error}</p>
</Col>
</Row>
</Container>
);
}
if (!product) {
return (
<Container>
<Row>
<Col>
<p>Loading...</p>
</Col>
</Row>
</Container>
);
}
return (
<Container className="mt-4">
<Row>
<Col md={6} className="d-flex justify-content-center">
<Image src={product.image || 'placeholder-image-url'} alt={product.name} fluid className="product-image" />
</Col>
<Col md={6} className="product-details">
<h2 className="product-title">{product.name}</h2>
<p className="product-id">ID: {product._id}</p>
<div className="product-description">
<p><strong>Description:</strong></p>
<p>{product.description}</p>
{/* Add more bullet points as needed */}
</div>
<p className="product-price">
<p><strong>Price:</strong></p>
PHP {new Intl.NumberFormat('en-PH', { style: 'currency', currency: 'PHP' }).format(product.price)}
</p>
<div className="quantity-selector">
<Button variant="outline-primary" onClick={handleQuantityDecrease}>
<BsDash className="rounded"/>
</Button>
<span className="quantity-value">{quantity}</span>
<Button variant="outline-primary" onClick={handleQuantityIncrease}>
<BsPlus />
</Button>
</div>
<div className="button-group mt-3">
<Button variant="primary" onClick={handleAddToCart}>
Add to Cart
</Button>
</div>
</Col>
</Row>
</Container>
);
};
export default ProductPage;

@ -0,0 +1,89 @@
// Profile.js
import React, { useContext, useEffect, useState } from 'react';
import { Row, Col, Button, Container } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import UserContext from '../UserContext';
import { Navigate } from 'react-router-dom';
export default function Profile() {
const { user } = useContext(UserContext);
const [profileData, setProfileData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchUserProfile = async () => {
try {
if (user && user.id) {
const token = localStorage.getItem('token');
const response = await fetch(
`${process.env.REACT_APP_API_URL}/user/details`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (response.ok) {
const data = await response.json();
setProfileData(data);
} else {
console.error('Failed to fetch user profile:', response.status);
}
}
} catch (error) {
console.error('Error fetching user profile:', error);
} finally {
setLoading(false);
}
};
fetchUserProfile();
}, [user]);
if (!user || user.id === null) {
return <Navigate to="/" />;
}
return (
<Container>
<Row>
<Col md={6} className="shadow-sm p-5 bg-white">
<h1 className="my-5">Profile</h1>
{loading ? (
<p>Loading profile...</p>
) : profileData ? (
<>
<h2 className="mt-3">
{profileData.firstName} {profileData.lastName}
</h2>
<hr />
<p>Email: {profileData.email}</p>
<p>Mobile Number: {profileData.mobileNo}</p>
<p>Account Type: {profileData.isAdmin ? 'Admin' : 'User'}</p>
<h3>Orders</h3>
{profileData.orderedProducts.length > 0 ? (
<ul>
{profileData.orderedProducts.map((order, index) => (
<li key={index}>
Order {index + 1}: {order.totalAmount}
</li>
))}
</ul>
) : (
<p>No orders yet.</p>
)}
<Link to="/update-profile">
<Button variant="primary">Update Profile</Button>
</Link>
</>
) : (
<p>No profile data available.</p>
)}
</Col>
</Row>
</Container>
);
}

@ -0,0 +1,19 @@
// Register.js
import React from 'react';
import { Container, Col } from 'react-bootstrap';
import RegisterForm from '../components/Register';
const Register = () => {
return (
<Container className="my-5">
<Col lg={{ span: 6, offset: 3 }}>
{/* Your other content */}
<RegisterForm />
{/* Your other content */}
</Col>
</Container>
);
};
export default Register;

@ -0,0 +1,16 @@
// TokenExpired.js
import React from 'react';
import { Container, Row, Col } from 'react-bootstrap';
const TokenExpired = () => (
<Container className="mt-5">
<Row>
<Col className="text-center">
<h1>Token Expired</h1>
<p>Your session has expired. Please log in again.</p>
</Col>
</Row>
</Container>
);
export default TokenExpired;

@ -0,0 +1,79 @@
/* ProductPage.css */
.product-container {
display: flex;
justify-content: space-around;
padding: 20px;
}
.product-image {
max-width: 80%; /* Adjust image size */
}
.product-details {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 20px;
}
.product-title {
margin-bottom: 10px;
}
.product-id,
.product-price {
margin-top: 5px;
}
.product-description {
margin-top: 10px;
}
.product-description strong {
font-size: 1.2em;
}
.product-description ul {
list-style-type: disc;
margin-left: 20px;
}
.product-price {
text-decoration: none; /* Remove the line in the middle */
}
/* Quantity Controls Styling */
.input-group-prepend,
.input-group-append {
height: 100%;
}
.input-group-prepend button,
.input-group-append button {
height: 100%;
}
/* productPage.css */
.quantity-selector {
display: flex;
align-items: center;
margin-top: 10px;
}
.quantity-selector button {
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
width: 40px;
height: 40px;
margin: 0 5px;
}
.quantity-value {
font-size: 18px;
margin: 0 10px;
}

@ -0,0 +1,9 @@
// cartActions.js
export const addToCart = (productId, quantity) => {
return {
type: 'ADD_TO_CART',
payload: { productId, quantity },
};
};

@ -0,0 +1,34 @@
// src/redux/cartReducer.js
const initialState = {
cartItems: [],
};
const cartReducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_CART':
const { productId } = action.payload;
const existingItem = state.cartItems.find((item) => item.productId === productId);
if (existingItem) {
return {
...state,
cartItems: state.cartItems.map((item) =>
item.productId === productId ? { ...item, quantity: item.quantity + 1 } : item
),
};
} else {
return {
...state,
cartItems: [...state.cartItems, { productId, quantity: 1 }],
};
}
// Add more cases for other actions if needed
default:
return state;
}
};
export default cartReducer;

@ -0,0 +1,8 @@
// src/redux/store.js
import { createStore } from 'redux';
import cartReducer from './cartReducer';
const store = createStore(cartReducer);
export default store;

@ -0,0 +1,17 @@
.banner-container {
background: url('path/to/your/image.jpg') center/cover;
height: 300px; /* Adjust the height as needed */
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.banner-content {
text-align: center;
color: #fff;
background-color: rgba(0, 0, 0, 0.5); /* Background color with low opacity */
padding: 20px;
max-width: 600px; /* Adjust the max-width as needed */
}

@ -1,22 +1,31 @@
import { useContext, useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import UserContext from '../UserContext';
// src/pages/Logout.js
import React, { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
export default function Logout() {
const Logout = () => {
const navigate = useNavigate();
const { unsetUser, setUser } = useContext(UserContext);
useEffect(() => {
// Clear the token from local storage
localStorage.removeItem('accessToken');
unsetUser();
// Display a success notification
toast.success('Logout successful!', {
position: 'top-right',
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
useEffect(() => {
setUser({
id: null,
isAdmin: null
})
})
// Redirect to the login page
navigate('/login');
}, [navigate]);
// Redirect back to login
return (
<Navigate to='/login' />
)
}
return <div>Logging out...</div>;
};
export default Logout;

@ -1,83 +1,44 @@
import { useEffect, useContext, useState } from 'react';
import React, { useContext } from 'react';
import { Row, Col } from 'react-bootstrap';
import UserContext from '../UserContext';
import { Navigate } from 'react-router-dom';
import ResetPassword from '../components/ResetPassword';
export default function Profile() {
const { user } = useContext(UserContext);
const [profileData, setProfileData] = useState(null);
useEffect(() => {
if (user && user.id) {
// Fetch user profile data when the component mounts
fetchUserProfile();
}
}, [user]);
const fetchUserProfile = async () => {
try {
const response = await fetch(
`${process.env.REACT_APP_API_URL}/users/details`,
{
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
}
);
if (response.ok) {
const data = await response.json();
console.log('Profile Data:', data);
if (data && data.firstName && data.lastName && data.email && data.mobileNo) {
setProfileData(data);
} else {
console.error('Invalid user profile data received from the server.');
}
} else {
console.error('Failed to fetch user profile');
}
} catch (error) {
console.error('Error fetching user profile:', error);
}
};
if (!user || user.id === null) {
return <Navigate to="/courses" />;
}
// Assume that the Profile component should only be rendered when the user is authenticated
// If this assumption is incorrect, you might need to handle authentication differently
return (
<>
<Row>
<Col className="p-5 bg-primary text-white">
<h1 className="my-5">Profile</h1>
{profileData ? (
<>
<h2 className="mt-3">
{profileData.firstName} {profileData.lastName}
</h2>
<hr />
<h4>Contacts</h4>
<ul>
<li>
Name: {profileData.firstName} {profileData.lastName}
</li>
<li>Email: {profileData.email}</li>
<li>Mobile No: {profileData.mobileNo}</li>
</ul>
</>
) : (
<p>Loading profile...</p>
)}
</Col>
</Row>
<Row className="pt-4 mt-4">
<Col>
<ResetPassword />
</Col>
</Row>
<Row>
<Col className="p-5 bg-primary text-white">
<h1 className="my-5">Profile</h1>
{user && (
<>
<h2 className="mt-3">
{user.firstName} {user.lastName}
</h2>
<hr />
<h4>Contacts</h4>
<ul>
<li>
Name: {user.firstName} {user.lastName}
</li>
<li>Email: {user.email}</li>
{/* Assuming user.mobileNo is the correct property */}
<li>Mobile No: {user.mobileNo}</li>
</ul>
</>
)}
</Col>
</Row>
<Row className="pt-4 mt-4">
<Col>
<ResetPassword />
</Col>
</Row>
</>
);
}

Loading…
Cancel
Save