diff --git a/client/package-lock.json b/client/package-lock.json index 0ebc8d8..258a210 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -16,6 +16,7 @@ "lodash.debounce": "^4.0.8", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-image-gallery": "^1.3.0", "react-redux": "^9.0.4", "react-router-dom": "^6.21.0" }, @@ -5150,6 +5151,14 @@ "react": "^18.2.0" } }, + "node_modules/react-image-gallery": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-image-gallery/-/react-image-gallery-1.3.0.tgz", + "integrity": "sha512-lKnPaOzxqSdujPFyl+CkVw0j1aYoNCHk61cvr1h7aahf5aWqmPcR9YhUB4cYrt5Tn5KHDaPUzYm5/+cX9WxzaA==", + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/client/package.json b/client/package.json index 9971061..d07f1dd 100644 --- a/client/package.json +++ b/client/package.json @@ -23,6 +23,7 @@ "lodash.debounce": "^4.0.8", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-image-gallery": "^1.3.0", "react-redux": "^9.0.4", "react-router-dom": "^6.21.0" }, diff --git a/client/src/components/form/Form.module.css b/client/src/components/form/Form.module.css index 07a3ef7..d1de9b4 100644 --- a/client/src/components/form/Form.module.css +++ b/client/src/components/form/Form.module.css @@ -1,4 +1,4 @@ -.login-container { +.form-container { margin: 1.4rem auto; width: 50rem; height: 31rem; @@ -15,13 +15,13 @@ height: 100%; } -.login-title { +.form-title { font-size: 1.7rem; font-weight: 500; margin: 0.5rem 0; } -.login-desc { +.form-desc { color: #dbdbdb; font-size: 1.1rem; } @@ -32,7 +32,7 @@ height: 100%; } -.right .login-form { +.right .form { margin-top: 2rem; display: flex; align-items: center; @@ -40,7 +40,7 @@ gap: 0.8rem; } -.login-form input { +.form input { width: 100%; padding: 0.4rem 1rem; margin: 0.6rem 0; @@ -49,11 +49,11 @@ border: 0; border-bottom: 1px solid grey; } -.login-form input::placeholder { +.form input::placeholder { opacity: 0.7; } -.login-form button { +.form button { border: 0; background-color: #fb641b; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2); @@ -64,7 +64,7 @@ color: white; } -small { +.form-small { text-align: left; font-size: 0.7rem; color: #878787; diff --git a/client/src/components/form/Form.tsx b/client/src/components/form/Form.tsx index bf49114..5f273ee 100644 --- a/client/src/components/form/Form.tsx +++ b/client/src/components/form/Form.tsx @@ -15,21 +15,21 @@ export default function From({ promptLinkText, }: FormProps) { return ( -
+
-
+
{mode}
-
+

Get access to your Orders,

Wishlist and Recommendations

-
+ - + By continuing, you agree to Flipkart's Terms of Use and diff --git a/client/src/components/notification/Notification.tsx b/client/src/components/notification/Notification.tsx new file mode 100644 index 0000000..4b1d00c --- /dev/null +++ b/client/src/components/notification/Notification.tsx @@ -0,0 +1,18 @@ +import { Alert } from '@mui/material' + +type Type = 'success' | 'info' | 'warning' | 'error' + +type Props = { + type: Type + message: string +} + +export default function Notification({ type = 'info', message }: Props) { + return ( + <> + + {message} + + + ) +} diff --git a/client/src/constants/filterConstants.ts b/client/src/constants/filterConstants.ts index 3416ed3..ef903fa 100644 --- a/client/src/constants/filterConstants.ts +++ b/client/src/constants/filterConstants.ts @@ -10,3 +10,122 @@ export const RATING_TYPE = { TWO_AND_UP: '2', ONE_AND_UP: '1', } + +export const RATING_ITEMS = [ + { value: RATING_TYPE.FOUR_AND_UP, label: '4★ & above' }, + { value: RATING_TYPE.THREE_AND_UP, label: '3★ & above' }, + { value: RATING_TYPE.TWO_AND_UP, label: '2★ & above' }, + { value: RATING_TYPE.ONE_AND_UP, label: '1★ & above' }, +] + +export const SORT_ITEMS = [ + { value: SORT_TYPE.PRICE_HIGH_TO_LOW, label: 'Price high to low' }, + { value: SORT_TYPE.PRICE_LOW_TO_HIGH, label: 'Price low to high' }, + { value: SORT_TYPE.RATING_HIGH_TO_LOW, label: 'Rating High To Low' }, +] + +type CategoryType = Record + +export const CATEGORIES: CategoryType = { + Smartphones: 'smartphones', + Laptops: 'laptops', + Fragrances: 'fragrances', + Skincare: 'skincare', + Groceries: 'groceries', + 'Home Decoration': 'home-decoration', + Furniture: 'furniture', + Tops: 'tops', + 'Womens Dresses': 'womens-dresses', + 'Womens Shoes': 'womens-shoes', + 'Mens Shirts': 'mens-shirts', + 'Mens Shoes': 'mens-shoes', + 'Mens Watches': 'mens-watches', + 'Womens Watches': 'womens-watches', + 'Womens Bags': 'womens-bags', + 'Womens Jewellery': 'womens-jewellery', + Sunglasses: 'sunglasses', + Automotive: 'automotive', + Motorcycle: 'motorcycle', + Lighting: 'lighting', +} + +export const BRAND = [ + 'Apple', + 'Samsung', + 'OPPO', + 'Huawei', + 'Microsoft Surface', + 'Infinix', + 'HP Pavilion', + 'Impression of Acqua Di Gio', + 'Royal_Mirage', + 'Fog Scent Xpressio', + 'Al Munakh', + 'Lord - Al-Rehab', + "L'Oreal Paris", + 'Hemani Tea', + 'Dermive', + 'ROREC White Rice', + 'Fair & Clear', + 'Saaf & Khaas', + 'Bake Parlor Big', + 'Baking Food Items', + 'fauji', + 'Dry Rose', + 'Boho Decor', + 'Flying Wooden', + 'LED Lights', + 'luxury palace', + 'Golden', + 'Furniture Bed Set', + 'Ratttan Outdoor', + 'Kitchen Shelf', + 'Multi Purpose', + 'AmnaMart', + 'Professional Wear', + 'Soft Cotton', + 'Top Sweater', + 'RED MICKY MOUSE..', + 'Digital Printed', + 'Ghazi Fabric', + 'IELGY', + 'IELGY fashion', + 'Synthetic Leather', + 'Sandals Flip Flops', + 'Maasai Sandals', + 'Arrivals Genuine', + 'Vintage Apparel', + 'FREE FIRE', + 'The Warehouse', + 'Sneakers', + 'Rubber', + 'Naviforce', + 'SKMEI 9117', + 'Strap Skeleton', + 'Stainless', + 'Eastern Watches', + 'Luxury Digital', + 'Watch Pearls', + 'Bracelet', + 'LouisWill', + 'Copenhagen Luxe', + 'Steal Frame', + 'Darojay', + 'Fashion Jewellery', + 'Cuff Butterfly', + 'Designer Sun Glasses', + 'mastar watch', + 'Car Aux', + 'W1209 DC12V', + 'TC Reusable', + 'Neon LED Light', + 'METRO 70cc Motorcycle - MR70', + 'BRAVE BULL', + 'shock absorber', + 'JIEPOLLY', + 'Xiangle', + 'lightingbrilliance', + 'Ifei Home', + 'DADAWU', + 'YIOSI', +] diff --git a/client/src/hooks/useFilters.ts b/client/src/hooks/useFilters.ts index 0933bbb..1444232 100644 --- a/client/src/hooks/useFilters.ts +++ b/client/src/hooks/useFilters.ts @@ -6,7 +6,7 @@ import useGetParams from './useGetParams' export default function useFilter() { const { data, isLoading } = useGetAllProductsQuery(null) - const { q, category, rating, price, sort } = useGetParams() + const { q, category, brand, rating, price, sort } = useGetParams() const products = data?.products const filteredData = products @@ -27,9 +27,12 @@ export default function useFilter() { ) return false - // category filter + // Category filter if (category && !(item.category === category)) return false + // Brand filter + if (brand && !(item.brand === brand)) return false + // Add more filters as needed return true }) diff --git a/client/src/hooks/useGetParams.ts b/client/src/hooks/useGetParams.ts index 209d676..01204f9 100644 --- a/client/src/hooks/useGetParams.ts +++ b/client/src/hooks/useGetParams.ts @@ -5,9 +5,10 @@ export default function useGetParams() { const q = searchParams.get('q') || '' const category = searchParams.get('category') || '' + const brand = searchParams.get('brand') || '' const rating = searchParams.get('rating') || '' const price = searchParams.get('price')?.split('-').map(Number) || [10, 2000] const sort = searchParams.get('sort') - return { category, price, q, rating, sort } + return { category, brand, price, q, rating, sort } } diff --git a/client/src/hooks/useHandleDispatch.ts b/client/src/hooks/useHandleDispatch.ts index 1bb4ce0..2dbcfbe 100644 --- a/client/src/hooks/useHandleDispatch.ts +++ b/client/src/hooks/useHandleDispatch.ts @@ -61,6 +61,14 @@ export default function useHandleDispatch() { }) } + const handleBrandFilter = (brand: string) => { + setSearchParams((prev) => { + prev.set('brand', brand) + prev.delete('q') + return prev + }) + } + const handleClearFilter = () => { setSearchParams((prev) => { prev.delete('q') @@ -68,6 +76,7 @@ export default function useHandleDispatch() { prev.delete('rating') prev.delete('price') prev.delete('sort') + prev.delete('brand') return prev }) } @@ -81,6 +90,7 @@ export default function useHandleDispatch() { handlePriceRange, handleSearchQuery, handleCategoryFilter, + handleBrandFilter, handleClearFilter, } } diff --git a/client/src/layouts/filters/SidebarFilters.tsx b/client/src/layouts/filters/SidebarFilters.tsx index 1f30f3a..1681e52 100644 --- a/client/src/layouts/filters/SidebarFilters.tsx +++ b/client/src/layouts/filters/SidebarFilters.tsx @@ -1,11 +1,12 @@ import { memo } from 'react' -import AppliedFilters from './components/applied-filters/AppliedFilters' import CategoryFilter from './components/CategoryFilter' -import ClearFilter from './components/clear-filter/ClearFilter' import PriceSliderFilter from './components/PriceSliderFilter' import RatingFilter from './components/RatingFilter' import SortProductsFilter from './components/SortProductsFilter' +import AppliedFilters from './components/applied-filters/AppliedFilters' +import BrandFilter from './components/brand-filter/BrandFilter' +import ClearFilter from './components/clear-filter/ClearFilter' function SidebarFilters() { return ( @@ -35,6 +36,8 @@ function SidebarFilters() { + +
) diff --git a/client/src/layouts/filters/components/CategoryFilter.tsx b/client/src/layouts/filters/components/CategoryFilter.tsx index 797563a..c509308 100644 --- a/client/src/layouts/filters/components/CategoryFilter.tsx +++ b/client/src/layouts/filters/components/CategoryFilter.tsx @@ -1,49 +1,25 @@ import { memo } from 'react' import Input from '@/components/ui/Input' +import { CATEGORIES } from '@/constants/filterConstants' import useGetParams from '@/hooks/useGetParams' import useHandleDispatch from '@/hooks/useHandleDispatch' -type CategoryType = Record - -const data: CategoryType = { - Smartphones: 'smartphones', - Laptops: 'laptops', - Fragrances: 'fragrances', - Skincare: 'skincare', - Groceries: 'groceries', - 'Home Decoration': 'home-decoration', - Furniture: 'furniture', - Tops: 'tops', - 'Womens Dresses': 'womens-dresses', - 'Womens Shoes': 'womens-shoes', - 'Mens Shirts': 'mens-shirts', - 'Mens Shoes': 'mens-shoes', - 'Mens Watches': 'mens-watches', - 'Womens Watches': 'womens-watches', - 'Womens Bags': 'womens-bags', - 'Womens Jewellery': 'womens-jewellery', - Sunglasses: 'sunglasses', - Automotive: 'automotive', - Motorcycle: 'motorcycle', - Lighting: 'lighting', -} - function CategoryFilter() { const { handleCategoryFilter } = useHandleDispatch() const { category: categoryParam } = useGetParams() return (

Categories

- {Object.keys(data).map((category) => ( + {Object.keys(CATEGORIES).map((category) => (
handleCategoryFilter(e.target.value)} - checked={categoryParam === data[category]} + checked={categoryParam === CATEGORIES[category]} />
))} diff --git a/client/src/layouts/filters/components/RatingFilter.tsx b/client/src/layouts/filters/components/RatingFilter.tsx index 09473c7..da0c852 100644 --- a/client/src/layouts/filters/components/RatingFilter.tsx +++ b/client/src/layouts/filters/components/RatingFilter.tsx @@ -1,24 +1,17 @@ import { memo } from 'react' import Input from '@/components/ui/Input' -import { RATING_TYPE } from '@/constants/filterConstants' +import { RATING_ITEMS } from '@/constants/filterConstants' import useGetParams from '@/hooks/useGetParams' import useHandleDispatch from '@/hooks/useHandleDispatch' -const ratingItems = [ - { value: RATING_TYPE.FOUR_AND_UP, label: '4★ & above' }, - { value: RATING_TYPE.THREE_AND_UP, label: '3★ & above' }, - { value: RATING_TYPE.TWO_AND_UP, label: '2★ & above' }, - { value: RATING_TYPE.ONE_AND_UP, label: '1★ & above' }, -] - function RatingFilter() { const { rating } = useGetParams() const { handleFilterRating } = useHandleDispatch() return (

Customer Review

- {ratingItems.map((item) => ( + {RATING_ITEMS.map((item) => (

Sort

- {sortItems.map((item) => ( + {SORT_ITEMS.map((item) => (
{ + let items: { value: string; label: string }[] = [] + + if (key === 'sort') { + items = SORT_ITEMS + } else if (key === 'rating') { + items = RATING_ITEMS + } + const item = items.find((item) => item.value === value) + return item ? item.label : value + } + return (
    @@ -25,7 +39,7 @@ function AppliedFilters() { key={key} onClick={() => handleRemoveFilter(key)} > - ✕ {value} + ✕ {getLabel(key, value)} )) ) : ( diff --git a/client/src/layouts/filters/components/brand-filter/BrandFilter.tsx b/client/src/layouts/filters/components/brand-filter/BrandFilter.tsx new file mode 100644 index 0000000..98ca589 --- /dev/null +++ b/client/src/layouts/filters/components/brand-filter/BrandFilter.tsx @@ -0,0 +1,31 @@ +import { memo } from 'react' + +import Input from '@/components/ui/Input' +import { BRAND } from '@/constants/filterConstants' +import useGetParams from '@/hooks/useGetParams' +import useHandleDispatch from '@/hooks/useHandleDispatch' + +function BrandFilter() { + const { handleBrandFilter } = useHandleDispatch() + const { brand: brandParam } = useGetParams() + return ( +
    +

    Brands

    + {BRAND.map((brand) => ( +
    + handleBrandFilter(e.target.value)} + checked={brandParam === brand} + /> +
    + ))} +
    + ) +} + +const MemoizedBrandFilter = memo(BrandFilter) +export default MemoizedBrandFilter diff --git a/client/src/main.tsx b/client/src/main.tsx index 7c8e4b1..d6d7164 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -3,6 +3,7 @@ import { Provider } from 'react-redux' import { RouterProvider } from 'react-router-dom' // Styles +import 'react-image-gallery/styles/css/image-gallery.css' import './assets/css/global.css' import './assets/css/reset.css' @@ -18,7 +19,7 @@ if (process.env.NODE_ENV === 'production') { console.error = () => {} } -createRoot(document.querySelector('#root') as HTMLElement).render( +createRoot(document.querySelector('#root')!).render( diff --git a/client/src/pages/cart/CartPage.module.css b/client/src/pages/cart/CartPage.module.css index ff02f38..a88e41a 100644 --- a/client/src/pages/cart/CartPage.module.css +++ b/client/src/pages/cart/CartPage.module.css @@ -9,60 +9,3 @@ gap: 1.2rem; min-height: 100%; } - -.cart-quantity-btn { - border: 1px solid var(--color-light-black); - border-radius: 50%; - height: 30px; - width: 30px; - cursor: pointer; -} - -.cart-item-btn { - border: 0; - padding: 0.3rem 0.8rem; - cursor: pointer; - background-color: transparent; - font-weight: 500; -} - -.cart-item-btn:hover { - color: var(--color-brand-blue); -} - -.cart-image { - height: 12rem; - width: 12rem; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; -} - -.cart-image img { - max-width: 100%; - max-height: 100%; - object-fit: cover; -} - -.cart-item { - width: 900px; - background-color: var(--color-white-bg); - margin-bottom: 0.1rem; - padding: 1rem; -} - -.cart-item-quantity { - display: flex; - align-items: center; - gap: 1rem; -} - -.cart-item-product-info { - display: flex; - gap: 2rem; -} - -.item-margin-top { - margin-top: 1.1rem; -} diff --git a/client/src/pages/cart/CartPage.tsx b/client/src/pages/cart/CartPage.tsx index 15b2d65..511afc8 100644 --- a/client/src/pages/cart/CartPage.tsx +++ b/client/src/pages/cart/CartPage.tsx @@ -1,15 +1,13 @@ -import useHandleDispatch from '@/hooks/useHandleDispatch' import { useAppSelector } from '@/state/store' import type { CartType } from '@/types' import styles from './CartPage.module.css' +import CartItem from './components/cart-item/CartItem' import CartPriceDetails from './components/cart-price-details/CartPriceDetails' import EmptyCart from './components/empty-cart/EmptyCart' import PlaceOrder from './components/place-order/PlaceOrder' export default function CartPage() { const cartData = useAppSelector((state) => state.cart) - const { handleRemoveFromCart, handleDecrementCartItem, handleAddToCart } = - useHandleDispatch() if (!cartData?.length) { return @@ -19,47 +17,7 @@ export default function CartPage() {
    {cartData?.map((product: CartType) => ( -
    -
    -
    - -
    -
    -

    {product.title}

    - {product.description} - $871 ${product.price} - {product.discountPercentage}% Off - 3 offers available -
    - Seller: Internet -
    - Delivery by Mon Jan 15 -
    -
    - - - {product.quantity} - - - - -
    -
    + ))}
    diff --git a/client/src/pages/cart/components/cart-item/CartItem.module.css b/client/src/pages/cart/components/cart-item/CartItem.module.css new file mode 100644 index 0000000..6606ab2 --- /dev/null +++ b/client/src/pages/cart/components/cart-item/CartItem.module.css @@ -0,0 +1,62 @@ +.cart-quantity-btn { + border: 1px solid #c2c2c2; + border-radius: 50%; + height: 30px; + width: 30px; + cursor: pointer; + background: linear-gradient(#fff, #f9f9f9); +} + +.cart-item-btn { + border: 0; + padding: 0.3rem 0.8rem; + cursor: pointer; + background-color: transparent; + font-weight: 500; +} + +.cart-item-btn:hover { + color: var(--color-brand-blue); +} + +.cart-image { + height: 12rem; + width: 12rem; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.cart-image img { + max-width: 100%; + max-height: 100%; + object-fit: cover; +} + +.cart-item { + width: 900px; + background-color: var(--color-white-bg); + margin-bottom: 0.1rem; + padding: 1rem; +} + +.cart-item-quantity { + display: flex; + align-items: center; + gap: 1rem; +} + +.cart-item-quantity span { + border: 1px solid #c2c2c2; + padding: 2px 20px; +} + +.cart-item-product-info { + display: flex; + gap: 2rem; +} + +.item-margin-top { + margin-top: 1.1rem; +} diff --git a/client/src/pages/cart/components/cart-item/CartItem.tsx b/client/src/pages/cart/components/cart-item/CartItem.tsx new file mode 100644 index 0000000..35eb294 --- /dev/null +++ b/client/src/pages/cart/components/cart-item/CartItem.tsx @@ -0,0 +1,54 @@ +import useHandleDispatch from '@/hooks/useHandleDispatch' +import type { CartType } from '@/types' +import styles from './CartItem.module.css' + +type CartItemProps = { + product: CartType +} + +export default function CartItem({ product }: CartItemProps) { + const { handleRemoveFromCart, handleDecrementCartItem, handleAddToCart } = + useHandleDispatch() + + return ( +
    +
    +
    + +
    +
    +

    {product.title}

    + {product.description} + $871 ${product.price} + {product.discountPercentage}% Off + 3 offers available +
    + Seller: Internet +
    + Delivery by Mon Jan 15 +
    +
    + + {product.quantity} + + + +
    +
    + ) +} diff --git a/client/src/pages/product-details/ProductDetailsPage.module.css b/client/src/pages/product-details/ProductDetailsPage.module.css new file mode 100644 index 0000000..5b8cc7d --- /dev/null +++ b/client/src/pages/product-details/ProductDetailsPage.module.css @@ -0,0 +1,30 @@ +.product-details-container { + display: flex; + min-height: 100dvh; + max-width: 80rem; + margin: 0 auto; + gap: 2rem; + margin-top: 1rem; +} + +.image-gallery-wrapper { + max-width: 40rem; + min-height: 30rem; +} + +.go-to-cart-page, +.add-to-cart-btn { + margin: 1rem 1rem; + padding: 1rem; + border: 0; + outline: 0; + background-color: var(--color-brand-orange); + right: 30px; + bottom: 20px; + cursor: pointer; + border-radius: 3px; + width: 15rem; + color: white; + font-size: 1rem; + font-weight: 500; +} diff --git a/client/src/pages/product-details/ProductDetailsPage.tsx b/client/src/pages/product-details/ProductDetailsPage.tsx index 752e805..89706fe 100644 --- a/client/src/pages/product-details/ProductDetailsPage.tsx +++ b/client/src/pages/product-details/ProductDetailsPage.tsx @@ -1,42 +1,53 @@ import { Link, useParams } from 'react-router-dom' +// @ts-expect-error - no type defination for RIG +import ImageGallery from 'react-image-gallery' +import Loader from '@/components/loader/Loader' import useHandleDispatch from '@/hooks/useHandleDispatch' import { useGetProductByIdQuery } from '@/state/services/productApi' -import { useState } from 'react' +import { useAppSelector } from '@/state/store' +import { getOriginalAndThumbnailImg, isItemInCart } from '@/utils' +import styles from './ProductDetailsPage.module.css' export default function ProductDetailsPage() { - const [isAddedToCart] = useState(false) const { id } = useParams() const { handleAddToCart } = useHandleDispatch() - const { data, isFetching } = useGetProductByIdQuery(String(id)) - if (isFetching) return

    Loading...

    + const cartData = useAppSelector((state) => state.cart) + if (isFetching || !data) return + + const Images = getOriginalAndThumbnailImg(data.images) return ( -
    +
    - {data?.title} +
    + +
    + {isItemInCart(cartData, data) ? ( + + + + ) : ( + + )}

    {data?.title}

    - {isAddedToCart ? ( - <> - - - ) : ( - <> - - - )}
    ) diff --git a/client/src/pages/product-listing/components/product-card/ProductCard.module.css b/client/src/pages/product-listing/components/product-card/ProductCard.module.css index e69de29..0c8b1aa 100644 --- a/client/src/pages/product-listing/components/product-card/ProductCard.module.css +++ b/client/src/pages/product-listing/components/product-card/ProductCard.module.css @@ -0,0 +1,44 @@ +.product-card-wrapper { + display: flex; + align-items: center; + justify-content: space-between; +} + +.left { + display: flex; + align-items: center; + gap: 1rem; +} + +.product-image { + height: 12rem; + width: 12rem; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.product-image img { + max-width: 100%; + max-height: 100%; + object-fit: cover; +} + +.right { + margin-right: 5rem; +} + +.right h2 { + font-weight: 500; + font-size: 25px; +} + +.product-desc { + max-width: 30rem; +} + +.product-discount { + color: #388e3c; + font-size: 13px; +} diff --git a/client/src/pages/product-listing/components/product-card/ProductCard.tsx b/client/src/pages/product-listing/components/product-card/ProductCard.tsx index a3b4621..8b2c968 100644 --- a/client/src/pages/product-listing/components/product-card/ProductCard.tsx +++ b/client/src/pages/product-listing/components/product-card/ProductCard.tsx @@ -1,7 +1,10 @@ -import { ProductType } from '@/types' import Paper from '@mui/material/Paper' +import Rating from '@mui/material/Rating' import { Link } from 'react-router-dom' +import { ProductType } from '@/types' +import styles from './ProductCard.module.css' + type Props = { product: ProductType } @@ -12,28 +15,32 @@ export default function ProductCard({ product }: Props) { -
    - {product.title} -

    {product.title}

    -

    {product.rating}

    -

    {product.description}

    +
    +
    + {product.title} +
    +
    +

    {product.title}

    + +

    {product.description}

    +
    - -
    +

    ${product.price}

    + + {product.discountPercentage}% off +
    diff --git a/client/src/types/index.ts b/client/src/types/index.ts index c15ca15..c34cfed 100644 --- a/client/src/types/index.ts +++ b/client/src/types/index.ts @@ -9,6 +9,7 @@ export type ProductType = { brand: string category: string thumbnail: string + images: string[] } export type CartType = ProductType & { diff --git a/client/src/utils/index.ts b/client/src/utils/index.ts index 344e0c0..8b6806b 100644 --- a/client/src/utils/index.ts +++ b/client/src/utils/index.ts @@ -1,4 +1,4 @@ -import type { CartType } from '@/types' +import type { CartType, ProductType } from '@/types' export const totalCartPrice = (cartData: CartType[]) => { let cartPrice = 0 @@ -13,3 +13,16 @@ type ClassesType = string | boolean | null | undefined export const cx = (...classes: ClassesType[]) => { return classes.filter(Boolean).join(' ') } + +export const isItemInCart = (cartData: CartType[], data: ProductType) => { + return cartData?.map((item: CartType) => item.id).includes(data?.id) +} + +export const getOriginalAndThumbnailImg = (images: string[]) => { + return images?.map((url) => { + return { + original: url, + thumbnail: url, + } + }) +}