Skip to content

Commit

Permalink
Merge pull request #187 from Sneha0019/wishlist_component
Browse files Browse the repository at this point in the history
Wishlist component
  • Loading branch information
Trisha-tech authored Jun 25, 2024
2 parents e22bdc9 + cc5be68 commit 2a80504
Show file tree
Hide file tree
Showing 9 changed files with 315 additions and 37 deletions.
24 changes: 17 additions & 7 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.8",
"axios": "^1.7.2",
"lottie-react": "^2.4.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hot-toast": "^2.4.1",
"react-icons": "^5.2.1",
"react-router-dom": "^6.23.1",
"react-scripts": "^5.0.1",
"swiper": "^11.1.4",
"tailwindcss": "^3.4.4",
"web-vitals": "^2.1.4"
},
"scripts": {
Expand Down
86 changes: 74 additions & 12 deletions client/src/Pages/ProductItem.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import Spinner from './Spinner';
import { FaHeart, FaRegHeart } from 'react-icons/fa';
import { Link } from 'react-router-dom';

const ProductItem = () => {
const [products, setProducts] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [wishlist, setWishlist] = useState([]);

useEffect(() => {
const fetchProducts = async () => {
Expand All @@ -17,24 +20,84 @@ const ProductItem = () => {
}
};

const fetchWishlist = async () => {
try {
const authToken = localStorage.getItem('token');
const response = await axios.get('http://localhost:8080/api/wishlist/getwishlistdata', {
headers: {
"Content-Type": "application/json",
"auth-token": authToken
},
withCredentials: true
});

// Extract product IDs from wishlist items
const wishlistItems = response.data.wishlistItems;
const wishlistProductIds = wishlistItems.map(item => item.product._id);
setWishlist(wishlistProductIds);
} catch (error) {
console.error('Error fetching wishlist', error);
}
};

fetchProducts();
fetchWishlist();
}, []);


const toggleWishlist = async (product) => {
try {
const authToken = localStorage.getItem('token');
const productId = product._id;

if (wishlist.includes(productId)) {
await axios.delete(`http://localhost:8080/api/wishlist/removefromwishlist/${productId}`, {
headers: {
"Content-Type": "application/json",
"auth-token": authToken
},
withCredentials: true
});

setWishlist(wishlist.filter((id) => id !== productId));
} else {
await axios.post(`http://localhost:8080/api/wishlist/addtowishlist/${productId}`, {}, {
headers: {
"Content-Type": "application/json",
"auth-token": authToken
},
withCredentials: true
});

setWishlist([...wishlist, productId]);
}
} catch (error) {
console.error('Error toggling wishlist', error);
}
};

const renderBooks = (books) => {
return books.map((book) => (
<div key={book._id} className="bg-white rounded-lg shadow-lg p-6 mb-6 flex flex-col relative">
<img src={book.images[0].url} alt={book.name} className="w-full h-48 object-cover mb-4 rounded-lg" />
<h3 className="text-xl font-bold mb-2">{book.name}</h3>
<p className="text-gray-600 mb-1">Author: {book.author}</p>
<p className="text-gray-600 mb-4">{book.description}</p>
<p className="text-gray-600 mb-0">Price: ₹ {book.price}</p>
<button className="absolute bottom-3 right-2 bg-blue-700 text-white px-5 py-4 rounded-md" style={{ backgroundColor: "#002147", color: "#ffffff", padding: "5px" }}>Add to Cart</button>
</div>
<div key={book._id} className="bg-white rounded-lg shadow-lg p-6 mb-6 flex flex-col relative transform transition duration-500 hover:scale-105 hover:shadow-2xl border border-gray-200">
<button
onClick={() => toggleWishlist(book)}
className="absolute top-2 right-2 text-red-600 hover:text-red-800 transition duration-300"
>
{wishlist.includes(book._id) ? <FaHeart size={20} fill="#ff0000" /> : <FaRegHeart size={20} />}
</button>
<img src={book.images[0].url} alt={book.name} className="w-full h-48 object-cover mb-4 rounded-lg" />
<h3 className="text-xl font-bold mb-2 text-gray-900">{book.name}</h3>
<p className="text-gray-600 mb-1">Author: {book.author}</p>
<p className="text-gray-600 mb-4">{book.description}</p>
<div className="flex justify-between items-center mt-auto">
<p className="text-gray-900 font-semibold text-lg">{book.price}</p>
<button className="self-end text-white px-4 py-2 rounded-md hover:bg-blue-800 transition duration-300 ease-in-out" style={{ backgroundColor: '#002147' }}>
Add to Cart
</button>
</div>
</div>
));
};


const categories = [...new Set(products.map((book) => book.category))];

return (
Expand All @@ -44,15 +107,14 @@ const ProductItem = () => {
) : (
categories.map((category) => (
<section key={category} className="container mx-auto my-8">
<h2 className="text-3xl font-bold mb-6 text-gray-800">{category}</h2>
<h2 className="text-3xl font-bold mb-6 text-gray-800 border-b border-gray-300 pb-2">{category}</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{renderBooks(products.filter((book) => book.category === category))}
</div>
</section>
))
)}
</div>

);
};

Expand Down
124 changes: 112 additions & 12 deletions client/src/Pages/Wishlist.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,115 @@
import { useState } from "react";
import Spinner from "./Spinner";

function Wishlist(){
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState(null);
return(
<div>
This will contain all information related to userwishlist.
<Spinner/>
import React, { useEffect, useState } from 'react';
import Spinner from './Spinner';
import axios from 'axios';
import { FaHeart, FaRegHeart } from 'react-icons/fa';

function Wishlist() {
const [isLoading, setIsLoading] = useState(true);
const [wishlistItems, setWishlistItems] = useState([]);
const [error, setError] = useState(null);

useEffect(() => {
const fetchWishlist = async () => {
try {
const authToken = localStorage.getItem('token');
const response = await axios.get('http://localhost:8080/api/wishlist/getwishlistdata', {
headers: {
"Content-Type": "application/json",
"auth-token": authToken
},
withCredentials: true
});

setWishlistItems(response.data.wishlistItems);
setIsLoading(false);
} catch (error) {
setError(error.message);
setIsLoading(false);
}
};

fetchWishlist();
}, []);

const removeFromWishlist = async (productId) => {
try {
const authToken = localStorage.getItem('token');
await axios.delete(`http://localhost:8080/api/wishlist/removefromwishlist/${productId}`, {
headers: {
"Content-Type": "application/json",
"auth-token": authToken
},
withCredentials: true
});


setWishlistItems(prevItems => prevItems.filter(item => item.product._id !== productId));
} catch (error) {
setError(error.message);
}
};

const addToCart = async (productId) => {
try {
const authToken = localStorage.getItem('token');
await axios.post('http://localhost:8080/api/cart/addtocart', { productId }, {
headers: {
"Content-Type": "application/json",
"auth-token": authToken
},
withCredentials: true
});

removeFromWishlist(productId);
} catch (error) {
setError(error.message);
}
};

if (isLoading) {
return <Spinner />;
}

if (error) {
return <div>Error fetching wishlist: {error}</div>;
}

return (
<div className="bg-gray-100 p-6">
{wishlistItems.length === 0 ? (
<p className="text-gray-600">Your wishlist is empty!</p>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{wishlistItems.map((item) => (
<div key={item._id} className="bg-white rounded-lg shadow-lg p-6 flex flex-col justify-between relative">
<button
onClick={() => removeFromWishlist(item.product._id)}
className="absolute top-2 right-2 text-red-600 hover:text-red-800 transition duration-300"
>
{wishlistItems.some(wishlistItem => wishlistItem.product._id === item.product._id) ? (
<FaHeart size={20} fill="#ff0000" />
) : (
<FaRegHeart size={20} />
)}
</button>
<div>
<img src={item.product.images[0].url} alt={item.product.name} className="w-full h-48 object-cover mb-4 rounded-lg" />
<h3 className="text-xl font-bold mb-2 text-gray-900">{item.product.name}</h3>
<p className="text-gray-600 mb-1">Author: {item.product.author}</p>
<p className="text-gray-600 mb-4">{item.product.description}</p>
</div>
<div className="flex justify-between items-center mt-4">
<p className="text-gray-900 font-semibold text-lg">{item.product.price}</p>
<button onClick={() => addToCart(item.product._id)} className="text-white px-4 py-2 rounded-md hover:bg-blue-800 transition duration-300 ease-in-out" style={{ backgroundColor: '#002147' }}>
Add to Cart
</button>
</div>
</div>
))}
</div>
)
)}
</div>
);
}

export default Wishlist;
export default Wishlist;
1 change: 1 addition & 0 deletions controllers/customerController.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ exports.registerCustomer = catchAsyncErrors(async (req, res, next) => {

// Access JWT secret key from environment variables
const jwtSecret = process.env.JWT_SECRET;
console.log(jwtSecret);

// Check if jwtSecret is defined
if (!jwtSecret) {
Expand Down
11 changes: 7 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ const MONGO_URL = process.env.MONGO_URL;
const cors = require("cors");
app.use(
cors({
origin: "http://localhost:3000", // your frontend's origin
optionsSuccessStatus: 200,
}),
origin: "http://localhost:3000", // Allow requests from localhost:3000
credentials: true, // Allow sending cookies from the frontend
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], // Allow the HTTP methods you use
allowedHeaders: ["Content-Type", "auth-token", "Origin", "X-Requested-With", "Accept"], // Allow headers
})
);

// Check if MONGO_URL is defined
Expand Down Expand Up @@ -55,12 +57,13 @@ const customer = require("./routes/customerRoutes.js");
const productRoutes = require("./routes/productRoutes.js");
const order = require("./routes/orderRoutes.js");
const admin = require("./routes/adminRoutes.js");
const wishlistRoutes = require("./routes/wishlistRoutes.js");
const { authorizeRoles } = require("./middlewares/auth.js");

app.use("/customer", customer);
app.use("/api/product", productRoutes);
app.use("/order", order);

app.use("/api/wishlist", wishlistRoutes);
app.use("admin", authorizeRoles, admin);
// Middleware for Errors
app.use(errorMiddleware);
Expand Down
3 changes: 2 additions & 1 deletion middlewares/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ const jwt = require("jsonwebtoken");
const Customer = require("../models/customerSchema.js");

exports.isAuthenticatedUser = catchAsyncErrors(async (req, res, next) => {
const { token } = req.cookies;
const token = req.headers["auth-token"];

if (!token) {
return next(new ErrorHandler("Please Login to access this resource", 401));
}

const decodedData = jwt.verify(token, process.env.JWT_SECRET);
req.body.user = await Customer.findById(decodedData.id);
console.log("verified successfully");

next();
});
Expand Down
Loading

0 comments on commit 2a80504

Please sign in to comment.