diff --git a/src/App.tsx b/src/App.tsx index 00a63d3..52c147e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,10 +8,8 @@ import GoogleAuthSuccess from './components/authentication/GoogleAuthSucces'; import { ToastContainer } from 'react-toastify'; import Searchpage from './containers/searchResults/SearchPage'; import { useDispatch } from 'react-redux'; -import { ProductResponse, Product } from './types/Types'; -import { useEffect, useRef } from 'react'; -import { useGetProductsQuery } from './services/productApi'; -import { setError, setIsLoading, setProductFetched, setProductsDataList } from './redux/slices/productsSlice'; +import { useEffect } from 'react'; +import { productsApi } from './services/productApi'; import Checkout from './components/checkout/Checkout'; import RestrictedRoute from './components/dashboard/RestrictedRoute'; import AdminPage from './pages/admin'; @@ -37,38 +35,20 @@ import { cartApi } from './services/cartApi'; import VerifyOTPPage from './pages/VerifyOTPPage'; import PaymentSuccessCard from './components/checkout/PaymentSuccessCard'; import PaymentPage from './pages/PaymentPage'; +import { useSelector } from 'react-redux'; +import { RootState } from './redux/store'; import BuyerRestrictedRoutes from './containers/buyer/BuyerRestrictedRoutes'; const App = () => { - const { data, error, isLoading } = useGetProductsQuery(); + const isAuthenticated = useSelector((state: RootState) => state.user.token); const dispatch = useDispatch(); - const firstRender = useRef(true); - - const productsData: ProductResponse = data as unknown as ProductResponse; - useEffect(() => { - const fetchProducts = async () => { - if (firstRender.current) { - firstRender.current = false; - return; - } - if (error) { - dispatch(setError(error)); - dispatch(setIsLoading(false)); - dispatch(setProductFetched(false)); - return; - } - if (!isLoading && productsData) { - const productsList = productsData.data as unknown as Product[]; - dispatch(setProductsDataList([...productsList])); - dispatch(setIsLoading(false)); - dispatch(setProductFetched(true)); - } - }; - fetchProducts(); - - dispatch(cartApi.endpoints.getCarts.initiate()); - }, [productsData, isLoading, dispatch]); + dispatch(productsApi.endpoints.getProducts.initiate()) + if (isAuthenticated) { + dispatch(cartApi.endpoints.getCarts.initiate()); + console.log("Cart") + } + }, [dispatch]); const router = createBrowserRouter([ { diff --git a/src/components/Products/ProductCard.tsx b/src/components/Products/ProductCard.tsx index a077f8b..5090f55 100644 --- a/src/components/Products/ProductCard.tsx +++ b/src/components/Products/ProductCard.tsx @@ -1,46 +1,122 @@ -import { FaHeart } from 'react-icons/fa6'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../redux/store'; +import { useAddProductToCartMutation, useDeleteCartMutation } from '../../services/cartApi'; +import { useAddProductToWishlistMutation, useRemoveWishListMutation } from '../../services/wishlistApi'; import { Product } from '../../types/Types'; -import { TiShoppingCart } from 'react-icons/ti'; import StarRating from '../common/Ratings'; +import { IoIosHeart, IoIosHeartEmpty } from 'react-icons/io'; +import { MdOutlineAddShoppingCart } from 'react-icons/md'; import { useNavigate } from 'react-router-dom'; interface ProductCardProps { product: Product; + cartAdded?: boolean, + wishList?: boolean, + wishListId?: string | string } -const ProductCard: React.FC = ({ product }) => { +const ProductCard: React.FC = ({ product, cartAdded, wishList, wishListId }) => { const imageUrl = product?.images?.[0] ?? ''; const price = product?.sizes?.[0]?.price ?? ''; - const navigate = useNavigate(); + const productId = product?.id + const sizeId = product?.sizes[0].id + const [addProductToCart] = useAddProductToCartMutation() + const [deleteCart] = useDeleteCartMutation() + const [addProductToWishlist] = useAddProductToWishlistMutation() + const [removeWishList] = useRemoveWishListMutation() + const isAuthenticated = useSelector((state: RootState) => state.user.token) ? true : false; + const navigate = useNavigate() + // AddCart + const onAddProductToCart = async (e: React.MouseEvent) => { + e.stopPropagation() + if (isAuthenticated) { + await addProductToCart({ productId, sizeId }) + } else { + navigate("/login") + } + } + // RemoveCart + const onRemoveProductToCart = async (e: React.MouseEvent) => { + e.stopPropagation() + if (isAuthenticated && sizeId) { + await deleteCart({ productId, sizeId }) + } else { + navigate("/login") + } + } + // AddWishList + const onAddWishList = async (e: React.MouseEvent) => { + e.stopPropagation() + if (isAuthenticated && sizeId) { + await addProductToWishlist(sizeId) + } else { + navigate("/login") + } + } + // onRemoveWishList + const onRemoveWishList = async (e: React.MouseEvent) => { + e.stopPropagation() + if (isAuthenticated && wishListId) { + await removeWishList(wishListId) + } else { + navigate("/login") + } + } return (
{ - navigate(`/products/${product.id}`); - window.scrollTo({ top: 0, behavior: 'smooth' }); + window.open(`/products/${product.id}`, '_blank'); + // window.scrollTo({ top: 0, behavior: 'smooth' }); }} - className='product-card bg-whiteColor min-w-44 md:min-w-56 lg:min-w-56 - shadow-lg rounded-lg p-2 m-4 md:p-4 md:m-4 transition-transform hover:scale-105 cursor-pointer' + className='bg-whiteColor flex-col relative w-[49%] sm:w-52 md:w-[32.5%] lg:w-[19%] 2xl:w-[253px] mb-2 drop-shadow-md rounded-lg cursor-pointer' > -
- {product.name} +
+ {product.name} +
+ {cartAdded + ? ( + + ) + : ( + + )} + {wishList + ? ( + + ) + : ( + + )} + +
-
+
-
-

{product.name}

-

${price}

-
-

{product.manufacturer}

-
-
-
-
- - -
-
+
+

{product.name.toLowerCase()}

+
+

+ $ + {price.toFixed(2)} +

diff --git a/src/components/authentication/GoogleAuthSucces.tsx b/src/components/authentication/GoogleAuthSucces.tsx index 88f146f..2a86f3c 100644 --- a/src/components/authentication/GoogleAuthSucces.tsx +++ b/src/components/authentication/GoogleAuthSucces.tsx @@ -1,7 +1,9 @@ import { useEffect } from 'react'; import { useNavigate, useLocation, useParams } from 'react-router-dom'; import { useDispatch } from 'react-redux'; -import { setToken } from '../../redux/slices/userSlice'; +import { setToken, setUser } from '../../redux/slices/userSlice'; +import { CustomJwtPayload } from '../../types/Types'; +import { jwtDecode } from 'jwt-decode'; const GoogleAuthSuccess = () => { const navigate = useNavigate(); @@ -12,6 +14,8 @@ const GoogleAuthSuccess = () => { useEffect(() => { if (token) { dispatch(setToken(token)); + const decodedToken = jwtDecode(token); + dispatch(setUser(decodedToken.id)); navigate('/'); } }, [location, dispatch, navigate]); @@ -19,4 +23,4 @@ const GoogleAuthSuccess = () => { return
Loading...
; }; -export default GoogleAuthSuccess; +export default GoogleAuthSuccess; \ No newline at end of file diff --git a/src/components/cart/Cart.tsx b/src/components/cart/Cart.tsx index 73134e9..6cfd144 100644 --- a/src/components/cart/Cart.tsx +++ b/src/components/cart/Cart.tsx @@ -1,11 +1,14 @@ import React, { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; // Assuming you're using Redux for state management import Navbar from '../navbar/Navbar'; import CartItem from './CartItem'; import { Link } from 'react-router-dom'; +import { RootState } from '../../redux/store'; // Replace with your actual root state type import { ICartProduct, ICartsHookResponse, ICartsResponse } from '../../utils/schemas'; import { useGetCartsQuery } from '../../services/cartApi'; const Cart: React.FC = () => { + // const isAuthenticated = useSelector((state: RootState) => state.user.token ? true : false); const { data: carts, isLoading, @@ -51,7 +54,7 @@ const Cart: React.FC = () => { } else if (isError) { content =
{error?.toString()}
; } - + return (
@@ -81,4 +84,5 @@ const Cart: React.FC = () => {
); }; + export default Cart; diff --git a/src/components/common/Ratings.tsx b/src/components/common/Ratings.tsx index 90e3c1d..867c029 100644 --- a/src/components/common/Ratings.tsx +++ b/src/components/common/Ratings.tsx @@ -30,8 +30,10 @@ const StarRating: React.FC = ({ reviews }) => { } return stars; }; - - return
{renderStars()}
; + return
+ {renderStars()} + ({reviews.length > 0 ? averageRating : '0'}) +
; }; export default StarRating; diff --git a/src/components/navbar/Navbar.tsx b/src/components/navbar/Navbar.tsx index 3636db0..3f735a9 100644 --- a/src/components/navbar/Navbar.tsx +++ b/src/components/navbar/Navbar.tsx @@ -78,7 +78,7 @@ const Navbar: React.FC = () => { isLoading: isFetchingUser, isSuccess: isUserFetched, data: userData, - } = useGetUserByIdQuery(userId); + } = useGetUserByIdQuery(userId, { skip: !userId }); const handleNavigate = () => { const token = localStorage.getItem('token') || null; if (!token) { diff --git a/src/components/navbar/notifications/Notifications.tsx b/src/components/navbar/notifications/Notifications.tsx index 422f17e..6f14a09 100644 --- a/src/components/navbar/notifications/Notifications.tsx +++ b/src/components/navbar/notifications/Notifications.tsx @@ -21,7 +21,7 @@ export const Notifications = () => { const [notifications, setNotifications] = useState([]); const [unRead, setUnRead] = useState(0); - const { data: fetchedNotifications, isLoading, isError } = useGetNotificationsQuery(userId); + const { data: fetchedNotifications, isLoading, isError } = useGetNotificationsQuery(userId, { skip: !userId }); const [deleteAll, { isLoading: areAllDeleting, isSuccess: areDeleted }] = useDeleteAllNotificationsMutation(); const [markAll, { isLoading: areAllUpdating, isSuccess: areAllUpdated }] = useMarkAllNotificationsAsReadMutation(); diff --git a/src/containers/Arrivals/NewArrivals.tsx b/src/containers/Arrivals/NewArrivals.tsx index 3ce3134..b7feefb 100644 --- a/src/containers/Arrivals/NewArrivals.tsx +++ b/src/containers/Arrivals/NewArrivals.tsx @@ -1,50 +1,49 @@ -import ProductCard from '../../components/Products/ProductCard'; -import ProductCardSkeleton from '../../components/Products/ProductCardSkeleton'; -import { useState } from 'react'; -import { Product } from '../../types/Types'; -import { BiSolidCircle } from 'react-icons/bi'; -import { useSelector } from 'react-redux'; +// import ProductCard from '../../components/Products/ProductCard'; +// import ProductCardSkeleton from '../../components/Products/ProductCardSkeleton'; +// import { Product } from '../../types/Types'; +// import { BiSolidCircle } from 'react-icons/bi'; +// import { useGetProductsQuery } from '../../services/productApi'; -const perPage = 8; +// const perPage = 10; -export default function NewArrivals() { - const [currentPage, setCurrentPage] = useState(0); - const { isLoading, productsDataList: productsList } = useSelector((state: any) => state.products); - const next = () => { - setCurrentPage(prevPage => Math.min(prevPage + 1, Math.floor(productsList.length / perPage))); - }; +// export default function NewArrivals() { +// const { data: productsList, isLoading, isSuccess } = useGetProductsQuery() +// // const [currentPage, setCurrentPage] = useState(0); +// // const { isLoading, productsDataList: productsList } = useSelector((state: any) => state.products); +// const next = () => { +// setCurrentPage(prevPage => Math.min(prevPage + 1, Math.floor(productsList.data.length / perPage))); +// }; - const prev = () => { - setCurrentPage(prevPage => Math.max(prevPage - 1, 0)); - }; +// const prev = () => { +// setCurrentPage(prevPage => Math.max(prevPage - 1, 0)); +// }; - const startIndex = currentPage * perPage; - const endIndex = startIndex + perPage; - const sortedProducts = [...productsList].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, 13); - const allProductsOnPage = sortedProducts.slice(startIndex, endIndex); +// const startIndex = currentPage * perPage; +// const endIndex = startIndex + perPage; +// const sortedProducts = [...productsList.data].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, 13); +// const allProductsOnPage = sortedProducts.slice(startIndex, endIndex); - return ( -
-
-

New Arrivals

-
-
- {isLoading - ? Array.from({ length: perPage }).map((_, index) => ) - : allProductsOnPage.map((product: Product) => )} -
-
- - = Math.floor(productsList.length / perPage) ? 'text-grayColor' : '' - }`} - /> -
-
- ); -} +// return ( +//
+//
+//

New Arrivals

+//
+//
+// {isLoading +// ? Array.from({ length: perPage }).map((_, index) => ) +// : allProductsOnPage.map((product: Product) => )} +//
+//
+// +// = Math.floor(productsList.data.length / perPage) ? 'text-grayColor' : '' +// }`} +// /> +//
+//
+// ); +// } diff --git a/src/containers/FeaturedProducts/FeaturedProducts.tsx b/src/containers/FeaturedProducts/FeaturedProducts.tsx index 2b2f14f..b06f47a 100644 --- a/src/containers/FeaturedProducts/FeaturedProducts.tsx +++ b/src/containers/FeaturedProducts/FeaturedProducts.tsx @@ -1,11 +1,22 @@ import ProductCard from '../../components/Products/ProductCard'; import ProductCardSkeleton from '../../components/Products/ProductCardSkeleton'; -import { useSelector } from 'react-redux'; import { Product } from '../../types/Types'; +import { useGetProductsQuery } from '../../services/productApi'; +import { useGetCartsQuery } from '../../services/cartApi'; +import { useGetUserWishlistQuery } from '../../services/wishlistApi'; +// import { useSelector } from 'react-redux'; +// import { RootState } from '../../redux/store'; + export default function FeaturedProduct() { - const { isLoading, productsDataList: productsList } = useSelector((state: any) => state.products); if (isLoading) { - return ( + // const isAuthenticated = useSelector((state: RootState) => state.user.token) ? true : false; + const { data: productsList, isLoading, isSuccess } = useGetProductsQuery() + const { data: cartList, isLoading: cartListLoading } = useGetCartsQuery() + const { data: wishList, isLoading: wishLoading } = useGetUserWishlistQuery() + const product = productsList?.data + let content; + if (isLoading && cartListLoading && wishLoading) { + content = (

Featured Products

@@ -17,18 +28,32 @@ export default function FeaturedProduct() {
); - } + } else if (isSuccess) { + content = ( +
+
+

Featured Products

+
+
+ { + product.map((product: Product) => { + const wishListed = wishList?.data.find(item => item.productId === product.sizes[0].id) + const wishListId = wishListed?.id + return ( + cartProduct.id === product.id)} + wishList={wishList?.data.some((wishList) => wishList.productId === product.sizes[0].id)} + wishListId={wishListId} + /> + ) + })} +
- return ( -
-
-

Featured Products

-
-
- {productsList.map((product: Product) => ( - - ))}
-
- ); + ) + } + + return content; } diff --git a/src/containers/herosection/HeroPage.tsx b/src/containers/herosection/HeroPage.tsx index 54d8d5d..4a04e36 100644 --- a/src/containers/herosection/HeroPage.tsx +++ b/src/containers/herosection/HeroPage.tsx @@ -5,7 +5,7 @@ import { TbTruck } from "react-icons/tb"; import { MdOutlinePayment } from "react-icons/md"; import { IoShieldCheckmarkOutline } from "react-icons/io5"; import Category from '../categories/Category'; -import NewArrivals from '../Arrivals/NewArrivals'; +// import NewArrivals from '../Arrivals/NewArrivals'; import FeaturedProduct from '../FeaturedProducts/FeaturedProducts'; import { useGetAllCategoriesQuery } from '../../services/productApi'; import { useDispatch, useSelector } from 'react-redux'; @@ -16,7 +16,7 @@ import CategorySkeleton from '../../components/categories/CategoriesSkeleton'; function HeroPage() { const dispatch = useDispatch(); - const { allCategories, isCategoriesFetched } :any = useSelector((state: RootState) => state.category); + const { allCategories, isCategoriesFetched }: any = useSelector((state: RootState) => state.category); const { data: categoriess } = useGetAllCategoriesQuery(); useEffect(() => { @@ -45,35 +45,35 @@ function HeroPage() { }, []); return ( -
+
Hot Phones + src="https://image.gazetevatan.com/i/gazetevatan/75/1200x675/65004ecee14f2adf329c06c9.jpg" + alt="Hot Phones" + className="w-full h-[150px] md:h-[250px] object-cover rounded-sm" + />

Hot Sells from new Arrivals

Best Shoes in Town + src="https://images.unsplash.com/photo-1606107557195-0e29a4b5b4aa?q=80&w=1664&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" + alt="Best Shoes in Town" + className="w-full h-[150px] md:h-[250px] object-cover rounded-sm" + />

Enjoy Black Friday Discount

- {images.map((src, index) => ( - {`Sliding - ))} + {images.map((src, index) => ( + {`Sliding + ))}

@@ -81,19 +81,19 @@ function HeroPage() {

Shop By Category Here:

- {list && list.length ? ( -
- {list.map((cat:{image:string, name: string}, index:number) => ( -
- -
- ))} -
- ) : ( -
- -
- )} + {list && list.length ? ( +
+ {list.map((cat: { image: string, name: string }, index: number) => ( +
+ +
+ ))} +
+ ) : ( +
+ +
+ )}
SMARTER VALUES, GREAT DEALS
@@ -105,28 +105,28 @@ function HeroPage() {
- +

Shoppers worldwide

More than 300 millions shoppers form 200+ countries & region

- +

Fast delivery

Faster delivery on selected items. Thanks to our improved logistics

- +

Safe payments

Safe payment methods preferred by international shoppers

- +

Buyer protection

Get refund if item arrived late or not as described

@@ -134,9 +134,9 @@ function HeroPage() {
{/* New Arrivals */} -
+ {/*
-
+
*/} {/* Featured Products */}
diff --git a/src/pages/CategoriesPage.tsx b/src/pages/CategoriesPage.tsx index 38b6f84..69b08ed 100644 --- a/src/pages/CategoriesPage.tsx +++ b/src/pages/CategoriesPage.tsx @@ -2,16 +2,14 @@ import React, { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import Footer from '../components/footer/Footer'; import Navbar from '../components/navbar/Navbar'; -import { TiShoppingCart } from "react-icons/ti"; -import { FaHeart } from "react-icons/fa"; import Hero from '../assets/Hero.png' -import StarRating from '../components/common/Ratings'; import { useSelector } from 'react-redux'; import { Category } from '../types/Types'; import { useDispatch } from 'react-redux'; import { useGetAllCategoriesQuery } from '../services/productApi'; import { setIsCategoriesFetched, setallCategories } from '../redux/slices/categorySlice'; -import { FaLongArrowAltRight, FaLongArrowAltLeft } from "react-icons/fa"; +import { FaLongArrowAltRight, FaLongArrowAltLeft } from "react-icons/fa"; +import ProductCard from '../components/Products/ProductCard'; @@ -22,9 +20,9 @@ const CategoriesPage: React.FC = () => { // categories const dispatch = useDispatch(); - const { allCategories, isCategoriesFetched } :any = useSelector((state: any) => state.category); + const { allCategories, isCategoriesFetched }: any = useSelector((state: any) => state.category); const { data: categoriess, isLoading } = useGetAllCategoriesQuery(); - + // categories on page load useEffect(() => { if (!isCategoriesFetched && categoriess) { @@ -39,7 +37,7 @@ const CategoriesPage: React.FC = () => { if (category) { setCategoryId(category.id); } else { - setCategoryId(""); + setCategoryId(""); } } }, [categoryId, allCategories, isLoading]); @@ -73,26 +71,26 @@ const CategoriesPage: React.FC = () => { }; const filteredProducts = [...productsList] - .filter(product => { - const matchesName = product.name.toLowerCase().includes(nameFilter.toLowerCase()); - const matchesMinPrice = minPrice === '' || product?.sizes?.[0]?.price >= parseFloat(minPrice); - const matchesMaxPrice = maxPrice === '' || product?.sizes?.[0]?.price <= parseFloat(maxPrice); - const matchesCategory = selectedCategory === '' || product.categoryName === selectedCategory; - const matchesCategoryId = cateId === '' || product.categoryName === cateId; - return matchesName && matchesMinPrice && matchesMaxPrice && matchesCategory && matchesCategoryId; - }) - .sort((a, b) => { - if (sortOption === 'name') { - return a.name.localeCompare(b.name); - } else if (sortOption === 'date') { - return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); - } - else if (sortOption === 'rating') { - return b.ratings - a.ratings; - } - return 0; - }); - + .filter(product => { + const matchesName = product.name.toLowerCase().includes(nameFilter.toLowerCase()); + const matchesMinPrice = minPrice === '' || product?.sizes?.[0]?.price >= parseFloat(minPrice); + const matchesMaxPrice = maxPrice === '' || product?.sizes?.[0]?.price <= parseFloat(maxPrice); + const matchesCategory = selectedCategory === '' || product.categoryName === selectedCategory; + const matchesCategoryId = cateId === '' || product.categoryName === cateId; + return matchesName && matchesMinPrice && matchesMaxPrice && matchesCategory && matchesCategoryId; + }) + .sort((a, b) => { + if (sortOption === 'name') { + return a.name.localeCompare(b.name); + } else if (sortOption === 'date') { + return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); + } + else if (sortOption === 'rating') { + return b.ratings - a.ratings; + } + return 0; + }); + const startIndex = currentPage * productsPerPage; const endIndex = startIndex + productsPerPage; const currentProducts = filteredProducts.slice(startIndex, endIndex); @@ -170,41 +168,13 @@ const CategoriesPage: React.FC = () => { {/* Products */}
-
- {currentProducts.length ===0 ? ( +
+ {currentProducts.length === 0 ? (

No products found.

- ):(currentProducts.map((product, index) => ( -
-
- {product.name} -
-
-
-
-

{product.name}

-

${product?.sizes?.[0]?.price}

-
-

{product.manufacturer}

-
-
-
-
- - -
-
- -
-
-
-
-
- )) - )} + ) : (currentProducts.map((product, index) => ( + + )) + )}