Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions src/components/Header/MainHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ import { Link } from 'react-router-dom';
import SearchBox from '../Search/SearchBox';
import TopHeader from '../TopHeader/TopHeader';
import ShopMenu from '../ShopMenu';
import CartIcon from '../CartComponent/CartIcon';
import CartDrawer from '../CartComponent/CartDrawer';
import { Menu, X, ChevronDown, Search, Heart, User } from 'lucide-react';
import { useSelector } from 'react-redux';
import WishListScreen from '../WishList/WishiListScreen';
import CartIcon from '../cart/CartIcon';
import CartDrawer from '../cart/CartDrawer';

export default function Header() {
const [mobileOpen, setMobileOpen] = useState(false);
const [shopOpen, setShopOpen] = useState(false);
const [search, setSearch] = useState(false);
const [cartOpen, setCartOpen] = useState(false);
const mobileMenuRef = useRef(null);
const [wishListOpen, setWishListOpen] = useState(false);

const wishListData = useSelector((state) => state.wishList);

// Handle shop button click
const handleShopClick = () => {
Expand Down Expand Up @@ -95,13 +100,22 @@ export default function Header() {
<Search className="h-5 w-5" />
</button>

<a
href="#"
<button
aria-label="Wishlist"
className="transform transition-transform duration-200 hover:scale-110 hover:text-black"
onClick={() => setWishListOpen(true)}
className="relative transform transition-transform duration-200 hover:scale-110 hover:text-black cursor-pointer"
>
<Heart className="h-5 w-5" />
</a>

{wishListData.items.length > 0 && (
<span
className="absolute -top-2 -right-2 bg-red-500 text-white text-xs font-bold
rounded-full h-4 w-4 flex items-center justify-center"
>
{wishListData.items.length}
</span>
)}
</button>

<Link
to="/login"
Expand Down Expand Up @@ -319,7 +333,13 @@ export default function Header() {
</div>

{/* Search Drawer */}
<SearchBox isOpen={search} onClose={() => setSearch(false)} />
{wishListOpen && (
<WishListScreen
setWishListOpen={setWishListOpen}
wishListData={wishListData}
/>
)}
{search && <SearchBox isOpen={search} onClose={() => setSearch(false)} />}

{/* Cart Drawer */}
<CartDrawer isOpen={cartOpen} onClose={() => setCartOpen(false)} />
Expand Down
19 changes: 8 additions & 11 deletions src/components/Products/ProductCard.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useState, forwardRef, useRef, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { addRecentlyViewed } from '../../utils/recentlyViewed';

import { addToWishList, removeFromWishList } from '../../store/wishListSlice';
import { useDispatch } from 'react-redux';
const HeartIcon = ({
isWishlisted = false,
animate = false,
Expand Down Expand Up @@ -33,13 +34,7 @@ const CartIcon = ({ className = 'h-6 w-6' }) => (

const ProductCard = forwardRef(
(
{
product,
onAddToWishlist,
onAddToCart,
isWishlisted: initialWishlisted = false,
isInCart,
},
{ product, onAddToCart, isWishlisted: initialWishlisted = false, isInCart },
ref
) => {
const navigate = useNavigate();
Expand All @@ -55,7 +50,7 @@ const ProductCard = forwardRef(
const [cartAdded, setCartAdded] = useState(false);
const cartLoadingTimeoutRef = useRef(null);
const cartAddedTimeoutRef = useRef(null);

const dispatch = useDispatch();
// Check if current product+flavor is in cart
const itemIsInCart = isInCart ? isInCart(selectedFlavor) : false;

Expand Down Expand Up @@ -112,8 +107,10 @@ const ProductCard = forwardRef(
if (likeTimeoutRef.current) clearTimeout(likeTimeoutRef.current);
likeTimeoutRef.current = setTimeout(() => setAnimateLike(false), 520);
// Call the toggle wishlist function
if (onAddToWishlist) {
onAddToWishlist(product);
if (isWishlisted) {
dispatch(removeFromWishList(product));
} else {
dispatch(addToWishList(product));
}
};

Expand Down
173 changes: 173 additions & 0 deletions src/components/WishList/WishiListScreen.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import React, { useEffect, useRef, useMemo } from 'react';
import { X } from 'lucide-react';
import { useDispatch } from 'react-redux';
import { removeFromWishList } from '../../store/wishListSlice';
import { Heart } from 'lucide-react';

const WishListProductCard = ({ product, removeFromWishList }) => {
if (!product) return null;
const { name, price, imageUrl, salePercentage, flavor } = product;
const priceStr = typeof price === 'number' ? price.toFixed(2) : price;
return (
<div className="w-full max-w-lg p-4 flex ">
{/* Left side container for image and product info */}
<div className="flex ">
{/* Product Image */}
<img
src={
imageUrl ||
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgdmlld0JveD0iMCAwIDMwMCAzMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIzMDAiIGhlaWdodD0iMzAwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik0xMjUgMTI1SDEzNVYxMzVIMTI1VjEyNVpNMTM1IDEyNUgxNDVWMTM1SDEzNVYxMjVaTTE0NSAxMjVIMTU1VjEzNUgxNDVWMTI1Wk0xNTUgMTI1SDE2NVYxMzVIMTU1VjEyNVpNMTY1IDEyNUgxNzVWMTM1SDE2NVYxMjVaIiBmaWxsPSIjOUI5QkEzIi8+CjxwYXRoIGQ9Ik0xMzUgMTQ1SDE0NVYxNTVIMTM1VjE0NVpNMTQ1IDE0NUgxNTVWMTU1SDE0NVYxNDVaTTE1NSAxNDVIMTY1VjE1NUgxNTVWMTQ1WiIgZmlsbD0iIzlCOUJBMyIvPgo8L3N2Zz4K'
}
alt={name}
className={`w-24 h-24 object-contain mr-6 `}
onLoad={() => {}}
onError={() => {}}
loading="lazy"
/>

{/* Product Info */}
<div className="flex flex-col">
<h3 className="text-sm font-medium text-gray-800">{name}</h3>
<div className="flex items-center space-x-2 my-1">
<span className="text-gray-800 text-sm">${priceStr}</span>
<span className="bg-blue-600 text-white text-xs font-semibold py-0.5 px-1.5 rounded">
{salePercentage}% OFF
</span>
</div>
<p className="text-gray-500 text-sm">{flavor}</p>
</div>
<div className="right-0 absolute pr-4">
{/* Wishlist Icon on the far right */}
<button
onClick={removeFromWishList}
className="text-gray-800 hover:text-red-500"
aria-label="Toggle wishlist"
>
<Heart className="h-6 w-6" fill="currentColor" />
</button>
</div>
</div>
</div>
);
};
const WishListScreen = ({ wishListData, setWishListOpen }) => {
const dispatch = useDispatch();
const drawerRef = useRef(null);

// 🔹 Close on Escape key
useEffect(() => {
const handleKeyDown = (e) => {
if (e.key === 'Escape') setWishListOpen(false);

if (e.key === 'Tab' && drawerRef.current) {
const focusableEls = drawerRef.current.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (!focusableEls.length) return;

const first = focusableEls[0];
const last = focusableEls[focusableEls.length - 1];

if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
};

document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [setWishListOpen]);

useEffect(() => {
if (drawerRef.current) {
const firstEl = drawerRef.current.querySelector(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
firstEl?.focus();
}
}, [wishListData]);

const wishListScreen = useMemo(() => {
return (
<div
className="fixed inset-0 bg-black/40 z-[999]"
role="presentation"
onClick={() => setWishListOpen(false)}
>
{/* Drawer */}
<div
role="dialog"
aria-modal="true"
aria-labelledby="wishlist-title"
className="fixed top-0 right-0 h-screen w-full max-w-md bg-white shadow-lg z-[1000] flex flex-col transition-transform duration-300 focus:outline-none"
ref={drawerRef}
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex justify-between items-center border-b p-4">
<h2
id="wishlist-title"
className="text-xl font-bold text-[#0b1a39] tracking-wide"
>
WISHLIST
</h2>
<button
aria-label="Close wishlist"
className="text-gray-600 hover:text-red-500 cursor-pointer"
onClick={() => setWishListOpen(false)}
>
<X size={24} />
</button>
</div>

{/* Wishlist Content */}
{wishListData.items.length === 0 ? (
<div className="flex flex-col flex-grow items-center justify-center text-center p-4">
<p className="text-gray-500 mb-2">Your wishlist is empty.</p>
<button
className="text-sm text-[#0b1a39] underline hover:text-black"
onClick={() => setWishListOpen(false)}
>
Start adding your favorite supplements!
</button>
</div>
) : (
<>
<div className="flex-grow overflow-y-auto p-4">
{wishListData.items.map((prod) => (
<WishListProductCard
key={prod.id}
product={prod}
removeFromWishList={() =>
dispatch(removeFromWishList(prod))
}
/>
))}
</div>

{/* Footer */}
<div className="p-4 mt-auto border-t">
<button
className="w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
onClick={() => {
// Add all add to cart slicer here in future
}}
>
Add All To Cart
</button>
</div>
</>
)}
</div>
</div>
);
}, [wishListData, dispatch, setWishListOpen]);

return wishListScreen;
};

export default WishListScreen;
2 changes: 2 additions & 0 deletions src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { configureStore } from '@reduxjs/toolkit';
import productSlice from './productSlice';
import cartSlice from './cartSlice';
import authSlice from './authSlice';
import wishListSlice from './wishListSlice';
import collectionSlice from './CollectionSlice';

const store = configureStore({
reducer: {
products: productSlice,
cart: cartSlice,
auth: authSlice,
wishList: wishListSlice,
collections: collectionSlice,
},
middleware: (getDefaultMiddleware) =>
Expand Down
25 changes: 25 additions & 0 deletions src/store/wishListSlice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createSlice } from '@reduxjs/toolkit';

//initialState for wishlist
const initialState = {
items: [],
};

//wishlist slice (stub implementation)
const wishListSlice = createSlice({
name: 'wishList',
initialState,
reducers: {
addToWishList: (state, action) => {
if (!state.items.some((i) => i.id === action.payload.id))
state.items.push(action.payload);
},
removeFromWishList: (state, action) => {
state.items = state.items.filter((item) => item.id !== action.payload.id);
},
},
});

export const { addToWishList, removeFromWishList } = wishListSlice.actions;

export default wishListSlice.reducer;
4 changes: 2 additions & 2 deletions src/utils/wishlist.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function isInWishlist(productId) {
}

export function addToWishlist(product) {
if (!product || !product.id && !product._id) return false; // Ensure product has an ID
if (!product || (!product.id && !product._id)) return false; // Ensure product has an ID
try {
const current = getWishlist();
const productId = product.id || product._id;
Expand Down Expand Up @@ -63,7 +63,7 @@ export function removeFromWishlist(productId) {
}

export function toggleWishlist(product) {
if (!product || !product.id && !product._id) return false; // Ensure product has an ID
if (!product || (!product.id && !product._id)) return false; // Ensure product has an ID

const productId = product.id || product._id;
const isCurrentlyInWishlist = isInWishlist(productId);
Expand Down
Loading