Skip to content

Commit

Permalink
Manage cart product
Browse files Browse the repository at this point in the history
  • Loading branch information
hozayves committed Jul 18, 2024
1 parent f38f190 commit b40f570
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 92 deletions.
8 changes: 8 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import CategoriesPage from './pages/CategoriesPage';
import ResetPassword from './pages/ResetPassword';
import NewPassword from './pages/NewPassword';
import { ProductDetail } from './pages/product/ProductDetail';
import Cart from './components/cart/Cart';
import { cartApi } from './services/cartApi';

const App = () => {
const { data, error, isLoading } = useGetProductsQuery();
Expand Down Expand Up @@ -52,6 +54,8 @@ const App = () => {
}
};
fetchProducts();

dispatch<any>(cartApi.endpoints.getCarts.initiate())
}, [productsData, isLoading, dispatch]);

const router = createBrowserRouter([
Expand All @@ -70,6 +74,10 @@ const App = () => {
path: 'auth/success/:token',
element: <GoogleAuthSuccess />,
},
{
path: 'shoppingcart',
element: <Cart />
},
{
path: 'categories/:categoryId',
element: <CategoriesPage />,
Expand Down
67 changes: 67 additions & 0 deletions src/components/cart/Cart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, { useEffect, useState } from "react"
import Navbar from "../navbar/Navbar"
import CartItem from "./CartItem"
import { Link } from "react-router-dom"
import { ICartProduct, ICartsHookResponse, ICartsResponse } from "../../utils/schemas"
import { useGetCartsQuery } from "../../services/cartApi"

const Cart: React.FC = () => {
const { data: carts, isLoading, isSuccess, isError, error } = useGetCartsQuery<ICartsResponse>() as ICartsHookResponse;
const [totalPrice, setTotalPrice] = useState(0)
const [items, setItems] = useState(0)

useEffect(() => {
if (isSuccess && carts) {
// Calculate total price
const calculatedTotalPrice = carts.cartProducts.reduce((total: number, cart: ICartProduct) => {
const pricePerItem = cart.sizes[0]?.price || 0;
const itemTotalPrice = pricePerItem * cart.quantity;
return total + itemTotalPrice;
}, 0);

// Update total price state
setTotalPrice(calculatedTotalPrice);

// Optionally calculate the number of items if needed
const totalItems = carts.cartProducts.length;
setItems(totalItems);
}
}, [isSuccess, carts]);

let content;
if (isLoading) {
content = <div>Loading</div>
} else if (isSuccess) {
const renderedCart = carts?.cartProducts.map((cart, index) => {
const price = cart.sizes[0]?.price
return <CartItem key={index} {...cart} price={price} />
})
content = renderedCart
} else if (isError) {
content = <div>{error?.toString()}</div>
}
return (
<div className="w-full min-h-screen overflow-x-hidden font-roboto flex flex-col 2xl:items-center">
<Navbar />
<div className="w-full px-3 pt-5 flex flex-col gap-2 md:p-4 xl:px-10 2xl:w-[1440px]">
<h1 className="leading-none font-semibold text-lg subpixel-antialiased tracking-wide">Shopping Cart ({items})</h1>
<p className="leading-none subpixel-antialiased text-xs tracking-wide font-light hidden md:block">Your cart contains {items} items and comes to a total of ${totalPrice}</p>
<div className="flex flex-col md:flex-row-reverse lg:gap-10 xl:gap-12">
<div className="flex flex-col gap-4 px-3 py-2 pt-4 md:flex-grow lg:w-72 lg:flex-grow-0 xl:flex-grow-0 xl:w-80">
<div className="flex justify-between text-sm font-medium">
<span className="md:font-semibold md:text-lg">Total:</span>
<span className="md:font-semibold md:text-lg">${totalPrice}</span>
</div>
<Link to="/checkoutbag"
className="leading-none bg-greenColor hover:bg-[#1a6461] rounded-full px-5 py-3 text-whiteColor font-medium text-xs transition-all delay-75 ease-in cursor-pointer text-center"
>Checkout</Link>
</div>
<div className="bg-[#f7f7f7] flex flex-col py-3 mt-2 md:flex-grow">
{content}
</div>
</div>
</div>
</div>
)
}
export default Cart
81 changes: 81 additions & 0 deletions src/components/cart/CartItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { useEffect, useState } from 'react'
import { useDeleteCartMutation, useUpdateCartMutation } from '../../services/cartApi'
import { ICartProduct } from '../../utils/schemas'
import { FaMinus, FaPlus } from 'react-icons/fa6'

interface CartItemsProps extends ICartProduct {
price: number
}

const CartItem: React.FC<CartItemsProps> = ({ id, name, image, sizes, quantity, price }) => {
const [itemTotalPrice, setItemTotalPrice] = useState(price * quantity)
const [updateCart] = useUpdateCartMutation()
const [deleteCart, { isLoading }] = useDeleteCartMutation()
const currentPrice = price;

useEffect(() => {
setItemTotalPrice(currentPrice * quantity);
}, [quantity, currentPrice]);

const addQty = (quantity: number, productId: string, sizeId: string) => {
const newQuantity = quantity + 1;
updateCart({ id, updatedCart: { productId, quantity: newQuantity, sizeId } })
}
const removeQty = (quantity: number, productId: string, sizeId: string) => {
const newQuantity = quantity - 1;
if (newQuantity > 0) {
updateCart({ id, updatedCart: { productId, quantity: newQuantity, sizeId } })
}
}
return (
<>
<div className="flex gap-1 border-b border-b-grayColor py-2 px-3">
<div className="h-36 max-w-[98px] min-w-[144px]">
<img src={image} alt="" className="w-36 h-36 object-cover" />
</div>
<div className="flex flex-col gap-2 ml-3 w-full">
<div className="flex justify-between font-semibold">
<h3 className="leading-5 w-2/3 text-wrap capitalize text-sm break-words font-medium tracking-normal outline-none
md:text-lg">{name}</h3>
<span className="text-xs md:text-base">${itemTotalPrice}</span>
</div>
<div className="">
<div className="flex flex-col gap-2 md:gap-1">
<div className="flex flex-col gap-1 font-light mt-1 md:gap-1">
<label htmlFor="size" className="leading-none text-xs opacity-70">Size</label>
<div id="size" className="border border-greenColor rounded-full h-9 p-[6px] text-sm bg-whiteColor w-4/5 font-medium outline-none md:w-1/2 justify-start flex items-center leading-none px-3">
{sizes[0].size !== null ? sizes[0].size : 'One'}
</div>
</div>
<div className="flex flex-col font-light gap-1 mt-2">
<label htmlFor="quantity" className="leading-none text-xs opacity-70">Quantity</label>
<div className='flex'>
<button
onClick={() => removeQty(quantity, id, sizes[0].id)}
className={`border w-10 h-9 text-greenColor rounded-l-full cursor-pointer text-center flex justify-center items-center ${quantity <= 1 ? 'cursor-not-allowed bg-grayColor' : 'bg-whiteColor'}`}
><FaMinus /></button>
<input type="text" value={quantity} className="h-9 w-9 border-t border-t-greenColor border-b text-center outline-none" />
<button
onClick={() => addQty(quantity, id, sizes[0].id)}
className="border w-10 h-9 text-greenColor rounded-r-full bg-whiteColor cursor-pointer text-center flex justify-center items-center"
><FaPlus /></button>
</div>
</div>
<div className="flex justify-end gap-2 mt-2 flex-col sm:flex-row">
{/* <button
onClick={() => addProductToWishlist(sizes[0].id)}
className="border border-greenColor rounded-full px-3 py-1 text-xs font-medium cursor-pointer"
>Save For Later</button> */}
<button
onClick={() => deleteCart({ productId: id, sizeId: sizes[0].id })}
className="border border-greenColor rounded-full px-3 py-1 text-xs font-medium cursor-pointer"
>{isLoading ? 'Loading' : "Remove"}</button>
</div>
</div>
</div>
</div>
</div>
</>
)
}
export default CartItem
15 changes: 8 additions & 7 deletions src/components/navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import { cn } from '../../utils';
import { useGetUserByIdQuery } from '../../services/userApi';
import { useSelector } from 'react-redux';
import { RootState } from '../../redux/store';
import { selectAllCarts } from '../../services/cartApi';

const Navbar: React.FC = () => {
const cartsCount = useSelector(selectAllCarts).length
const [notificationOpen, setNotificationOpen] = useState<boolean>(false);
const navbarRef = useRef<HTMLDivElement>(null);
const [navbarHeight, setNavbarHeight] = useState<number>(20);
Expand Down Expand Up @@ -111,9 +113,8 @@ const Navbar: React.FC = () => {
></div>
)}
<div
className={`flex flex-col bg-blackColor md:bg-whiteColor md:text-blackColor text-whiteColor font-roboto w-full 2xl:items-center top-0 ${
wish || cartOpen ? 'sticky' : ''
} z-10`}
className={`flex flex-col bg-blackColor md:bg-whiteColor md:text-blackColor text-whiteColor font-roboto w-full 2xl:items-center top-0 ${wish || cartOpen ? 'sticky' : ''
} z-10`}
>
<div
className='flex justify-between gap-2 flex-wrap p-3 md:p-4 xl:px-10 2xl:w-[1440px] relative'
Expand Down Expand Up @@ -152,7 +153,7 @@ const Navbar: React.FC = () => {
onClick={handleNavigate}
className='rounded-full transition-all ease-in-out delay-100 hover:bg-grayColor active:bg-greenColor p-1 active:text-blackColor hover:text-blackColor'
>

<LuUser stroke='currentColor' className='size-6 md:size-8' strokeWidth={1} />
{wish && (
<div
Expand All @@ -168,12 +169,12 @@ const Navbar: React.FC = () => {
</div>
)}
</div>

<a
className='rounded-full transition-all ease-in-out delay-100 hover:bg-grayColor active:bg-greenColor p-1 active:text-blackColor hover:text-blackColor relative'
onClick={() => setNotificationOpen(!notificationOpen)}
>

<LuBell strokeWidth={1} stroke='currentColor' className='size-6 md:size-8' />
{/* <span className='absolute top-1 right-1 w-2 h-2 rounded-full bg-redColor'></span> */}
</a>
Expand All @@ -197,7 +198,7 @@ const Navbar: React.FC = () => {
/>
</svg>
<span className='p-1 leading-none text-[9px] bg-redColor text-whiteColor rounded-full flex justify-center items-center w-5 h-5 md:w-6 md:h-6 md:text-xs absolute -top-1 -right-1 md:-right-2 md:-top-2'>
3
{cartsCount}
</span>
</div>
{cartOpen && (
Expand Down
Loading

0 comments on commit b40f570

Please sign in to comment.