Implement shopping cart functionality with dedicated context, product integration, and UI components. and removed unwanted components

This commit is contained in:
Alaguraj0361 2025-12-12 17:42:50 +05:30
parent a7e48ddb6c
commit 3d8be45a96
42 changed files with 210 additions and 1621 deletions

View File

@ -16,43 +16,25 @@ import { BaseURL } from '../../../utils/BaseUrl'
const Cart = () => {
const [timeLeft, setTimeLeft] = useState(countdownTime());
const router = useRouter()
const [cart, setCart] = useState([])
const [token, setToken] = useState<string | null>()
const { cartState, updateCart, removeFromCart } = useCart();
const [isLoggedIn, setIsLoggedIn] = useState(false)
// Check if user is logged in
useEffect(() => {
getCartData();
}, []);
const token = localStorage.getItem('token')
setIsLoggedIn(!!token)
useEffect(() => {
const Token = localStorage.getItem('token')
setToken(Token)
// If logged in, optionally sync with API (cart is already in cartState)
if (token) {
console.log("✅ Logged in - using CartContext (already synced)")
} else {
console.log("🛒 Guest mode - using CartContext from localStorage")
}
}, [])
const getCartData = async () => {
try {
const token = localStorage.getItem("token"); // should be plain token string
console.log("Stored token:", token);
if (!token) {
console.error("❌ No token found in localStorage");
return;
}
const res: any = await axios.get(`${BaseURL}/cart`, {
headers: {
Authorization: `Bearer ${token}`, // must include "Bearer "
"Content-Type": "application/json",
},
withCredentials: true, // if backend uses cookies + JWT
});
setCart(res?.data?.cart?.items)
console.log("Cart response ✅:", res.data);
} catch (error: any) {
console.error("❌ Error fetching cart:", error.response?.data || error.message);
}
};
console.log("cart", cart)
// ✅ Use cartState.cartArray as the source of truth for BOTH guest and logged-in users
const cart = cartState.cartArray;
console.log("📦 Cart from CartContext:", cart)
useEffect(() => {
@ -63,57 +45,40 @@ const Cart = () => {
return () => clearInterval(timer);
}, []);
const { cartState, updateCart, removeFromCart } = useCart();
// Handle quantity change for both guest and logged-in users
const handleQuantityChange = async (item: any, newQuantity: number) => {
if (newQuantity < 1) return;
// Optimistic Update: Update local state immediately
const updatedCart: any = cart.map((cartItem: any) => {
if (cartItem._id === item._id) {
return { ...cartItem, quantity: newQuantity };
// Get the product ID (handle both formats)
const productId = item._id || item.id;
const size = item.selectedSize || item.size;
const color = item.selectedColor || item.color;
// Update CartContext immediately (works for both guest and logged-in)
updateCart(productId, newQuantity, size, color);
// If logged in, also sync with API
if (isLoggedIn) {
const token = localStorage.getItem("token");
const payload = {
quantity: newQuantity,
color: color,
size: size
};
try {
await axios.put(
`${BaseURL}/cart/${productId}`,
payload,
{
headers: { Authorization: `Bearer ${token}` },
}
);
console.log("✅ Cart quantity updated on API");
} catch (error) {
console.error("❌ Error updating cart on API:", error);
}
return cartItem;
});
setCart(updatedCart);
// Optimistic Update: Update Context (for header, etc.)
if (item.product && item.product._id) {
updateCart(item.product._id, newQuantity, item.size, item.color);
}
const payload = {
quantity: newQuantity,
color: item.color,
size: item.size
}
const token = localStorage.getItem("token")
try {
const res = await axios.put(
`${BaseURL}/cart/${item.product._id}`, // ✅ use product._id, not item._id
payload,
{
headers: { Authorization: `Bearer ${token}` },
}
)
console.log("Updated Cart:", res.data)
getCartData()
} catch (error) {
console.log("error", error)
}
// const itemToUpdate = cart.find((item) => item._id === cart._id);
// // Kiểm tra xem sản phẩm có tồn tại không
// if (itemToUpdate) {
// // Truyền giá trị hiện tại của selectedSize và selectedColor
// updateCart(cart._id, newQuantity, itemToUpdate.selectedSize, itemToUpdate.selectedColor);
// }
};
let moneyForFreeship = 150;
@ -157,30 +122,25 @@ const Cart = () => {
const handleRemoveCart = async (item: any) => {
// Optimistic update for local cart
const updatedCart = cart.filter((cartItem: any) => cartItem._id !== item._id);
setCart(updatedCart);
const token = localStorage.getItem("token");
const productId = item._id || item.id;
const size = item.selectedSize || item.size;
const color = item.selectedColor || item.color;
// ✅ Immediately remove from CartContext (localStorage + Redux)
const cartItem = cartState.cartArray.find(
(cartItem) => (cartItem.id === item.product._id || cartItem.id === item.product.id)
);
if (cartItem) {
removeFromCart(cartItem.id);
}
// Remove from CartContext immediately (works for both)
removeFromCart(productId);
// ✅ Then sync with API
try {
const res = await axios.delete(`${BaseURL}/cart/${item.product._id}`, {
headers: { Authorization: `Bearer ${token}` },
params: { color: item.color, size: item.size }, // ✅ query params
});
getCartData();
console.log("Removed from cart:", res.data);
} catch (error: any) {
console.error("Error removing item:", error.response?.data || error.message);
// If logged in, also sync with API
if (isLoggedIn) {
const token = localStorage.getItem("token");
try {
await axios.delete(`${BaseURL}/cart/${productId}`, {
headers: { Authorization: `Bearer ${token}` },
params: { color, size },
});
console.log("✅ Item removed from API cart");
} catch (error: any) {
console.error("❌ Error removing from API cart:", error.response?.data || error.message);
}
}
};
@ -238,58 +198,72 @@ const Cart = () => {
{cart?.length < 1 ? (
<p className='text-button pt-3'>No product in cart</p>
) : (
cart?.map((cart: any) => (
<div className="item flex md:mt-7 md:pb-7 mt-5 pb-5 border-b border-line w-full" key={cart._id}>
<div className="w-1/2">
<div className="flex items-center gap-6">
<div className="bg-img md:w-[100px] w-20 aspect-[3/4]">
<Image
src={cart.product.thumbImage[0]}
width={1000}
height={1000}
alt={cart.product.name}
className='w-full h-full object-cover rounded-lg'
cart?.map((item: any) => {
// Handle both API format (item.product.name) and CartContext format (item.name)
const itemId = item._id || item.id;
const itemName = item.product?.name || item.name;
const itemImage = item.product?.thumbImage?.[0] || item.thumbImage?.[0] || item.images?.[0];
const itemPrice = item.price;
const itemQuantity = item.quantity || item.quantityPurchase || 1;
const itemSize = item.selectedSize || item.size || item.sizes?.[0];
const itemColor = item.selectedColor || item.color || item.variation?.[0]?.color;
return (
<div className="item flex md:mt-7 md:pb-7 mt-5 pb-5 border-b border-line w-full" key={itemId}>
<div className="w-1/2">
<div className="flex items-center gap-6">
<div className="bg-img md:w-[100px] w-20 aspect-[3/4]">
<Image
src={itemImage}
width={1000}
height={1000}
alt={itemName}
className='w-full h-full object-cover rounded-lg'
/>
</div>
<div>
<div className="text-title">{itemName}</div>
<div className="list-select mt-3">
<span className='text-secondary2'>Size: {itemSize}</span>
{itemColor && <span className='text-secondary2 ml-2'>Color: {itemColor}</span>}
</div>
</div>
</div>
</div>
<div className="w-1/12 price flex items-center justify-center">
<div className="text-title text-center">${itemPrice}.00</div>
</div>
<div className="w-1/6 flex items-center justify-center">
<div className="quantity-block bg-surface md:p-3 p-2 flex items-center justify-between rounded-lg border border-line md:w-[100px] flex-shrink-0 w-20">
<Icon.Minus
onClick={() => {
if (itemQuantity > 1) {
handleQuantityChange(item, itemQuantity - 1)
}
}}
className={`text-base max-md:text-sm cursor-pointer ${itemQuantity === 1 ? 'opacity-50' : ''}`}
/>
<div className="text-button quantity">{itemQuantity}</div>
<Icon.Plus
onClick={() => handleQuantityChange(item, itemQuantity + 1)}
className='text-base max-md:text-sm cursor-pointer'
/>
</div>
<div>
<div className="text-title">{cart.product.name}</div>
<div className="list-select mt-3"></div>
</div>
</div>
</div>
<div className="w-1/12 price flex items-center justify-center">
<div className="text-title text-center">${cart.price}.00</div>
</div>
<div className="w-1/6 flex items-center justify-center">
<div className="quantity-block bg-surface md:p-3 p-2 flex items-center justify-between rounded-lg border border-line md:w-[100px] flex-shrink-0 w-20">
<Icon.Minus
<div className="w-1/6 flex total-price items-center justify-center">
<div className="text-title text-center">${itemQuantity * itemPrice}.00</div>
</div>
<div className="w-1/12 flex items-center justify-center">
<Icon.XCircle
className='text-xl max-md:text-base text-red cursor-pointer hover:text-black duration-500'
onClick={() => {
if (cart.quantity > 1) {
handleQuantityChange(cart, cart.quantity - 1)
}
handleRemoveCart(item)
}}
className={`text-base max-md:text-sm ${cart.quantity === 1 ? 'disabled' : ''}`}
/>
<div className="text-button quantity">{cart.quantity}</div>
<Icon.Plus
onClick={() => handleQuantityChange(cart, cart.quantity + 1)}
className='text-base max-md:text-sm'
/>
</div>
</div>
<div className="w-1/6 flex total-price items-center justify-center">
<div className="text-title text-center">${cart.quantity * cart.price}.00</div>
</div>
<div className="w-1/12 flex items-center justify-center">
<Icon.XCircle
className='text-xl max-md:text-base text-red cursor-pointer hover:text-black duration-500'
onClick={() => {
handleRemoveCart(cart)
}}
/>
</div>
</div>
))
);
})
)}
</div>
</div>

View File

@ -1,32 +0,0 @@
'use client'
import React from 'react'
import { useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import BoughtTogether from '@/components/Product/Detail/BoughtTogether';
import Footer from '@/components/Footer/Footer'
import productData from '@/data/Product.json'
const ProductBoughtTogether = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='bought-together' productId={productId} />
</div>
<BoughtTogether data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductBoughtTogether

View File

@ -1,32 +0,0 @@
'use client'
import React from 'react'
import { useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import VariableProduct from '@/components/Product/Detail/VariableProduct';
import Footer from '@/components/Footer/Footer'
import productData from '@/data/Product.json'
const ProductCombinedOne = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='variable' productId={productId} />
</div>
<VariableProduct data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductCombinedOne

View File

@ -1,32 +0,0 @@
'use client'
import React from 'react'
import { useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import External from '@/components/Product/Detail/External';
import Footer from '@/components/Footer/Footer'
import productData from '@/data/Product.json'
const ProductCombinedTwo = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='external' productId={productId} />
</div>
<External data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductCombinedTwo

View File

@ -1,32 +0,0 @@
'use client'
import React from 'react'
import { useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import CountdownTimer from '@/components/Product/Detail/CountdownTimer';
import Footer from '@/components/Footer/Footer'
import productData from '@/data/Product.json'
const ProductCountdownTimer = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='countdown-timer' productId={productId} />
</div>
<CountdownTimer data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductCountdownTimer

View File

@ -1,34 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useSearchParams } from 'next/navigation';
import Link from 'next/link'
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import Default from '@/components/Product/Detail/Default';
import Footer from '@/components/Footer/Footer'
import { ProductType } from '@/type/ProductType'
import productData from '@/data/Product.json'
const ProductDefault = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='default' productId={productId} />
</div>
<Default data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductDefault

View File

@ -1,32 +0,0 @@
'use client'
import React from 'react'
import { useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import Discount from '@/components/Product/Detail/Discount';
import Footer from '@/components/Footer/Footer'
import productData from '@/data/Product.json'
const ProductDiscount = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full style-discount'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='discount' productId={productId} />
</div>
<Discount data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductDiscount

View File

@ -1,32 +0,0 @@
'use client'
import React from 'react'
import { useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import External from '@/components/Product/Detail/External';
import Footer from '@/components/Footer/Footer'
import productData from '@/data/Product.json'
const ProductExternal = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='external' productId={productId} />
</div>
<External data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductExternal

View File

@ -1,32 +0,0 @@
'use client'
import React from 'react'
import { useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import FixedPrice from '@/components/Product/Detail/FixedPrice';
import Footer from '@/components/Footer/Footer'
import productData from '@/data/Product.json'
const ProductFixedPrice = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='fixed-price' productId={productId} />
</div>
<FixedPrice data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductFixedPrice

View File

@ -1,32 +0,0 @@
'use client'
import React from 'react'
import { useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import Grouped from '@/components/Product/Detail/Grouped';
import Footer from '@/components/Footer/Footer'
import productData from '@/data/Product.json'
const ProductGrouped = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='grouped' productId={productId} />
</div>
<Grouped data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductGrouped

View File

@ -1,32 +0,0 @@
'use client'
import React from 'react'
import { useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import OnSale from '@/components/Product/Detail/OnSale';
import Footer from '@/components/Footer/Footer'
import productData from '@/data/Product.json'
const ProductOnSale = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='on-sale' productId={productId} />
</div>
<OnSale data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductOnSale

View File

@ -1,32 +0,0 @@
'use client'
import React from 'react'
import { useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import Grouped from '@/components/Product/Detail/Grouped';
import Footer from '@/components/Footer/Footer'
import productData from '@/data/Product.json'
const ProductOneScrolling = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='grouped' productId={productId} />
</div>
<Grouped data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductOneScrolling

View File

@ -1,32 +0,0 @@
'use client'
import React from 'react'
import { useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import OutOfStock from '@/components/Product/Detail/OutOfStock';
import Footer from '@/components/Footer/Footer'
import productData from '@/data/Product.json'
const ProductOutOfStock = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='out-of-stock' productId={productId} />
</div>
<OutOfStock data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductOutOfStock

View File

@ -1,34 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useSearchParams } from 'next/navigation';
import Link from 'next/link'
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import Sale from '@/components/Product/Detail/Sale';
import Footer from '@/components/Footer/Footer'
import { ProductType } from '@/type/ProductType'
import productData from '@/data/Product.json'
const ProductSale = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='sale' productId={productId} />
</div>
<Sale data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductSale

View File

@ -1,34 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useSearchParams } from 'next/navigation';
import Link from 'next/link'
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import Sidebar from '@/components/Product/Detail/Sidebar';
import Footer from '@/components/Footer/Footer'
import { ProductType } from '@/type/ProductType'
import productData from '@/data/Product.json'
const ProductSidebar = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='sidebar' productId={productId} />
</div>
<Sidebar data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductSidebar

View File

@ -1,25 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopFilterCanvas from '@/components/Shop/ShopFilterCanvas'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function FilterCanvasProductOne() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopFilterCanvas data={productData} productPerPage={12} dataType={type} productStyle='style-1' />
<Footer />
</>
)
}

View File

@ -1,25 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopFilterCanvas from '@/components/Shop/ShopFilterCanvas'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function FilterCanvasProductTwo() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopFilterCanvas data={productData} productPerPage={12} dataType={type} productStyle='style-2' />
<Footer />
</>
)
}

View File

@ -1,25 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopFilterCanvas from '@/components/Shop/ShopFilterCanvas'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function FilterCanvasProductThree() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopFilterCanvas data={productData} productPerPage={12} dataType={type} productStyle='style-3' />
<Footer />
</>
)
}

View File

@ -1,25 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopFilterCanvas from '@/components/Shop/ShopFilterCanvas'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function FilterCanvasProductFour() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopFilterCanvas data={productData} productPerPage={12} dataType={type} productStyle='style-4' />
<Footer />
</>
)
}

View File

@ -1,25 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopFilterCanvas from '@/components/Shop/ShopFilterCanvas'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function FilterCanvasProductFive() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopFilterCanvas data={productData} productPerPage={12} dataType={type} productStyle='style-5' />
<Footer />
</>
)
}

View File

@ -1,33 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useSearchParams } from 'next/navigation';
import Link from 'next/link'
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import Sale from '@/components/Product/Detail/Sale';
import Footer from '@/components/Footer/Footer'
import productData from '@/data/Product.json'
const ProductThumbnailBottom = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='sale' productId={productId} />
</div>
<Sale data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductThumbnailBottom

View File

@ -1,34 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useSearchParams } from 'next/navigation';
import Link from 'next/link'
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import Default from '@/components/Product/Detail/Default';
import Footer from '@/components/Footer/Footer'
import { ProductType } from '@/type/ProductType'
import productData from '@/data/Product.json'
const ProductThumbnailLeft = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='default' productId={productId} />
</div>
<Default data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductThumbnailLeft

View File

@ -1,32 +0,0 @@
'use client'
import React from 'react'
import { useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import CountdownTimer from '@/components/Product/Detail/CountdownTimer';
import Footer from '@/components/Footer/Footer'
import productData from '@/data/Product.json'
const ProductTwoScrolling = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='countdown-timer' productId={productId} />
</div>
<CountdownTimer data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductTwoScrolling

View File

@ -1,32 +0,0 @@
'use client'
import React from 'react'
import { useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import BreadcrumbProduct from '@/components/Breadcrumb/BreadcrumbProduct'
import VariableProduct from '@/components/Product/Detail/VariableProduct';
import Footer from '@/components/Footer/Footer'
import productData from '@/data/Product.json'
const ProductVariableProduct = () => {
const searchParams = useSearchParams()
let productId = searchParams.get('id')
if (productId === null) {
productId = '1'
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-white" />
<BreadcrumbProduct data={productData} productPage='variable' productId={productId} />
</div>
<VariableProduct data={productData} productId={productId} />
<Footer />
</>
)
}
export default ProductVariableProduct

View File

@ -1,61 +0,0 @@
'use client'
import React, { useState, useEffect } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopBreadCrumbImg from '@/components/Shop/ShopBreadCrumbImg';
import Footer from '@/components/Footer/Footer'
import { BaseURL } from '../../../../utils/BaseUrl'
import axios from 'axios';
export default function BreadcrumbImg() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
const categoryName = searchParams.get('category')
const [products, setProducts] = useState([])
const [loading, setLoading] = useState(true)
const [categories, setCategories] = useState([])
useEffect(() => {
fetchProducts()
getCategory()
}, [])
const fetchProducts = async () => {
try {
const response = await axios.get(`${BaseURL}/products`)
setProducts(response?.data?.data)
setLoading(false)
} catch (error) {
console.error('Error fetching products:', error)
setLoading(false)
}
}
const getCategory = async () => {
try {
const res: any = await axios?.get(`${BaseURL}/categories`)
setCategories(res?.data?.data)
} catch (error) {
console.log(error)
}
}
if (loading) {
return <div>Loading...</div>
}
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopBreadCrumbImg data={products} productPerPage={12} dataType={categoryName} category={categories} />
<Footer />
</>
)
}

View File

@ -1,33 +0,0 @@
'use client'
import React, { useState, useEffect } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopBreadCrumb1 from '@/components/Shop/ShopBreadCrumb1'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function BreadCrumb1() {
const searchParams = useSearchParams()
let [type,setType] = useState<string | null | undefined>()
let datatype = searchParams.get('type')
let gender = searchParams.get('gender')
let category = searchParams.get('category')
useEffect(() => {
setType(datatype);
}, [datatype]);
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopBreadCrumb1 data={productData} productPerPage={9} dataType={type} gender={gender} category={category} />
<Footer />
</>
)
}

View File

@ -1,26 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopBreadCrumb2 from '@/components/Shop/ShopBreadCrumb2'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function BreadCrumb2() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
const category = searchParams.get('category')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopBreadCrumb2 data={productData} productPerPage={9} dataType={type} />
<Footer />
</>
)
}

View File

@ -1,24 +0,0 @@
'use client'
import React, { useState } from 'react'
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import Breadcrumb from '@/components/Breadcrumb/Breadcrumb'
import productData from '@/data/Product.json'
import ShopCollection from '@/components/Shop/ShopCollection'
import Footer from '@/components/Footer/Footer'
export default function Collection() {
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
<Breadcrumb heading='Shop Collection' subHeading='Collection' />
</div>
<ShopCollection data={productData} />
<Footer />
</>
)
}

View File

@ -1,27 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopBreadCrumb1 from '@/components/Shop/ShopBreadCrumb1'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function DefaultGrid() {
const searchParams = useSearchParams()
let type = searchParams.get('type')
let gender = searchParams.get('gender')
let category = searchParams.get('category')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopBreadCrumb1 data={productData} productPerPage={9} dataType={type} gender={gender} category={category} />
<Footer />
</>
)
}

View File

@ -1,26 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopSidebarList from '@/components/Shop/ShopSidebarList'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function DefaultList() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
const category = searchParams.get('category')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopSidebarList data={productData} productPerPage={4} dataType={type} />
<Footer />
</>
)
}

View File

@ -1,26 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopBreadCrumbImg from '@/components/Shop/ShopBreadCrumbImg';
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function Default() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
const category = searchParams.get('category')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopBreadCrumbImg data={productData} productPerPage={12} dataType={type} />
<Footer />
</>
)
}

View File

@ -1,362 +0,0 @@
'use client';
import { useEffect, useRef, useState } from 'react';
import { fabric } from 'fabric';
import { useSearchParams } from 'next/navigation';
import { BaseURL } from '../../../../utils/BaseUrl';
export default function TshirtCustomizer() {
const searchParams = useSearchParams();
const productSlug = searchParams.get('slug');
const canvasRef = useRef<fabric.Canvas | null>(null);
const [shirtColor, setShirtColor] = useState('#ffffff');
const [textInput, setTextInput] = useState('');
const [fontFamily, setFontFamily] = useState('Arial');
const [layers, setLayers] = useState<{ name: string; id: number }[]>([]);
const [history, setHistory] = useState<any[]>([]);
const [redoStack, setRedoStack] = useState<any[]>([]);
const [products, setProducts] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [shirtImageObject, setShirtImageObject] = useState<fabric.Image | null>(null);
useEffect(() => {
if (productSlug) fetchProducts();
}, [productSlug]);
const fetchProducts = async () => {
try {
const response = await fetch(`${BaseURL}/products/slug/${productSlug}`);
const data = await response.json();
setProducts(data?.data);
setLoading(false);
} catch (error) {
console.error('Error fetching product:', error);
setLoading(false);
}
};
useEffect(() => {
const canvas = new fabric.Canvas('tshirt-canvas', {
width: window.innerWidth < 500 ? 300 : 500,
height: 600,
});
canvasRef.current = canvas;
saveHistory();
updateLayers();
canvas.on('mouse:wheel', (opt) => {
const delta = opt.e.deltaY;
let zoom = canvas.getZoom();
zoom *= 0.999 ** delta;
zoom = Math.min(Math.max(zoom, 0.5), 2);
canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom);
opt.e.preventDefault();
opt.e.stopPropagation();
});
canvas.on('object:added', updateLayers);
canvas.on('object:removed', updateLayers);
canvas.on('object:modified', saveHistory);
canvas.on('selection:created', updateLayers);
canvas.on('selection:updated', updateLayers);
return () => canvas.dispose();
}, []);
useEffect(() => {
if (products && canvasRef.current) {
const imageUrl = products?.thumbImage?.[0];
if (imageUrl) {
const existing = canvasRef.current
.getObjects('image')
.find((img) => (img as fabric.Image).getSrc?.() === imageUrl);
if (!existing) {
fabric.Image.fromURL(
imageUrl,
(img) => {
img.set({
left: 50,
top: 50,
scaleX: 0.5,
scaleY: 0.5,
selectable: false,
});
setShirtImageObject(img);
canvasRef.current?.add(img);
canvasRef.current?.sendToBack(img);
canvasRef.current?.renderAll();
saveHistory();
},
{ crossOrigin: 'anonymous' } // ✅ FIXED HERE
);
}
}
}
}, [products]);
const applyColorFilter = (color: string) => {
const canvas = canvasRef.current;
if (!canvas || !shirtImageObject) return;
try {
const blendColorFilter = new (fabric as any).Image.filters.BlendColor({
color,
mode: 'tint',
alpha: 0.5,
});
shirtImageObject.filters = [blendColorFilter];
shirtImageObject.applyFilters();
canvas.renderAll();
saveHistory();
} catch (err) {
console.error('Color filter failed:', err);
alert('Failed to apply color filter. Make sure image is CORS-enabled.');
}
};
const updateLayers = () => {
const objs = canvasRef.current?.getObjects() || [];
setLayers(objs.map((obj, i) => ({ name: obj.type + ' ' + (i + 1), id: i })));
};
const saveHistory = () => {
const json = canvasRef.current?.toJSON();
if (json) setHistory((prev) => [...prev, json.objects]);
setRedoStack([]);
};
const handleUndo = () => {
if (!canvasRef.current || history.length <= 1) return;
const last = history[history.length - 2];
setRedoStack((r) => [canvasRef.current?.toJSON().objects, ...r]);
canvasRef.current.clear();
canvasRef.current.loadFromJSON(
{ objects: last },
canvasRef.current.renderAll.bind(canvasRef.current)
);
setHistory((h) => h.slice(0, -1));
};
const handleRedo = () => {
if (!canvasRef.current || redoStack.length === 0) return;
const next = redoStack[0];
setRedoStack((r) => r.slice(1));
setHistory((h) => [...h, next]);
canvasRef.current.clear();
canvasRef.current.loadFromJSON(
{ objects: next },
canvasRef.current.renderAll.bind(canvasRef.current)
);
};
const handleUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files;
if (!files || !canvasRef.current) return;
Array.from(files).forEach((file) => {
const reader = new FileReader();
reader.onload = () => {
if (typeof reader.result === 'string') {
fabric.Image.fromURL(reader.result, (img) => {
img.scale(0.4).set({ left: 50, top: 50 });
canvasRef.current?.add(img);
saveHistory();
});
}
};
reader.readAsDataURL(file);
});
};
const addText = () => {
if (!textInput.trim() || !canvasRef.current) return;
const text = new fabric.Textbox(textInput, {
left: 100,
top: 100,
fontFamily,
fill: '#000',
fontSize: 30,
});
canvasRef.current.add(text);
saveHistory();
};
const loadTemplate = async () => {
const res = await fetch('/api/designs/my-template');
const data = await res.json();
if (canvasRef.current && data?.json) {
canvasRef.current.loadFromJSON(
data.json,
canvasRef.current.renderAll.bind(canvasRef.current)
);
}
};
const downloadImage = () => {
const dataURL = canvasRef.current?.toDataURL({ format: 'png', quality: 1 });
const link = document.createElement('a');
link.href = dataURL!;
link.download = 'tshirt-design.png';
link.click();
};
const deleteObject = (index: number) => {
const obj = canvasRef.current?.getObjects()[index];
if (obj) {
canvasRef.current?.remove(obj);
updateLayers();
saveHistory();
}
};
return (
<div className="product-detail sale sidebar">
<div className="featured-product underwear md:py-20 py-10">
<div className="container flex justify-between gap-y-6 flex-wrap max-sm:flex-col-reverse">
<div className="sidebar lg:w-1/4 sm:w-[40%] lg:pr-12 sm:pr-10 w-full">
<div className="filter-type pb-8 border-b border-line">
<div className="heading6">Products Type</div>
</div>
<div className="benefit pb-3 border-b border-line mt-3">
<div className="heading6">T-shirt Color</div>
<input type="color" value={shirtColor} onChange={(e) => { setShirtColor(e.target.value); applyColorFilter(e.target.value); }} className="w-full h-10 border mb-4" />
</div>
<div className="benefit pb-3 border-b border-line mt-3">
<div className="heading6">Add Text</div>
<input type="text" value={textInput} onChange={(e) => setTextInput(e.target.value)} className="w-full border mb-2" placeholder="Type text" />
<select value={fontFamily} onChange={(e) => setFontFamily(e.target.value)} className="w-full p-2 border">
<option value="Arial">Arial</option>
<option value="Courier New">Courier New</option>
<option value="Georgia">Georgia</option>
<option value="Comic Sans MS">Comic Sans</option>
<option value="Impact">Impact</option>
</select>
<button onClick={addText} className="w-full p-2 bg-black text-white">Add Text</button>
</div>
<div className="benefit pb-3 border-b border-line mt-3">
<div className="heading6">Upload Images</div>
<input type="file" accept="image/*" onChange={handleUpload} className="mb-2" />
</div>
<div className="benefit pb-3 border-b border-line mt-3">
<div className="heading6">History</div>
<div className="flex justify-center mt-2 gap-2">
<button onClick={handleUndo} className="w-full bg-black text-white p-2 mb-2 rounded">Undo</button>
<button onClick={handleRedo} className="w-full bg-black text-white p-2 mb-2 rounded">Redo</button>
</div>
</div>
<div className="benefit pb-3 border-b border-line mt-3">
<div className="heading6">Sample</div>
<div className="flex justify-center mt-2 gap-2">
<button onClick={loadTemplate} className="w-full p-2 bg-black text-white">Load Sample</button>
<button onClick={downloadImage} className="w-full p-2 bg-black text-white">Download</button>
</div>
</div>
<div className="benefit pb-3 border-b border-line mt-3 ">
<div className="heading6">Canvas Controls</div>
<div className="flex gap-2 mt-2">
<button onClick={() => canvasRef.current?.setZoom(canvasRef.current.getZoom() + 0.1)} className="w-full p-2 bg-black text-white">Zoom In</button>
<button onClick={() => canvasRef.current?.setZoom(canvasRef.current.getZoom() - 0.1)} className="w-full p-2 bg-black text-white">Zoom Out</button>
</div>
<button
onClick={() => {
if (!canvasRef.current) return;
const canvas = canvasRef.current;
let isDragging = false;
let lastPosX = 0;
let lastPosY = 0;
canvas.on('mouse:down', function (opt) {
const evt = opt.e;
isDragging = true;
lastPosX = evt.clientX;
lastPosY = evt.clientY;
canvas.setCursor('grab');
});
canvas.on('mouse:move', function (opt) {
if (isDragging) {
const e = opt.e;
const vpt = canvas.viewportTransform!;
vpt[4] += e.clientX - lastPosX;
vpt[5] += e.clientY - lastPosY;
canvas.requestRenderAll();
lastPosX = e.clientX;
lastPosY = e.clientY;
}
});
canvas.on('mouse:up', function () {
isDragging = false;
canvas.setCursor('default');
});
}}
className="w-full p-2 bg-yellow-500 text-black mt-2 bg-black text-white"
>
Enable Pan
</button>
</div>
<div className="benefit pb-3 border-b border-line mt-3">
<div className="heading6">Object Tools</div>
<button onClick={() => {
const obj = canvasRef.current?.getActiveObject();
if (obj) {
canvasRef.current.remove(obj);
updateLayers();
saveHistory();
}
}} className="w-full p-2 mt-2 bg-black text-white">Delete Selected</button>
<button onClick={() => canvasRef.current?.clear()} className="w-full p-2 mt-2 bg-black text-white">Clear Canvas</button>
<button onClick={() => {
const obj = canvasRef.current?.getActiveObject();
if (obj) canvasRef.current.bringForward(obj);
}} className="w-full p-2 mt-2 bg-black text-white">Bring Forward</button>
<button onClick={() => {
const obj = canvasRef.current?.getActiveObject();
if (obj) canvasRef.current.sendBackwards(obj);
}} className="w-full p-2 mt-2 bg-black text-white">Send Backward</button>
</div>
<div className="heading6 mt-4">Layers</div>
<ul className="list-disc list-inside text-sm mt-2">
{layers.map((layer, index) => (
<li key={layer.id} className="flex justify-between">
<span>{layer.name}</span>
<button
onClick={() => {
const obj = canvasRef.current?.getObjects()[layer.id];
if (obj) {
canvasRef.current?.remove(obj);
updateLayers();
saveHistory();
}
}}
className="bg-black text-white"
>
Delete
</button>
</li>
))}
</ul>
</div>
<div className="right lg:w-3/4 sm:w-[60%] sm:pl-3 w-full">
<canvas id="tshirt-canvas" className="border w-full w-100" />
</div>
</div>
</div>
</div>
);
}

View File

@ -1,26 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopFilterCanvas from '@/components/Shop/ShopFilterCanvas'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function FilterCanvas() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
const category = searchParams.get('category')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopFilterCanvas data={productData} productPerPage={12} dataType={type} />
<Footer />
</>
)
}

View File

@ -1,26 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopFilterDropdown from '@/components/Shop/ShopFilterDropdown'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function FilterDropdown() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
const category = searchParams.get('category')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopFilterDropdown data={productData} productPerPage={12} dataType={type} />
<Footer />
</>
)
}

View File

@ -1,26 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopFilterOptions from '@/components/Shop/ShopFilterOptions'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function FilterOptions() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
const category = searchParams.get('category')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopFilterOptions data={productData} productPerPage={12} />
<Footer />
</>
)
}

View File

@ -1,26 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopFilterCanvas from '@/components/Shop/ShopFilterCanvas'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function Fullwidth() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
const category = searchParams.get('category')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopFilterCanvas data={productData} productPerPage={12} dataType={type} />
<Footer />
</>
)
}

View File

@ -1,26 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopSidebarList from '@/components/Shop/ShopSidebarList'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function SidebarList() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
const category = searchParams.get('category')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<ShopSidebarList data={productData} productPerPage={4} dataType={type} />
<Footer />
</>
)
}

View File

@ -1,28 +0,0 @@
'use client'
import React, { useState } from 'react'
import { useRouter, useSearchParams } from 'next/navigation';
import TopNavOne from '@/components/Header/TopNav/TopNavOne'
import MenuOne from '@/components/Header/Menu/MenuOne'
import ShopFilterDropdown from '@/components/Shop/ShopFilterDropdown'
import productData from '@/data/Product.json'
import Footer from '@/components/Footer/Footer'
export default function FilterDropdown() {
const searchParams = useSearchParams()
const type = searchParams.get('type')
const category = searchParams.get('category')
return (
<>
<TopNavOne props="style-one bg-black" slogan="New customers save 10% with the code GET10" />
<div id="header" className='relative w-full'>
<MenuOne props="bg-transparent" />
</div>
<div className="shop-square">
<ShopFilterDropdown data={productData} productPerPage={12} dataType={type} />
</div>
<Footer />
</>
)
}

View File

@ -842,7 +842,7 @@ const MenuOne: React.FC<Props> = ({ props }) => {
</div>
</div> */}
</li>
<li className='h-full'>
{/* <li className='h-full'>
<Link
href="#!"
className={`text-button-uppercase duration-300 h-full flex items-center justify-center ${pathname.includes('/product/') ? 'active' : ''}`}
@ -1059,7 +1059,7 @@ const MenuOne: React.FC<Props> = ({ props }) => {
</div>
</div>
</div>
</li>
</li> */}
<li className='h-full relative'>
<Link href="#!" className={`text-button-uppercase duration-300 h-full flex items-center justify-center ${pathname.includes('/blog') ? 'active' : ''}`}>
Blog

View File

@ -25,12 +25,13 @@ const ModalCart = ({ serverTimeLeft }: { serverTimeLeft: CountdownTimeType }) =>
return () => clearInterval(timer);
}, []);
// Refresh cart when modal opens
useEffect(() => {
if (isModalOpen) {
refreshCart()
}
}, [isModalOpen, refreshCart])
// Cart is already in cartState.cartArray - no need to refresh
// Removing this prevents clearing the cart when modal opens
// useEffect(() => {
// if (isModalOpen) {
// refreshCart()
// }
// }, [isModalOpen, refreshCart])
const handleRemoveFromCart = async (item: any) => {
const token = localStorage.getItem("token");

View File

@ -48,18 +48,42 @@ const Product: React.FC<ProductProps> = ({ data, type, style }) => {
const handleAddToCart = async () => {
const token = localStorage.getItem("token"); // check login
// Get the product ID (handle both _id and id)
const productId = data._id || data.id;
const payload = {
productId: data._id,
quantity: data.quantityPurchase,
productId: productId,
quantity: data.quantityPurchase || 1,
color: activeColor ? activeColor : data?.variation[0]?.color,
size: activeSize ? activeSize : data?.sizes[0],
};
// Create cart item with proper structure for localStorage
const cartItem = {
...data,
id: productId, // ✅ Ensure id is set for CartContext
_id: productId, // ✅ Also set _id for consistency
quantity: payload.quantity,
selectedColor: payload.color,
selectedSize: payload.size,
quantityPurchase: payload.quantity
};
console.log("🛒 Adding to cart:", cartItem);
console.log("📦 Current cart:", cartState.cartArray);
// ✅ Always update Redux (works for both guest + logged-in users)
if (!cartState.cartArray.find((item) => item.id === data.id)) {
addToCart({ ...data, ...payload });
const existingItem = cartState.cartArray.find((item) =>
(item.id === productId || item._id === productId)
);
if (!existingItem) {
console.log(" Adding new item to cart");
addToCart(cartItem);
} else {
updateCart(data.id, data.quantityPurchase, payload.size, payload.color);
console.log("🔄 Updating existing item");
updateCart(productId, payload.quantity, payload.size, payload.color);
}
// ✅ If logged in → also sync with API
@ -81,7 +105,7 @@ const Product: React.FC<ProductProps> = ({ data, type, style }) => {
);
}
} else {
console.log("🛒 Guest mode: cart stored only in Redux");
console.log("🛒 Guest mode: cart stored in Redux + localStorage (via CartContext)");
}
openModalCart();

View File

@ -10,6 +10,7 @@ interface CartItem extends ProductType {
quantity: number
selectedSize: string
selectedColor: string
_id?: string // Support MongoDB _id format
}
interface CartState {
@ -65,18 +66,32 @@ const CART_KEY = 'guestCart' // Using same key for all users now
const saveCartToLocalStorage = (cart: CartItem[]) => {
try {
console.log("💾 saveCartToLocalStorage called with:", cart);
localStorage.setItem(CART_KEY, JSON.stringify(cart))
console.log("✅ Cart saved to localStorage successfully");
} catch (error) {
console.error('Error saving cart to localStorage:', error)
console.error('Error saving cart to localStorage:', error)
}
}
const loadCartFromLocalStorage = (): CartItem[] => {
try {
const cart = localStorage.getItem(CART_KEY)
return cart ? JSON.parse(cart) : []
const cartString = localStorage.getItem(CART_KEY)
console.log("📖 loadCartFromLocalStorage - Raw data:", cartString);
console.log("📖 loadCartFromLocalStorage - Type:", typeof cartString);
console.log("📖 loadCartFromLocalStorage - Length:", cartString?.length);
// Check if it's the string "[]" vs actual empty
if (cartString === "[]") {
console.warn("⚠️ localStorage contains empty array string '[]'");
}
const cart = cartString ? JSON.parse(cartString) : []
console.log("📖 loadCartFromLocalStorage - Parsed cart:", cart);
console.log("📖 loadCartFromLocalStorage - Parsed cart length:", cart.length);
return cart
} catch (error) {
console.error('Error loading cart from localStorage:', error)
console.error('Error loading cart from localStorage:', error)
return []
}
}
@ -93,12 +108,16 @@ const clearCartFromLocalStorage = () => {
export const CartProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [cartState, dispatch] = useReducer(cartReducer, { cartArray: [] })
const [loading, setLoading] = useState(true)
const [isInitialLoad, setIsInitialLoad] = useState(true)
const refreshCart = useCallback(async () => {
// Load from localStorage for ALL users (both logged-in and guest)
console.log("🔄 refreshCart called");
const cart = loadCartFromLocalStorage()
console.log("🔄 refreshCart - Dispatching LOAD_CART with:", cart);
dispatch({ type: 'LOAD_CART', payload: cart })
setLoading(false)
setIsInitialLoad(false) // Mark that initial load is complete
}, [])
// Load cart initially
@ -107,17 +126,41 @@ export const CartProvider: React.FC<{ children: React.ReactNode }> = ({ children
}, [refreshCart])
// Sync cart to localStorage for ALL users whenever cartState changes
// BUT skip on initial load to prevent overwriting with empty cart
useEffect(() => {
if (!loading) {
console.log("🔔 useEffect triggered - loading:", loading, "isInitialLoad:", isInitialLoad, "cartArray:", cartState.cartArray);
if (!loading && !isInitialLoad) {
console.log("💾 Calling saveCartToLocalStorage...");
saveCartToLocalStorage(cartState.cartArray)
} else {
if (loading) {
console.log("⏳ Skipping save - still loading");
}
if (isInitialLoad) {
console.log("🚀 Skipping save - initial load (preventing empty cart overwrite)");
}
}
}, [cartState.cartArray, loading])
}, [cartState.cartArray, loading, isInitialLoad])
const addToCart = (item: CartItem) => dispatch({ type: 'ADD_TO_CART', payload: item })
const removeFromCart = (itemId: string) => dispatch({ type: 'REMOVE_FROM_CART', payload: itemId })
const updateCart = (itemId: string, quantity: number, selectedSize: string, selectedColor: string) =>
const addToCart = (item: CartItem) => {
console.log(" addToCart:", item);
dispatch({ type: 'ADD_TO_CART', payload: item })
}
const removeFromCart = (itemId: string) => {
console.log(" removeFromCart:", itemId);
dispatch({ type: 'REMOVE_FROM_CART', payload: itemId })
}
const updateCart = (itemId: string, quantity: number, selectedSize: string, selectedColor: string) => {
console.log("🔄 updateCart:", { itemId, quantity, selectedSize, selectedColor });
dispatch({ type: 'UPDATE_CART', payload: { itemId, quantity, selectedSize, selectedColor } })
const loadCart = (items: CartItem[]) => dispatch({ type: 'LOAD_CART', payload: items })
}
const loadCart = (items: CartItem[]) => {
console.log("📥 loadCart:", items);
dispatch({ type: 'LOAD_CART', payload: items })
}
const clearCart = () => {
console.log("🗑️ Clearing cart - Redux store and localStorage");
dispatch({ type: 'CLEAR_CART' })