Capstone-3 Update

master
Ron Reciproco 9 months ago
parent cbbce1bd1a
commit 12b87b8d2f

@ -0,0 +1,4 @@
PORT=4000
MONGO_URL=mongodb+srv://ronreciproco123:admin123@cluster0.9ao1xec.mongodb.net
SECRET_SAUCE=cornhub
BASE_URL=http://localhost:4000

@ -0,0 +1,6 @@
{
"tabWidth": 4,
"useTabs": true,
"semi": false,
"trailingComma": "es5"
}

@ -0,0 +1,74 @@
const jwt = require("jsonwebtoken");
require("dotenv").config();
// Middleware for verifying and authenticating JWT token
exports.authenticateToken = (req, res, next) => {
try {
// Extract the token from the Authorization header
const authHeader = req.header('Authorization');
if (!authHeader) {
return res.status(401).json({ message: 'Unauthorized. Token not provided.' });
}
const token = authHeader.replace('Bearer ', '');
console.log('Token:', token); // Log the token to the console
const decoded = jwt.verify(token, process.env.SECRET_SAUCE);
req.user = decoded; // Attach the decoded information to the request for future use
// Debugging purposes
console.log('userId:', decoded.userId);
console.log('isAdmin:', decoded.isAdmin);
next();
} catch (error) {
console.log('User token expired.');
if (error instanceof jwt.TokenExpiredError) {
return res.status(401).json({ message: 'Unauthorized. Token has expired.' });
} else {
return res.status(401).json({ message: 'Unauthorized. Invalid token.' });
}
}
};
// Function to generate a JWT token
exports.generateToken = (userId, email, isAdmin) => {
return jwt.sign({ userId, email, isAdmin }, process.env.SECRET_SAUCE, {
expiresIn: "3h",
})
}
// Middleware to verify admin status
exports.verifyAdmin = (req, res, next) => {
if (req.user && req.user.isAdmin) {
next()
} else {
return res
.status(403)
.json({ message: "Action Forbidden. User is not an admin." })
}
}
exports.verifyUser = (req, res, next) => {
const authenticatedUserId = req.user.userId;
const requestedUserId = req.body.userId;
if (authenticatedUserId && authenticatedUserId === requestedUserId) {
// User is authenticated, and the requested userId matches the authenticated userId
next();
} else {
return res.status(403).json({
message: "Permission denied.",
});
}
};
exports.extractAnonymousUserId = (req, res, next) => {
req.anonymousUserId = req.headers['x-anonymous-user-id'];
next();
};

@ -0,0 +1,101 @@
const Cart = require('../model/Cart');
exports.addToCart = async (req, res) => {
const { userId } = req.body;
const { productId, quantity } = req.body;
try {
let cart = await Cart.findOne({ userId }).populate('items.product');
if (!cart) {
cart = new Cart({
userId,
items: [{ product: productId, quantity }],
});
} else {
const existingItem = cart.items.find(item => item.product._id.toString() === productId);
if (existingItem) {
existingItem.quantity += quantity;
} else {
cart.items.push({ product: productId, quantity });
}
}
await cart.save();
console.log(`Product with ID ${productId} added to cart for user with ID ${userId}`);
res.json({ message: 'Item added to cart successfully' });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
}
};
exports.updateCart = async (req, res) => {
const { userId, product, quantity } = req.body;
try {
let cart = await Cart.findOne({ userId });
if (!cart) {
return res.status(404).json({ error: 'Cart not found for the user' });
}
const existingItem = cart.items.find(item => item.product.toString() === product);
if (!existingItem) {
return res.status(404).json({ error: 'Product not found in the cart' });
}
existingItem.quantity = quantity;
await cart.save();
res.json({ message: 'Quantity updated successfully' });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
}
};
exports.removeFromCart = async (req, res) => {
const { userId, productId } = req.body;
try {
let cart = await Cart.findOne({ userId });
if (!cart) {
return res.status(404).json({ error: 'Cart not found for the user' });
}
// Remove the product from the cart
cart.items = cart.items.filter(item => item.productId.toString() !== productId);
await cart.save();
res.json({ message: 'Product removed from cart successfully' });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
}
};
// Assuming you have middleware that adds user information to the request object
exports.getCartDetails = async (req, res) => {
const { userId } = req.params;
try {
const cart = await Cart.findOne({ userId });
if (!cart) {
return res.status(404).json({ message: 'Cart not found for the user' });
}
return res.json({ items: cart.items });
} catch (error) {
console.error('Error fetching cart details:', error);
res.status(500).json({ error: 'Internal Server Error' });
}
};

@ -0,0 +1,227 @@
const Cart = require('../model/Cart');
const { v4: uuidv4 } = require('uuid');
const jwt = require('jsonwebtoken');
require("dotenv").config()
exports.addToCart = async (req, res) => {
let { userId, anonymousUserId } = req.body;
const { productId, quantity } = req.body;
if (!userId && !anonymousUserId) {
return res.status(400).json({ error: 'User ID or anonymous ID is required' });
}
try {
if (!userId && anonymousUserId) {
// If anonymousUserId is present, create a separate entry in the database
const cart = new Cart({
anonymousUserId,
items: [{ product: productId, quantity }],
});
await cart.save();
console.log(`Product with ID ${productId} added to cart for anonymous user with ID ${anonymousUserId}`);
return res.json({ message: 'Item added to cart successfully', userId: anonymousUserId });
}
// If userId is provided, use it for the database query
let cart = await Cart.findOne({ userId }).populate('items.product');
if (!cart) {
cart = new Cart({
userId,
items: [{ product: productId, quantity }],
});
} else {
const existingItem = cart.items.find(item => item.product._id.toString() === productId);
if (existingItem) {
existingItem.quantity += quantity;
} else {
cart.items.push({ product: productId, quantity });
}
}
await cart.save();
console.log(`Product with ID ${productId} added to cart for user with ID ${userId}`);
res.json({ message: 'Item added to cart successfully', userId });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
}
};
exports.updateCart = async (req, res) => {
let { userId } = req.body;
const { updates } = req.body;
// If userId is 'anonymousUserId', use a constant user ID
if (userId === 'anonymousUserId') {
userId = 'constantAnonymousUserId';
}
try {
if (!userId) {
return res.status(400).json({ error: 'User ID is required' });
}
let cart = await Cart.findOne({ userId });
if (!cart) {
return res.status(404).json({ error: 'Cart not found for the user' });
}
updates.forEach((update) => {
const { product, quantity } = update;
const existingItem = cart.items.find(
(item) => item.product.toString() === product.toString()
);
if (!existingItem) {
return res.status(404).json({ error: 'Product not found in the cart' });
}
if (quantity === 0) {
// If the quantity is 0, remove the product from the cart
cart.items = cart.items.filter(
(item) => item.product.toString() !== product.toString()
);
} else {
existingItem.quantity = quantity;
}
});
await cart.save();
res.json({ message: 'Cart updated successfully', userId });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
}
};
exports.removeFromCart = async (req, res) => {
let { userId } = req.body;
const { product } = req.body;
// If userId is 'anonymousUserId', use a constant user ID
if (userId === 'anonymousUserId') {
userId = 'constantAnonymousUserId';
}
try {
if (!userId) {
return res.status(400).json({ error: 'User ID is required' });
}
let cart = await Cart.findOne({ userId });
if (!cart) {
return res.status(404).json({ error: 'Cart not found for the user' });
}
// Remove the product from the cart
cart.items = cart.items.filter(item => item.product.toString() !== product);
await cart.save();
res.json({ message: 'Product removed from cart successfully', userId });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
}
};
// Main getCartDetails function that handles both cases
exports.getCartDetails = async (req, res) => {
const { userId } = req.params;
try {
const cart = await Cart.findOne({ userId });
if (!cart) {
return res.status(404).json({ message: 'Cart not found for the user' });
}
return res.json({ items: cart.items });
} catch (error) {
console.error('Error fetching cart details:', error);
res.status(500).json({ error: 'Internal Server Error' });
}
};
// Function to get cart details by anonymous user ID
const getCartDetailsByAnonymousUserId = async (userId) => {
try {
const cart = await Cart.findByAnonymousUserId(userId);
console.log('Cart found:', cart);
if (!cart) {
return { status: 404, message: "Cart not found for the user" };
}
return cart;
} catch (error) {
throw error;
}
};
// New function for anonymous user cart details
exports.getCartDetailsForAnonymousUser = async (req, res) => {
try {
const anonymousUserId = req.params.anonymousUserId;
console.log("Received anonymousUserId:", anonymousUserId);
if (anonymousUserId) {
const cartDetails = await getCartDetailsByAnonymousUserId(anonymousUserId);
if (!cartDetails) {
return res.status(404).json({ message: "Cart not found for the user" });
}
res.json({ items: cartDetails.items });
} else {
res.status(400).json({ error: "Invalid request" });
}
} catch (error) {
console.error("Error fetching cart details for anonymous user:", error);
res.status(500).json({ error: "Internal Server Error" });
}
};
exports.clearCart = async (req, res) => {
try {
const token = req.headers.authorization.split(' ')[1];
const anonymousUserId = req.headers['x-anonymous-user-id'];
// Check if the request is from an authenticated user
if (token) {
const decoded = jwt.verify(token, process.env.SECRET_SAUCE) // Replace 'your-secret-key' with your actual secret key
const userId = decoded.userId;
// Clear the cart for the authenticated user
await Cart.findOneAndDelete({ userId });
} else if (anonymousUserId) {
// Clear the cart for the anonymous user
await Cart.findOneAndDelete({ anonymousUserId });
} else {
return res.status(400).json({ error: 'Invalid request. Missing user information.' });
}
return res.status(200).json({ message: 'Cart cleared successfully.' });
} catch (error) {
console.error('Error clearing cart:', error);
return res.status(500).json({ error: 'Internal server error.' });
}
};

@ -0,0 +1,67 @@
const User = require("../model/User")
exports.createOrder = async (req, res) => {
try {
const { userId, products, totalAmount } = req.body
const user = await User.findById(userId)
if (!user) {
return res.status(404).json({ message: "User not found" })
}
// Check if the user is an admin
if (user.isAdmin) {
return res
.status(403)
.json({ message: "Admins cannot create orders" })
}
const newOrder = {
products: products,
totalAmount: totalAmount,
purchaseOn: Date.now(),
}
user.orderedProducts.push(newOrder)
await user.save()
res.status(201).json({
message: "Order created successfully",
order: newOrder,
})
} catch (error) {
console.error(error)
res.status(500).json({ message: "Internal Server Error" })
}
}
// Retrieve authenticated user's orders
exports.getOrders = async (req, res) => {
try {
const { userId } = req.body
const user = await User.findById(userId)
if (!user) {
return res.status(404).json({ message: "User not found" })
}
// Return the user details in the response
res.send({ orderedProducts: user.orderedProducts })
} catch (error) {
console.error(error)
res.status(500).json({ message: "Internal server error" })
}
}
exports.getAllOrders = async (req, res) => {
try {
const orders = await User.find({ "orderedProducts.0": { $exists: true } }, "orderedProducts");
res.status(200).json({ success: true, data: orders });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
};

@ -0,0 +1,232 @@
const Product = require("../model/Product")
const fs = require('fs');
require("dotenv").config();
const path = require('path');
exports.editProduct = async (req, res) => {
try {
const productId = req.params.productId;
const updatedProductData = req.body;
// Validate or sanitize the input data if needed
// Find the product by ID and update it
const updatedProduct = await Product.findByIdAndUpdate(
productId,
updatedProductData,
{ new: true } // This option ensures you get the updated product in the response
);
if (!updatedProduct) {
return res.status(404).json({ message: 'Product not found' });
}
// Send the updated product in the response
res.status(200).json(updatedProduct);
} catch (error) {
console.error('Error editing product:', error);
res.status(500).json({ message: 'Internal server error' });
}
};
exports.productTag = async (req, res) => {
try {
const { tag } = req.query;
// If no specific tag is provided, return all products
const filter = tag ? { tags: tag } : {};
// Fetch products from the database based on the filter
const products = await Product.find(filter);
res.json(products); // Ensure this line sends a JSON array, even if empty ([] for no products).
} catch (error) {
console.error('Error fetching tagged products:', error);
res.status(500).json({ error: 'Internal Server Error' });
}
}
exports.createProduct = async (req, res) => {
try {
const { name, description, price, isActive } = req.body;
if (req.file) {
const imagePath = req.file.path;
const newProduct = new Product({
name,
description,
price,
isActive,
image: `http://localhost:4000/uploads/${path.basename(imagePath)}`, // Store the image link
});
await newProduct.save();
return res.status(201).json(newProduct);
} else {
return res.status(400).json({ error: 'Image is required' });
}
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
}
};
exports.deleteProduct = async (req, res) => {
const productId = req.params.id;
try {
// Find the product by ID
const product = await Product.findById(productId);
if (!product) {
return res.status(404).json({ message: 'Product not found' });
}
// Remove the associated image file from the "uploads" folder
if (product.image) {
const imagePath = path.join(__dirname, '../uploads', path.basename(product.image));
fs.unlinkSync(imagePath);
}
// Use deleteOne method to delete the product
await product.deleteOne();
res.json({ message: 'Product deleted successfully' });
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Internal Server Error' });
}
};
// Controller function for retrieving all products (accessible to both admin and normal user)
exports.getAllProducts = async (req, res) => {
try {
const products = await Product.find()
res.status(200).json(products)
} catch (error) {
console.error(error)
res.status(500).json({ message: "Internal server error" })
}
}
// Controller function for retrieving all active products (accessible to both admin and normal user)
exports.getActiveProducts = async (req, res) => {
try {
const activeProducts = await Product.find({ isActive: true })
res.status(200).json(activeProducts)
} catch (error) {
console.error(error)
res.status(500).json({ message: "Internal server error" })
}
}
// Controller to retrieve a single product by id
exports.getProductById = async (req, res) => {
const productId = req.params.id;
try {
const product = await Product.findById(productId);
if (!product) {
return res.status(404).json({ message: 'Product not found' });
}
res.status(200).json(product);
} catch (error) {
console.error('Error fetching product by ID:', error);
res.status(500).json({ message: 'Internal server error' });
}
};
// Controller function to update product information
exports.updateProduct = async (req, res) => {
const productId = req.params.id;
const { name, description, price, isActive } = req.body;
try {
// Check if the product exists
const existingProduct = await Product.findById(productId);
if (!existingProduct) {
return res.status(404).json({ message: 'Product not found' });
}
// Update product information
existingProduct.name = name;
existingProduct.description = description;
existingProduct.price = price;
existingProduct.isActive = isActive;
// Save the updated product
const updatedProduct = await existingProduct.save();
res.status(200).json(updatedProduct);
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Internal Server Error' });
}
};
exports.activateProduct = async (req, res) => {
const { productId } = req.params;
try {
// Find the product by ID
const product = await Product.findById(productId);
if (!product) {
return res.status(404).json({ message: 'Product not found' });
}
// Check if the product is already active
if (product.isActive) {
return res.status(400).json({ message: 'Product is already active' });
}
// Activate the product
product.isActive = true;
// Save the updated product
await product.save();
res.json({ message: 'Product activated successfully', product });
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Internal Server Error' });
}
};
exports.archiveProduct = async (req, res) => {
const { productId } = req.params;
try {
// Find the product by ID
const product = await Product.findById(productId);
if (!product) {
return res.status(404).json({ message: 'Product not found' });
}
// Check if the product is already active
if (!product.isActive) {
return res.status(400).json({ message: 'Product is already in Archive' });
}
// Activate the product
product.isActive = false;
// Save the updated product
await product.save();
res.json({ message: 'Product successfuly archived', product });
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Internal Server Error' });
}
};

@ -0,0 +1,535 @@
const jwt = require("jsonwebtoken")
const bcrypt = require("bcrypt")
const faker = require("faker")
const User = require("../model/User")
const Cart = require('../model/Cart');
const Address = require('../model/Address');
const PromoCode = require("../model/PromoCode")
const auth = require("../auth")
const { validationResult } = require("express-validator")
// Secret Sauce
require("dotenv").config()
exports.deletePromoCode = async (req, res) => {
try {
const promoCodeId = req.params.id
// Use Mongoose to find and remove the promo code
const deletedPromoCode = await PromoCode.findOneAndDelete({
_id: promoCodeId,
})
if (!deletedPromoCode) {
return res.status(404).json({ message: "Promo code not found" })
}
res.status(200).json({ message: "Promo code deleted successfully" })
} catch (error) {
console.error("Error deleting promo code:", error)
res.status(500).json({
error: "An error occurred while deleting promo code",
})
}
}
exports.showPromoCodes = async (req, res) => {
try {
// Fetch all promo codes from the database
const promoCodes = await PromoCode.find()
res.status(200).json({ promoCodes })
} catch (error) {
console.error("Error fetching promo codes:", error)
res.status(500).json({
error: "An error occurred while fetching promo codes",
})
}
}
exports.createPromoCode = async (req, res) => {
try {
const { code, discountAmount, expirationDate } = req.body
// Check if the promo code already exists
const existingPromoCode = await PromoCode.findOne({ code })
if (existingPromoCode) {
return res
.status(400)
.json({ message: "Promo code already exists" })
}
// Create a new promo code
const newPromoCode = new PromoCode({
code,
discountAmount,
expirationDate,
// Add more fields as needed
})
// Save the promo code to the database
await newPromoCode.save()
res.status(201).json({ message: "Promo code created successfully" })
} catch (error) {
console.error("Error creating promo code:", error)
res.status(500).json({ message: "Internal server error" })
}
}
exports.applyPromoCode = async (req, res) => {
try {
// Validate request using express-validator
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Retrieve promo code from request body
const { promoCode } = req.body;
// Check if promo code exists in the database
const existingPromoCode = await PromoCode.findOne({ code: promoCode });
if (!existingPromoCode) {
// If promo code does not exist, return an error response
return res.status(400).json({ message: "Invalid promo code" });
}
// Log the discount amount
console.log("Discount Amount:", existingPromoCode.discountAmount);
// Additional logic to calculate discount and update total price
// Assuming totalPrice is provided in the request or can be fetched from somewhere
const totalPrice = 100; // Replace this with actual total price
// Calculate discounted amount based on the discountAmount
const discountAmount = existingPromoCode.discountAmount;
// Subtract discounted amount from the current total price
const updatedTotalPrice = totalPrice - discountAmount;
// Log the updated total price
console.log("Updated Total Price:", updatedTotalPrice);
// Return success response
return res.status(200).json({
message: "Promo code applied successfully",
promoCode: existingPromoCode.code,
discountAmount,
updatedTotalPrice,
});
} catch (error) {
console.error("Error applying promo code:", error);
return res.status(500).json({ message: "Internal server error" });
}
};
exports.resetPassword = async (req, res) => {
const { email, newPassword, resetToken } = req.body
try {
// Check if the reset token is valid (you may store reset tokens in your database)
const user = await User.findOne({ email, resetToken })
if (!user) {
return res
.status(400)
.json({ message: "Invalid reset token or email." })
}
// Check if the reset token is still valid (e.g., not expired)
// Reset the password
user.password = newPassword
user.resetToken = null // Invalidate the reset token after use
await user.save()
return res.status(200).json({ message: "Password reset successful." })
} catch (error) {
console.error("Error resetting password:", error)
return res.status(500).json({ message: "Internal server error." })
}
}
exports.userDetails = async (req, res) => {
try {
// Fetch user details from the database using the authenticated user ID
const user = await User.findById(req.user.userId)
if (!user) {
return res.status(404).json({ message: "User not found" })
}
// Check if the token is expired
const token = req.header("Authorization").replace("Bearer ", "")
const decoded = jwt.verify(token, process.env.SECRET_SAUCE)
if (decoded.exp < Date.now() / 1000) {
// Token is expired
return res
.status(401)
.json({ message: "Unauthorized. Token has expired." })
}
// Send the user details in the response
res.json({
_id: user._id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
mobileNo: user.mobileNo,
isAdmin: user.isAdmin,
orderedProducts: user.orderedProducts,
// Add other user details you want to include
})
} catch (error) {
console.error("Error fetching user details:", error)
if (error.name === "TokenExpiredError") {
return res
.status(401)
.json({ message: "Unauthorized. Token has expired." })
}
res.status(500).json({ message: "Internal server error" })
}
}
exports.checkEmail = async (req, res) => {
try {
const { email } = req.body
const existingUser = await User.findOne({ email })
res.json({ exists: !!existingUser })
} catch (error) {
console.error("Error checking email:", error)
res.status(500).json({ error: "Internal Server Error" })
}
}
// Controller function for user registration
exports.registerUser = async (req, res) => {
try {
const { email, password, firstName, lastName } = req.body
// If firstName and lastName are not provided, generate default values using faker
const autoGeneratedFirstName = firstName || faker.name.firstName()
const autoGeneratedLastName = lastName || faker.name.lastName()
// Hash the password before saving it
const hashedPassword = await bcrypt.hash(password, 10)
// Set mobileNo to null by default
const newUser = new User({
email,
password: hashedPassword,
firstName: autoGeneratedFirstName,
lastName: autoGeneratedLastName,
mobileNo: "",
})
await newUser.save()
res.status(201).json({
message:
"User registered successfully. To update account details, access user/update",
})
} catch (error) {
console.error(error)
res.status(500).json({ message: "Internal server error" })
}
}
// Controller function for user authentication
exports.authenticateUser = async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const passwordMatch = await bcrypt.compare(password, user.password);
if (!passwordMatch) {
return res.status(401).json({ message: 'Invalid credentials' });
}
// Check if there are existing cart items with anonymousUserId
const anonymousUserId = req.headers['x-anonymous-user-id'];
const existingCart = await Cart.findOne({ userId: anonymousUserId }).populate('items.product');
if (existingCart) {
// Update the userId for existing cart items
existingCart.userId = user._id; // Assuming you have the user object
await existingCart.save();
}
// Generate JWT token using the function from auth.js
const token = auth.generateToken(user._id, user.email, user.isAdmin);
// Decode JWT token to get expiration time
const decodedToken = jwt.decode(token);
if (decodedToken) {
const expiration = new Date(decodedToken.exp * 1000); // Convert seconds to milliseconds
// Log token expiration
console.log(`Authenticate success. Token will expire on: ${expiration}`);
} else {
console.error('Error decoding token');
}
// Return user details and token
res.status(200).json({
userId: user._id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
mobileNo: user.mobileNo,
isAdmin: user.isAdmin,
token: token,
});
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Internal server error' });
}
};
exports.updateUserData = async (req, res) => {
try {
const {
userId,
newEmail,
newFirstName,
newLastName,
newPassword,
newMobileNo,
} = req.body
const userIdFromToken = req.user.userId
if (userIdFromToken !== userId) {
return res.status(403).json({
message:
"Permission denied. You can only update your own data.",
})
}
const user = await User.findById(userId)
if (!user) {
return res.status(404).json({ message: "User not found" })
}
// Update email if provided
if (newEmail) {
user.email = newEmail
}
// Update firstName if provided
if (newFirstName) {
user.firstName = newFirstName
}
// Update lastName if provided
if (newLastName) {
user.lastName = newLastName
}
// Update password if provided
if (newPassword) {
const hashedPassword = await bcrypt.hash(newPassword, 10)
user.password = hashedPassword
}
// Update mobileNo if provided
if (newMobileNo) {
user.mobileNo = newMobileNo
}
// Save the updated user data
await user.save()
// Fetch the updated user details
const updatedUser = await User.findById(userId)
// Return the complete updated user details in the response
res.status(200).json({
message: "User data updated successfully",
user: {
_id: updatedUser._id,
email: updatedUser.email,
isAdmin: updatedUser.isAdmin,
firstName: updatedUser.firstName,
lastName: updatedUser.lastName,
mobileNo: updatedUser.mobileNo,
},
})
} catch (error) {
console.error(error)
res.status(500).json({ message: "Internal server error" })
}
}
exports.getUserDetails = async (req, res) => {
try {
const { userId } = req.body
const user = await User.findById(userId)
if (!user) {
return res.status(404).json({ message: "User not found" })
}
// Create a new object with only the desired properties (excluding password)
const userWithoutPassword = {
_id: user._id,
username: user.username,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
orderedProducts: user.orderedProducts,
// Add other properties you want to include in the response
}
// Return the user details in the response
res.status(200).json({
user: userWithoutPassword,
})
} catch (error) {
console.error(error)
res.status(500).json({ message: false })
}
}
exports.setAdmin = async (req, res) => {
try {
const { userId } = req.body // assuming userId is sent in the request body
const user = await User.findById(userId)
if (!user) {
return res.status(404).json({ error: "User not found." })
}
user.isAdmin = true
await user.save()
res.json({ message: "User is now an admin." })
} catch (error) {
console.error(error)
res.status(500).json({ error: "Internal server error" })
}
}
exports.getAddresses = async (req, res) => {
try {
const userId = req.body.userId; // Assuming you include userId in the URL parameters
// Validate the presence of the user ID
if (!userId) {
return res.status(400).json({ error: "User ID is required" });
}
// Fetch addresses for the specified user ID
const addresses = await Address.find({ userId });
res.status(200).json(addresses);
} catch (error) {
console.error("Error fetching addresses:", error);
res.status(500).json({ error: "Internal Server Error" });
}
};
exports.createAddress = async (req, res) => {
try {
const { street, zipCode, city, state, country, userId } = req.body;
// Validate the presence of required fields
if (!street || !zipCode || !city || !state || !country || !userId) {
return res.status(400).json({ error: "Please fill in all required address fields" });
}
// Create the address
const address = new Address({
street,
zipCode,
city,
state,
country,
userId,
});
// Save the address to the database
const savedAddress = await address.save();
res.status(201).json(savedAddress);
} catch (error) {
console.error("Error creating address:", error);
res.status(500).json({ error: "Internal Server Error" });
}
};
exports.deleteAddress = async (req, res) => {
try {
const { id: addressId } = req.params;
console.log('Received Address ID:', addressId);
// Check if the address with the given ID exists
const existingAddress = await Address.findById(addressId);
console.log('Existing Address:', existingAddress);
if (!existingAddress) {
return res.status(404).json({ message: 'Address not found' });
}
// Perform the deletion of the address
await Address.findByIdAndDelete(addressId);
res.status(200).json({ message: 'Address deleted successfully' });
} catch (error) {
console.error('Error deleting address:', error);
res.status(500).json({ message: 'Internal server error' });
}
};
// In userController.js or your relevant controller file
exports.updateAddress = async (req, res) => {
try {
const { id: addressId } = req.params;
const { street, zipCode, city, state, country } = req.body;
// Check if the address with the given ID exists
const existingAddress = await Address.findById(addressId);
if (!existingAddress) {
return res.status(404).json({ message: 'Address not found' });
}
// Update the address fields
existingAddress.street = street;
existingAddress.zipCode = zipCode;
existingAddress.city = city;
existingAddress.state = state;
existingAddress.country = country;
// Save the updated address
await existingAddress.save();
res.status(200).json({ message: 'Address updated successfully' });
} catch (error) {
console.error('Error updating address:', error);
res.status(500).json({ message: 'Internal server error' });
}
};

@ -0,0 +1,48 @@
// Dependencies
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
require("dotenv").config();
const userRoute = require("./routes/user");
const productRoute = require("./routes/product");
const cartRoute = require("./routes/cart");
const orderRoute = require("./routes/order");
const path = require('path'); // Add this line
// Server start
const app = express();
// Middlewares
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
// Serve static files from the "uploads" folder
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
// Database
mongoose
.connect(process.env.MONGO_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
dbName: "CAPSTONE-3",
})
.then(() => {
console.log(`Connected to Database ${process.env.MONGO_URL}`);
})
.catch((err) => {
console.log(err);
});
// Routes
app.use("/order", orderRoute);
app.use("/user", userRoute);
app.use("/product", productRoute);
app.use("/cart", cartRoute);
// Server up
app.listen(process.env.PORT || 4000, () => {
console.log(`Server is running on port ${process.env.PORT}..`);
});

@ -0,0 +1,14 @@
const mongoose = require('mongoose');
const addressSchema = new mongoose.Schema({
street: { type: String, required: true },
zipCode: { type: String, required: true },
city: { type: String, required: true },
state: { type: String, required: true },
country: { type: String, required: true },
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
});
const Address = mongoose.model('Address', addressSchema);
module.exports = Address;

@ -0,0 +1,40 @@
const mongoose = require('mongoose');
const cartItemSchema = new mongoose.Schema({
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
required: false,
},
quantity: {
type: Number,
default: 1,
},
});
const cartSchema = new mongoose.Schema({
userId: { type: String }, // For authenticated users
anonymousUserId: { type: String }, // For anonymous users
items: [
{
product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' },
quantity: { type: Number, default: 1 },
},
],
});
// Method to find a cart by user ID
cartSchema.statics.findByUserId = function (userId) {
return this.findOne({ userId });
};
// Method to find a cart by anonymous user ID
cartSchema.statics.findByAnonymousUserId = function (anonymousUserId) {
return this.findOne({ anonymousUserId });
};
const Cart = mongoose.model('Cart', cartSchema);
module.exports = Cart;

@ -0,0 +1,15 @@
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
name: { type: String, required: true },
description: { type: String, required: true },
price: { type: Number, required: true },
isActive: { type: Boolean, default: false },
image: { type: String },
tags: { type: [String], default: ["default"] }, // Array of tags
createdOn: { type: Date, default: Date.now },
});
const Product = mongoose.model('Product', productSchema);
module.exports = Product;

@ -0,0 +1,22 @@
// models/PromoCode.js
const mongoose = require('mongoose');
const promoCodeSchema = new mongoose.Schema({
code: {
type: String,
required: true,
unique: true,
},
discountAmount: {
type: Number,
required: true,
},
expirationDate: {
type: Date,
},
// Add more fields as needed
});
const PromoCode = mongoose.model('PromoCode', promoCodeSchema);
module.exports = PromoCode;

@ -0,0 +1,32 @@
// Your user model definition
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
productId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
required: true,
},
productName: { type: String, required: true },
quantity: { type: Number, required: true },
});
const userSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true },
firstName: { type: String },
lastName: { type: String },
password: { type: String, required: true },
isAdmin: { type: Boolean, default: false },
mobileNo: { type: String },
orderedProducts: [
{
products: [productSchema],
totalAmount: { type: Number, required: true },
purchaseOn: { type: Date, default: Date.now },
},
],
});
const User = mongoose.model('User', userSchema);
module.exports = User;

@ -0,0 +1,11 @@
// models/productTag.js
const mongoose = require('mongoose');
const productTagSchema = new mongoose.Schema({
name: { type: String, required: true, unique: true },
});
const ProductTag = mongoose.model('ProductTag', productTagSchema);
module.exports = ProductTag;

@ -0,0 +1,22 @@
// multer.js
const multer = require('multer');
const path = require('path');
// Set up storage using disk storage
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/'); // Specify the directory where you want to store uploaded files
},
filename: function (req, file, cb) {
// Generate a unique filename
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
const fileExtension = path.extname(file.originalname);
cb(null, file.fieldname + '-' + uniqueSuffix + fileExtension);
},
});
// Create a Multer instance with the storage configuration
const upload = multer({ storage: storage });
module.exports = upload;

@ -0,0 +1,26 @@
{
"name": "capstone-2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "nodemon index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-validator": "^7.0.1",
"faker": "^5.5.3",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.0.0",
"multer": "^1.4.5-lts.1",
"nodemon": "^3.0.1",
"react-responsive": "^9.0.2",
"uuid": "^9.0.1"
}
}

@ -0,0 +1,176 @@
## E-COMMERCE API DOCUMENTATION
**_INSTALLATION COMMAND:_**
`npm install bcrypt cors dotenv express faker jsonwebtoken mongoose nodemon`
**_Start_**
npm start
**_TEST ACCOUNTS:_**
- Regular User:
- email: user@email.com
- pwd: user
- Admin User:
- email: admin@email.com
- pwd: admin
**_ROUTES:_**
- User registration (POST)
- http://localhost:3000/user/register
- auth header required: NO
- request body:
{
"email": "admin@email.com",
"password": "admin"
}
- User authentication (POST)
- http://localhost:3000/user/login
- auth header required: NO
- request body:
{
"email": "admin@email.com",
"password": "admin"
}
- Create Product (Admin only) (POST)
- http://localhost:4000/product/create
- auth header required: YES
- request body:
{
"name": "Poring Card",
"description": "Description unknown",
"price": 500
}
- Update Profile
- http://localhost:3000/user/update
- auth header required: YES
- request body:
{
"userId": "",
"newEmail": "",
"newFirstname": "",
"newLastName": "",
"newPassword": ""
}
- Retrieve all products (Admin only) (GET)
- http://localhost:3000/product/all
- auth header required: YES
- request body: none
- Retrieve all active products (GET)
- http://localhost:3000/product/active
- auth header required: NO
- request body: none
- Get all products (GET)
- http://localhost:3000/product/active
- auth header required: NO
- request body: none
- Get a product (GET)
- http://localhost:3000/product/products/65545a1e6fa9d841e1518d1d
- auth header required: YES
- request body: none
- Update Single product (PUT)
- http://localhost:3000/product/products/65545a1e6fa9d841e1518d1d
- auth header required: YES
- request body:
{
"name": "Christmas Cookie Card",
"description": "Updated Product Description",
"price": 29.99,
"isActive": false
}
- Create Order (POST)
- http://localhost:3000/user/order
- auth header required: YES
- request body:
{
"userId": "65535cb526b586a3e2fd56cc", // Replace with a valid user ID from your database
"products": [
{
"productId": "6553a4e897ac8ac9462f96c4", // Replace with a valid product ID from your database
"productName": "Mastering Card",
"quantity": 1
}
],
"totalAmount": 500
}
- Activate / Archive Product (PUT)
- auth header required: YES
- request body: none
- http://localhost:3000/product/products/6554634e5cac4bcd6f2394ed/activate
- http://localhost:3000/product/products/6554634e5cac4bcd6f2394ed/archive
- Set User to Admin (POST) [Admin Only]
- hhttp://localhost:3000/user/set-admin/
- auth header required: YES
- request body:
{
"userId":
}
- Retrieve All Orders [Admin Only] (GET)
- http://localhost:3000/user/orders-all
- auth header required: YES
- request body: none
- Add To Cart (POST)
- http://localhost:3000/cart/add-to-cart
- auth header required: YES
- request body:
{
"userId": "655396dcc8ea29f42422e214",
"productId": "6553a54566c4c86c39034b55",
"quantity": 5
}
- Delete Item (DELETE)
- http://localhost:3000/cart/remove-from-cart
- auth header required: YES
- request body:
{
"userId": "655396dcc8ea29f42422e214",
"productId": "6553a54566c4c86c39034b55",
"quantity": 5
}
- Update Quantity (PUT)
- http://localhost:3000/cart//update-quantity
- auth header required: YES
- request body:
{
"userId": "655396dcc8ea29f42422e214",
"productId": "6553a55666c4c86c39034b59",
"quantity": 2000
}
- Cart Total (GET)
- http://localhost:3000/cart/cart-details
- auth header required: YES
- request body:
{
"userId": "655396dcc8ea29f42422e214"
}
\***\* Stretch Goals \*\***
- Set user as Admin ( Admin Only )
- Retrieve Authenticated User's Orders
- Retrieve all orders ( Admin Only )
- Add to Cart ( Added Products, Change Product Quantities, Remove Products From Cart, Subtotal for each item, Total price for all items)
- Authentication Token with expiration (1hr)
- dotenv
- faker (Auto Generate Names)
- getUserDetails function ( Detects if the user tries to get the details of the other useId's + Token auth)
- Middleware Secure verification that match Token and UserId to next()

@ -0,0 +1,24 @@
const express = require('express');
const router = express.Router();
const cartController = require('../controllers/cart');
const auth = require("../auth");
const { authenticateToken, extractAnonymousUserId } = auth;
// Route to add an item to the cart
router.post('/add-to-cart', extractAnonymousUserId, cartController.addToCart);
// Update cart route
router.put('/update-cart', extractAnonymousUserId, cartController.updateCart);
router.delete('/remove-from-cart', extractAnonymousUserId, cartController.removeFromCart);
// Route to get cart details for a specific user
router.get('/user/:userId', cartController.getCartDetails);
// Route to get cart details for an anonymous user
router.get('/anonymous/:anonymousUserId', cartController.getCartDetailsForAnonymousUser);
router.post('/clear-cart', authenticateToken, cartController.clearCart);
module.exports = router;

@ -0,0 +1,19 @@
const express = require('express');
const router = express.Router();
const orderController = require("../controllers/order")
const auth = require("../auth");
const { authenticateToken, verifyUser, verifyAdmin } = auth;
// POST /users/order
router.post("/order", authenticateToken, orderController.createOrder);
// Route to retrieve authenticated user's orders
router.post("/get-order", authenticateToken, orderController.getOrders);
// Route to retrieve all orders
router.get("/orders-all", authenticateToken, verifyAdmin, orderController.getAllOrders);
module.exports = router;

@ -0,0 +1,46 @@
const express = require("express")
const router = express.Router()
const productController = require("../controllers/product")
const auth = require("../auth")
const upload = require('../multer');
const { authenticateToken, verifyAdmin, } = auth
router.put('/edit/:productId', productController.editProduct);
router.get("/product-tag", productController.productTag)
// S50
// Create a product route (accessible only by isAdmin)
// Retrieve all products route (accessible to both admin and normal user)
router.post("/create", authenticateToken, verifyAdmin, upload.single('image'), productController.createProduct)
// Retrieve all products route (accessible to both admin and normal user)
router.get("/", authenticateToken, productController.getAllProducts)
// Retrieve all products route (accessible to both admin and normal user)
router.get("/all", authenticateToken, verifyAdmin, productController.getAllProducts)
router.delete('/delete/:id', authenticateToken, verifyAdmin, productController.deleteProduct)
// Retrieve all active products route (accessible to both admin and normal user)
router.get("/active", productController.getActiveProducts)
// S51
// Retrieve a single product by ID
router.get('/:id', productController.getProductById)
// Update product route + admin verification
router.put('/:id', authenticateToken, verifyAdmin, productController.updateProduct)
// Archive a product
router.put('/:productId/archive', authenticateToken, verifyAdmin, productController.archiveProduct)
// Activate a product
router.put('/:productId/activate', authenticateToken, verifyAdmin, productController.activateProduct)
module.exports = router

@ -0,0 +1,46 @@
const express = require("express")
const router = express.Router()
const userController = require("../controllers/user")
const auth = require("../auth");
const { authenticateToken, verifyAdmin, verifyUser } = auth;
router.post("/apply-promo-code", authenticateToken, userController.applyPromoCode)
router.get("/details", authenticateToken, userController.userDetails)
// User registration route
router.post("/register", userController.registerUser)
router.post("/check-email", userController.checkEmail)
router.post("/reset-password", userController.resetPassword)
// User authentication route
router.post("/authenticate", userController.authenticateUser)
// Update user data route
router.put("/update", authenticateToken, userController.updateUserData)
// Retrieve user details
router.get('/retrieveUser', authenticateToken, verifyUser, userController.getUserDetails);
// EXCLUSIVE ADMIN ACCOUNT
// Set user to Admin User
router.post('/set-admin', authenticateToken, verifyAdmin, userController.setAdmin);
router.post('/create-promo-code', authenticateToken, verifyAdmin, userController.createPromoCode);
router.delete('/delete-promo-code/:id', authenticateToken, verifyAdmin, userController.deletePromoCode);
router.get('/promo-code', authenticateToken, verifyAdmin, userController.showPromoCodes);
// Address
router.post('/get-address', authenticateToken, verifyUser, userController.getAddresses);
router.post('/create-address', authenticateToken, verifyUser, userController.createAddress);
router.delete('/delete-address/:id', authenticateToken, userController.deleteAddress);
router.put('/update-address/:id', authenticateToken, userController.updateAddress);
module.exports = router

@ -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)

@ -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: 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,122 @@
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 };
case "CLEAR_CART":
return { ...state, cart: [], cartDetails: null };
// 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 });
};
// Action to clear the cart
const clearCart = async () => {
try {
const token = localStorage.getItem("token");
const response = await fetch(
`${process.env.REACT_APP_API_URL}/cart/clear-cart`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
}
);
if (response.ok) {
console.log("Cart cleared successfully.");
setCart([]); // Clear the cart locally
setCartDetails(null); // Clear the cart details locally
} else {
console.error("Failed to clear cart.");
}
} catch (error) {
console.error("Error clearing cart:", error);
}
};
// 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);
setCartDetails(data);
}
} 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, clearCart }}
>
{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,178 @@
import React from 'react';
import { Form, Button } from 'react-bootstrap';
const AddressForm = ({
selectedAddress,
setSelectedAddress,
handleContinueAddress,
addressFormError,
}) => {
const philippineStates = [
"...",
"Abra",
"Agusan del Norte",
"Agusan del Sur",
"Aklan",
"Albay",
"Antique",
"Apayao",
"Aurora",
"Basilan",
"Bataan",
"Batanes",
"Batangas",
"Benguet",
"Biliran",
"Bohol",
"Bukidnon",
"Bulacan",
"Cagayan",
"Camarines Norte",
"Camarines Sur",
"Camiguin",
"Capiz",
"Catanduanes",
"Cavite",
"Cebu",
"Cotabato",
"Davao de Oro (formerly Compostela Valley)",
"Davao del Norte",
"Davao del Sur",
"Davao Occidental",
"Davao Oriental",
"Dinagat Islands",
"Eastern Samar",
"Guimaras",
"Ifugao",
"Ilocos Norte",
"Ilocos Sur",
"Iloilo",
"Isabela",
"Kalinga",
"La Union",
"Laguna",
"Lanao del Norte",
"Lanao del Sur",
"Leyte",
"Maguindanao",
"Marinduque",
"Masbate",
"Misamis Occidental",
"Misamis Oriental",
"Mountain Province",
"Negros Occidental",
"Negros Oriental",
"Northern Samar",
"Nueva Ecija",
"Nueva Vizcaya",
"Occidental Mindoro",
"Oriental Mindoro",
"Palawan",
"Pampanga",
"Pangasinan",
"Quezon",
"Quirino",
"Rizal",
"Romblon",
"Samar (Western Samar)",
"Sarangani",
"Siquijor",
"Sorsogon",
"South Cotabato",
"Southern Leyte",
"Sultan Kudarat",
"Sulu",
"Surigao del Norte",
"Surigao del Sur",
"Tarlac",
"Tawi-Tawi",
"Zambales",
"Zamboanga del Norte",
"Zamboanga del Sur",
"Zamboanga Sibugay",
];
// Define the array of Philippine states
return (
<Form onSubmit={handleContinueAddress}>
{/* Address form for adding a new address */}
<Form.Group controlId="street">
<Form.Label>Street</Form.Label>
<Form.Control
type="text"
placeholder="Enter street address"
value={selectedAddress?.street || ""}
onChange={(e) =>
setSelectedAddress({
...selectedAddress,
street: e.target.value,
})
}
required
/>
</Form.Group>
<Form.Group controlId="zipCode">
<Form.Label>Zip / Postal Code</Form.Label>
<Form.Control
type="text"
placeholder="Enter zip/postal code"
value={selectedAddress?.zipCode || ""}
onChange={(e) =>
setSelectedAddress({
...selectedAddress,
zipCode: e.target.value,
})
}
required
/>
</Form.Group>
<Form.Group controlId="city">
<Form.Label>City</Form.Label>
<Form.Control
type="text"
placeholder="Enter city"
value={selectedAddress?.city || ""}
onChange={(e) =>
setSelectedAddress({
...selectedAddress,
city: e.target.value,
})
}
required
/>
</Form.Group>
<Form.Group controlId="state">
<Form.Label>State</Form.Label>
<Form.Control
as="select"
value={selectedAddress?.state || ""}
onChange={(e) =>
setSelectedAddress({
...selectedAddress,
state: e.target.value,
})
}
required
>
{philippineStates.map((state, index) => (
<option key={index}>{state}</option>
))}
</Form.Control>
</Form.Group>
<Form.Group controlId="country">
<Form.Label>Country</Form.Label>
<Form.Control type="text" placeholder="Philippines" disabled />
</Form.Group>
<Button className="mt-3" variant="primary" type="submit">
Continue
</Button>
{addressFormError && (
<div className="text-danger mt-2">{addressFormError}</div>
)}
</Form>
);
};
export default AddressForm;

@ -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! USE CODE: NEW15 </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,222 @@
import React, { useState, useEffect } from "react";
import { Form, Button, Table, Alert, Modal } from "react-bootstrap";
import { toast, ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
const CreatePromoCode = () => {
const [promoCodes, setPromoCodes] = useState([]);
const [promoCode, setPromoCode] = useState("");
const [discountAmount, setDiscountAmount] = useState(""); // Change variable name here
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [promoToDelete, setPromoToDelete] = useState(null);
useEffect(() => {
fetchPromoCodes();
}, []);
const fetchPromoCodes = async () => {
try {
setLoading(true);
const response = await fetch(
`${process.env.REACT_APP_API_URL}/user/promo-code`,
{
method: "GET",
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
}
);
if (response.ok) {
const data = await response.json();
setPromoCodes(data.promoCodes);
} else {
console.error("Failed to fetch promo codes:", response.status);
setError("Failed to fetch promo codes. Please try again.");
}
} catch (error) {
console.error("Error fetching promo codes:", error);
setError("An error occurred while fetching promo codes.");
} finally {
setLoading(false);
}
};
const notifySuccess = (message) => {
toast.success(message, {
position: "bottom-right",
autoClose: 3000,
});
};
const notifyError = (message) => {
toast.error(message, {
position: "bottom-right",
autoClose: 3000,
});
};
const handleCreatePromoCode = async () => {
try {
const response = await fetch(
`${process.env.REACT_APP_API_URL}/user/create-promo-code`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
body: JSON.stringify({
code: promoCode, // Provide the 'code' field
discountAmount,
}),
}
);
if (response.ok) {
setPromoCode("");
setDiscountAmount(""); // Change variable name here
setError(null);
fetchPromoCodes();
notifySuccess("Promo code created successfully");
} else {
console.error("Failed to create promo code:", response.status);
setError("Failed to create promo code. Please try again.");
notifyError("Failed to create promo code. Please try again.");
}
} catch (error) {
console.error("Error creating promo code:", error);
setError("An error occurred while creating promo code.");
notifyError("An error occurred while creating promo code.");
}
};
const handleCloseDeleteModal = () => {
setPromoToDelete(null);
setShowDeleteModal(false);
};
const handleDeletePromoCode = async (id) => {
try {
setLoading(true);
const response = await fetch(
`${process.env.REACT_APP_API_URL}/user/delete-promo-code/${id}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
}
);
if (response.ok) {
fetchPromoCodes();
notifySuccess("Promo code deleted successfully");
} else {
console.error("Failed to delete promo code:", response.status);
setError("Failed to delete promo code. Please try again.");
notifyError("Failed to delete promo code. Please try again.");
}
} catch (error) {
console.error("Error deleting promo code:", error);
setError("An error occurred while deleting promo code.");
notifyError("An error occurred while deleting promo code.");
} finally {
setLoading(false);
handleCloseDeleteModal();
}
};
const handleShowDeleteModal = (id) => {
setPromoToDelete(id);
setShowDeleteModal(true);
};
return (
<div>
<h2>Create Promo Code</h2>
{error && <Alert variant="danger">{error}</Alert>}
<Form>
<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)}
/>
</Form.Group>
<Form.Group controlId="discountAmount">
<Form.Label>Discount Amount</Form.Label> {/* Change label here */}
<Form.Control
type="text"
placeholder="Enter discount amount"
value={discountAmount}
onChange={(e) => setDiscountAmount(e.target.value)}
/>
</Form.Group>
<Button variant="primary" onClick={handleCreatePromoCode}>
Create Promo Code
</Button>
</Form>
<h2>Promo Codes</h2>
{loading && <p>Loading...</p>}
<Table striped bordered hover>
<thead>
<tr>
<th>Promo Code</th>
<th>Discount Amount</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{promoCodes.map((promo) => (
<tr key={promo._id}>
<td>{promo.code}</td>{" "}
{/* Display 'code' instead of 'promoCode' */}
<td>{promo.discountAmount}</td>
<td>
<Button
variant="danger"
onClick={() => handleShowDeleteModal(promo._id)}
>
Delete
</Button>
</td>
</tr>
))}
</tbody>
</Table>
{/* Toast Container */}
<ToastContainer position="bottom-right" autoClose={3000} />
<Modal show={showDeleteModal} onHide={handleCloseDeleteModal}>
<Modal.Header closeButton>
<Modal.Title>Delete Promo Code</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to delete this promo code?
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleCloseDeleteModal}>
Cancel
</Button>
<Button
variant="danger"
onClick={() => handleDeletePromoCode(promoToDelete)}
>
Delete
</Button>
</Modal.Footer>
</Modal>
</div>
);
};
export default CreatePromoCode;

@ -0,0 +1,103 @@
import React, { useState, useEffect } from "react";
import { Modal, Button, Form } from "react-bootstrap";
const EditAddress = ({ show, onHide, onSave, address }) => {
const [editedAddress, setEditedAddress] = useState({ ...address });
const [updateStatus, setUpdateStatus] = useState(null);
useEffect(() => {
setEditedAddress({ ...address });
}, [address]);
const handleChange = (e) => {
const { name, value } = e.target;
console.log(`Updating ${name} to:`, value);
setEditedAddress((prevAddress) => ({
...prevAddress,
[name]: value,
}));
};
const handleSave = async () => {
console.log("Saving edited address:", editedAddress);
try {
const response = await onSave(editedAddress);
if (response.ok) {
console.log("Address updated successfully:", editedAddress._id);
setUpdateStatus("success");
} else {
console.error("Failed to update address:", response.status);
setUpdateStatus("error");
}
} catch (error) {
console.error("Error updating address:", error);
setUpdateStatus("error");
}
};
return (
<Modal show={show} onHide={onHide}>
<Modal.Header closeButton>
<Modal.Title>Edit Address</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form>
<Form.Group controlId="street">
<Form.Label>Street</Form.Label>
<Form.Control
type="text"
name="street"
value={editedAddress.street}
onChange={handleChange}
/>
</Form.Group>
<Form.Group controlId="zipCode">
<Form.Label>Zip Code</Form.Label>
<Form.Control
type="text"
name="zipCode"
value={editedAddress.zipCode}
onChange={handleChange}
/>
</Form.Group>
<Form.Group controlId="city">
<Form.Label>City</Form.Label>
<Form.Control
type="text"
name="city"
value={editedAddress.city}
onChange={handleChange}
/>
</Form.Group>
<Form.Group controlId="state">
<Form.Label>State</Form.Label>
<Form.Control
type="text"
name="state"
value={editedAddress.state}
onChange={handleChange}
/>
</Form.Group>
<Form.Group controlId="country">
<Form.Label>Country</Form.Label>
<Form.Control
type="text"
name="country"
value={editedAddress.country}
onChange={handleChange}
/>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={onHide}>
Close
</Button>
<Button variant="primary" onClick={handleSave}>
Save Changes
</Button>
</Modal.Footer>
</Modal>
);
};
export default EditAddress;

@ -0,0 +1,78 @@
import React, { useContext } from 'react';
import { Container, Row, Col, Form, Button } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import UserContext from '../UserContext';
const Footer = () => {
const { user } = useContext(UserContext);
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> 999F IGOP Building</p>
<p className='text-white'><i className="bi bi-chevron-right"></i> +639-123-456-7890</p>
<p className='text-white'><i className="bi bi-chevron-right"></i> customercare@istore.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>
{user && user.id !== null ? (
<a href="/profile" className="text-decoration-none text-white"><i className="bi bi-chevron-right"></i> Profile</a>
) : (
<a href="/login" 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,68 @@
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>
<Nav.Link as={NavLink} to="/cart" onClick={handleNavLinkClick}>
Cart
</Nav.Link>
{user.isAdmin && (
<Nav.Link as={NavLink} to="/dashboard" exact onClick={handleNavLinkClick}>
Admin Dashboard
</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.Link as={NavLink} to="/cart" onClick={handleNavLinkClick}>
Cart
</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,163 @@
// 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 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>
</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,24 @@
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';
import { CartProvider } from './CartContext';
const root = createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<CartProvider>
<Provider store={store}>
<App />
</Provider>
</CartProvider>
</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,36 @@
// AdminDashboard.js
import React, { useRef } from 'react';
import { Tabs, Tab, Container } from 'react-bootstrap';
import CreateProduct from '../components/CreateProduct';
import AllProducts from '../components/AllProducts';
import CreatePromoCode from '../components/CreatePromoCode'; // Import the new component
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>
<Tab eventKey="create-promo-code" title="Create Promo Code"> {/* Add the new tab for creating a promo code */}
<CreatePromoCode />
</Tab>
{/* Add more tabs for other actions if needed */}
</Tabs>
</Container>
);
};
export default AdminDashboard;

@ -0,0 +1,360 @@
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]);
useEffect(() => {
// Fetch cart items from local storage
const storedCartItems = localStorage.getItem("cartItems");
if (storedCartItems) {
setCartItems(JSON.parse(storedCartItems));
}
// Fetch other necessary data or perform any additional logic
}, []);
const updateCart = async () => {
try {
const token = localStorage.getItem("token");
const decoded = jwtDecode(token);
const storeCartInLocalStorage = (cartItems) => {
localStorage.setItem("cartItems", JSON.stringify(cartItems));
};
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();
storeCartInLocalStorage(cartItems);
} 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>
<td>
{cartItems.reduce(
(total, item) => total + item.quantity,
0
)}
</td>
</tr>
<tr>
<td>Shipping Fee</td>
<td>{shippingFee}</td>
</tr>
<tr>
<td>Total</td>
<td>
<strong>{totalPrice + shippingFee}</strong>
</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,376 @@
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 [loading, setLoading] = useState(true);
const navigate = useNavigate();
const fetchCartItems = async () => {
try {
setLoading(true);
const token = localStorage.getItem("token");
const decoded = token ? jwtDecode(token) : null;
const anonymousUserId = localStorage.getItem("anonymousUserId");
const url =
token
? `${process.env.REACT_APP_API_URL}/cart/user/${decoded?.userId}`
: `${process.env.REACT_APP_API_URL}/cart/anonymous/${anonymousUserId}`;
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: token ? `Bearer ${token}` : undefined,
"X-Anonymous-User-ID": !token ? anonymousUserId : undefined,
},
});
const data = await response.json();
if (Array.isArray(data.items)) {
setCartItems(data.items);
}
} catch (error) {
console.error("Error fetching cart items:", error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchCartItems();
}, []);
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 = () => {
// Dummy function for calculating shipping fee
// Modify as needed based on your business logic
return 50;
};
calculateTotalPrice();
setShippingFee(calculateShippingFee());
}, [cartItems, productDetails]);
useEffect(() => {
// Fetch cart items from local storage
const storedCartItems = localStorage.getItem("cartItems");
if (storedCartItems) {
setCartItems(JSON.parse(storedCartItems));
}
// Fetch other necessary data or perform any additional logic
}, []);
const updateCart = async () => {
try {
const token = localStorage.getItem("token");
const decoded = jwtDecode(token);
const storeCartInLocalStorage = (cartItems) => {
localStorage.setItem("cartItems", JSON.stringify(cartItems));
};
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();
storeCartInLocalStorage(cartItems);
} 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>
<td>
{cartItems.reduce(
(total, item) => total + item.quantity,
0
)}
</td>
</tr>
<tr>
<td>Shipping Fee</td>
<td>{shippingFee}</td>
</tr>
<tr>
<td>Total</td>
<td>
<strong>{totalPrice + shippingFee}</strong>
</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,721 @@
import React, { useState, useEffect } from "react";
import {
Container,
Row,
Col,
Table,
Button,
Collapse,
Form,
Modal,
} from "react-bootstrap";
import AddressForm from "../components/AddressForm";
import EditAddress from "../components/EditAddress";
import { jwtDecode } from "jwt-decode";
import { toast, ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
const CheckoutPage = () => {
const [cartItems, setCartItems] = useState([]);
const [totalPrice, setTotalPrice] = useState(0);
const [shippingFee, setShippingFee] = useState({
value: 200,
details: "Fixed shipping fee of ₱200",
});
const [promoCode, setPromoCode] = useState("");
const [personalInfoOpen, setPersonalInfoOpen] = useState(true);
const [addressOpen, setAddressOpen] = useState(false);
const [paymentOpen, setPaymentOpen] = useState(false);
const [isCustomerLoggedIn, setIsCustomerLoggedIn] = useState(false);
const [userDetails, setUserDetails] = useState(null);
const [appliedPromoCode, setAppliedPromoCode] = useState(null);
const [productDetails, setProductDetails] = useState({});
const [discountAmount, setDiscountAmount] = useState(0);
const [addAddressFormOpen, setAddAddressFormOpen] = useState(false);
const [addresses, setAddresses] = useState([]);
const [selectedAddressId, setSelectedAddressId] = useState(null);
const [addressFormError, setAddressFormError] = useState(null);
const handleAddressSelect = (addressId) => {
setSelectedAddressId(addressId);
};
const handleDeleteAddress = async (addressId) => {
try {
const token = localStorage.getItem("token");
const response = await fetch(
`${process.env.REACT_APP_API_URL}/user/delete-address/${addressId}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (response.ok) {
console.log("Address deleted successfully:", addressId);
toast.success("Successfully deleted address", {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
// Fetch addresses again after deleting an address
fetchAddresses();
} else {
console.error("Failed to delete address:", response.status);
toast.error("Failed to delete address", {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
}
} catch (error) {
console.error("Error deleting address:", error);
toast.error("Error deleting address", {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
}
};
const [selectedAddress, setSelectedAddress] = useState({
street: "",
zipCode: "",
city: "",
state: "",
country: "Philippines",
});
const handleContinueAddress = async (e) => {
e.preventDefault(); // Prevent the default form submission behavior
// Add validation logic here to check if the required fields are filled
if (
!selectedAddress.street ||
!selectedAddress.zipCode ||
!selectedAddress.city ||
!selectedAddress.state
) {
console.error("Please fill in all required address fields");
// Display an error toast
toast.error("Please fill in all required address fields", {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
return;
}
try {
const token = localStorage.getItem("token");
// Decode the token to extract user information
const decodedToken = jwtDecode(token);
const userId = decodedToken.userId;
const response = await fetch(
`${process.env.REACT_APP_API_URL}/user/create-address`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ ...selectedAddress, userId }), // Include userId in the request body
}
);
if (response.ok) {
const data = await response.json();
console.log("Address created successfully:", data);
// Optionally, you can update the UI or state to reflect the new address
// Display a success toast
toast.success("Address created successfully!", {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
// Fetch addresses again after creating a new address
fetchAddresses();
// Close the forms after creating a new address
setAddAddressFormOpen(false);
setPersonalInfoOpen(false);
} else {
console.error("Failed to create address:", response.status);
// Display an error toast
toast.error("Failed to create address", {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
}
} catch (error) {
console.error("Error creating address:", error);
// Display an error toast
toast.error("Error creating address", {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
}
};
const fetchAddresses = async () => {
try {
const token = localStorage.getItem("token");
// Decode the JWT token to get user information
const decoded = jwtDecode(token);
const userId = decoded.userId;
const response = await fetch(
`${process.env.REACT_APP_API_URL}/user/get-address`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ userId }),
}
);
if (response.ok) {
const data = await response.json();
setAddresses(data);
// Preselect the first address if available
if (data.length > 0) {
handleAddressSelect(data[0].id);
}
return data; // Return the fetched addresses
} else {
console.error("Failed to fetch addresses:", response.status);
return []; // Return an empty array if fetching fails
}
} catch (error) {
console.error("Error fetching addresses:", error);
return []; // Return an empty array if an error occurs
}
};
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);
}
};
const calculateTotalPrice = () => {
const totalPriceWithoutShipping = cartItems.reduce((total, item) => {
const product = productDetails[item.product];
if (product) {
return total + product.price * item.quantity;
}
return total;
}, 0);
// Calculate the discount based on the percentage
const calculatedDiscount =
(discountAmount / 100) * totalPriceWithoutShipping;
// Calculate the total price after applying the discount
const totalPriceAfterDiscount =
totalPriceWithoutShipping - calculatedDiscount;
// Use shippingFee.value in the calculation
const updatedTotalPrice = totalPriceAfterDiscount + shippingFee.value;
// Update total price state
setTotalPrice(updatedTotalPrice);
};
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);
}
};
const fetchUserProfile = async () => {
try {
if (isCustomerLoggedIn) {
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();
setUserDetails(data);
} else {
console.error("Failed to fetch user profile:", response.status);
}
}
} catch (error) {
console.error("Error fetching user profile:", error);
}
};
const checkCustomerLoginStatus = () => {
const token = localStorage.getItem("token");
if (token) {
try {
const decoded = jwtDecode(token);
if (decoded && decoded.userId) {
setIsCustomerLoggedIn(true);
fetchCartItems(); // Fetch cart items when the user logs in
return;
}
} catch (error) {
console.error("Error decoding token:", error);
}
}
setIsCustomerLoggedIn(false);
};
const handleApplyPromoCode = async () => {
try {
const response = await fetch(
`${process.env.REACT_APP_API_URL}/user/apply-promo-code`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
body: JSON.stringify({
promoCode,
discountAmount,
}),
}
);
if (response.ok) {
const data = await response.json();
// Set discount amount obtained from the response
setDiscountAmount(data.discountAmount || 0);
// Set the applied promo code
setAppliedPromoCode(promoCode);
// Display success notification at the bottom-right
toast.success("Promo code applied successfully!", {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
checkCustomerLoginStatus();
// Wait for state to update before calculating the total price
setTimeout(() => {
calculateTotalPrice();
}, 0);
} else {
console.error("Failed to apply promo code:", response.status);
// Display error notification at the bottom-right
toast.error("Failed to apply promo code", {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
// Handle error or provide feedback to the user
}
} catch (error) {
console.error("Error applying promo code:", error);
// Display error notification at the bottom-right
toast.error("Error applying promo code", {
position: "bottom-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
}
};
useEffect(() => {
checkCustomerLoginStatus();
}, []);
useEffect(() => {
fetchUserProfile();
}, [isCustomerLoggedIn]);
useEffect(() => {
fetchCartItems();
}, []);
useEffect(() => {
fetchProductDetails();
}, [cartItems]);
useEffect(() => {
calculateTotalPrice();
}, [cartItems, productDetails, shippingFee]);
useEffect(() => {
fetchAddresses();
}, [isCustomerLoggedIn]);
return (
<Container className="my-5 text-start">
<h2>Checkout</h2>
<Row>
{/* Left side - Collapsible Forms */}
<Col xs={12} md={8}>
{/* Collapsible Table - Personal Information */}
<div className="border rounded mb-3 p-3">
<h3>
<div className="d-flex justify-content-between align-items-start">
<span className="fs-6">1. Personal Information</span>
<Button
variant="link"
onClick={() => setPersonalInfoOpen(!personalInfoOpen)}
aria-controls="personalInfoCollapse"
aria-expanded={personalInfoOpen}
>
<i
className={`bi bi-chevron-${
personalInfoOpen ? "up" : "down"
}`}
></i>
</Button>
</div>
</h3>
<Collapse in={personalInfoOpen}>
<div id="personalInfoCollapse">
{isCustomerLoggedIn ? (
<div>
<p>
<strong>Name:</strong> {userDetails?.firstName}{" "}
{userDetails?.lastName}
</p>
<p>
<strong>Email:</strong> {userDetails?.email}
</p>
<p>
<strong>Mobile Number:</strong> {userDetails?.mobileNo}
</p>
<p>If you want to update your information</p>
<Button variant="primary" href="/update-profile">
Update Information
</Button>
</div>
) : (
<div className="mx-4">
<p>
You are not logged in. Please log in or register to
proceed.
</p>
<Button variant="primary" href="/register">
Register
</Button>
<Button className="mx-2" variant="primary" href="/login">
Login
</Button>
</div>
)}
</div>
</Collapse>
</div>
{/* Collapsible Table - Address */}
<div className="border rounded mb-3 p-3">
<h3>
<div className="d-flex justify-content-between align-items-start">
<span className="fs-6">2. Address</span>
<Button
variant="link"
onClick={() => setAddressOpen(!addressOpen)}
aria-controls="addressCollapse"
aria-expanded={addressOpen}
>
<i
className={`bi bi-chevron-${addressOpen ? "up" : "down"}`}
></i>
</Button>
</div>
</h3>
<Collapse in={addressOpen}>
<div id="addressCollapse">
{isCustomerLoggedIn ? (
<Row>
<span className="mb-2" style={{ fontSize: "16px" }}>
The selected address will be used both as your personal
address (for invoice) and as your delivery address.
</span>
<Col className="border rounded mb-3 p-3" md={6}>
{/* Left column - Add New Address Form Dropdown */}
<div>
{addAddressFormOpen && (
<AddressForm
selectedAddress={selectedAddress}
setSelectedAddress={setSelectedAddress}
handleContinueAddress={handleContinueAddress}
addressFormError={addressFormError}
/>
)}
<Button
className="my-3"
variant="primary"
onClick={() =>
setAddAddressFormOpen(!addAddressFormOpen)
}
>
Add New Address
</Button>
</div>
</Col>
<Col md={6}>
{/* Right column - My Addresses from addressForm */}
{addresses && addresses.length > 0 ? (
<Table striped bordered hover>
<thead>
<tr>
<th>My Addresses</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{addresses.map((address, index) => (
<tr key={index}>
<td>
<div className="d-flex justify-content-between align-items-center">
<Form.Check
type="radio"
id={`addressRadio${index}`}
name="addressRadio"
checked={selectedAddressId === address.id}
onChange={() =>
handleAddressSelect(address.id)
}
/>
<p className="mx-3">
{address.street}, {address.city},{" "}
{address.state}, {address.zipCode},{" "}
{address.country}
</p>
</div>
</td>
<td>
<Button
variant="link"
className="text-danger"
onClick={() =>
handleDeleteAddress(address._id)
}
>
<i className="bi bi-trash"></i>
</Button>
</td>
</tr>
))}
</tbody>
</Table>
) : (
<p>No existing addresses. Add a new address below.</p>
)}
</Col>
</Row>
) : (
<p>
User not logged in. Log in to view and manage addresses.
</p>
)}
</div>
</Collapse>
</div>
{/* Collapsible Table - Payment */}
<div className="border rounded mb-3 p-3">
<h3>
<div className="d-flex justify-content-between align-items-start">
<span className="fs-6">4. Payment</span>
<Button
variant="link"
onClick={() => setPaymentOpen(!paymentOpen)}
aria-controls="paymentCollapse"
aria-expanded={paymentOpen}
>
<i
className={`bi bi-chevron-${paymentOpen ? "up" : "down"}`}
></i>
</Button>
</div>
</h3>
<Collapse in={paymentOpen}>
<div id="paymentCollapse">
{/* Placeholder content for payment table */}
</div>
</Collapse>
</div>
</Col>
{/* Right side - Order Summary */}
<Col xs={12} md={4} className="text-start">
<h3>Order Summary</h3>
<Table striped bordered hover>
<tbody>
<tr>
<td>Total Items</td>
<td>
{cartItems.reduce((total, item) => total + item.quantity, 0)}
</td>
</tr>
<tr>
<td>Shipping Fee</td>
<td>{shippingFee.value}</td>
</tr>
<tr>
<td>Shipping Details</td>
<td>{shippingFee.details}</td>
</tr>
<tr>
<td>Total</td>
<td>
<strong>{totalPrice}</strong>
</td>
</tr>
<tr>
<td>Discount</td>
<td>
<strong>{discountAmount}%</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)}
/>
<Button
className="mt-3"
variant="primary"
onClick={handleApplyPromoCode}
>
Apply Promo Code
</Button>
</Form.Group>
{appliedPromoCode && (
<div>
<p className="mt-2">
Applied Promo Code: <strong>{appliedPromoCode}</strong>
</p>
</div>
)}
</td>
</tr>
</tbody>
</Table>
</Col>
</Row>
<ToastContainer position="bottom-right" />
</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,37 @@
import { useContext, useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import UserContext from '../UserContext';
import { useCart } from '../CartContext';
export default function Logout() {
const { unsetUser, setUser } = useContext(UserContext);
const { clearCart } = useCart();
useEffect(() => {
const handleLogout = async () => {
try {
// Call the clearCart function from the CartContext
await clearCart();
// Display success toast for cart clearing, using toastId to ensure it only notifies once
toast.success('Your cart is cleared.', { position: 'bottom-right', toastId: 'cart-cleared-toast' });
} catch (error) {
console.error('Error clearing cart:', error);
}
// Unset user locally
unsetUser();
setUser({
id: null,
isAdmin: null,
});
// Redirect back to login
};
handleLogout();
}, [unsetUser, setUser, clearCart]);
return <Navigate to="/login" />;
}

@ -0,0 +1,189 @@
import React, { useState, useEffect } from "react";
import { v4 as uuidv4 } from "uuid";
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");
let anonymousUserId = localStorage.getItem("anonymousUserId");
const headers = {
"Content-Type": "application/json",
};
let userId; // variable to hold either userId or anonymousUserId
if (token) {
headers.Authorization = `Bearer ${token}`;
userId = jwtDecode(token).userId;
} else if (!anonymousUserId) {
// Generate a new anonymousUserId if it doesn't exist
anonymousUserId = uuidv4();
localStorage.setItem("anonymousUserId", anonymousUserId);
}
headers["X-Anonymous-User-ID"] = anonymousUserId;
userId = null; // Set userId to null if not logged in
const response = await fetch(
`${process.env.REACT_APP_API_URL}/cart/add-to-cart`,
{
method: "POST",
headers,
body: JSON.stringify({
userId,
anonymousUserId,
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,87 @@
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({ setUserDetails }) {
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);
// Pass user details to the parent component
setUserDetails(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, setUserDetails]);
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;
Loading…
Cancel
Save