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
)
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 (
+
+ )
+}
+
+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 (
-
+
-

+
+
+
+ {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.rating}
-
{product.description}
+
+
+

+
+
+
{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,
+ }
+ })
+}