diff --git a/package.json b/package.json
index 838e402..863c171 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
- "dev": "vite",
+ "dev": "vite --host",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
@@ -27,11 +27,13 @@
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.5",
"react-icons": "^5.2.1",
+ "react-medium-image-zoom": "^5.2.5",
"react-redux": "^9.1.2",
"react-router-dom": "^6.23.1",
"react-toastify": "^10.0.5",
"sass": "^1.77.2",
"tailwind-merge": "^2.3.0",
+ "tailwind-scrollbar": "^3.1.0",
"tailwind-scrollbar-hide": "^1.1.7",
"zod": "^3.23.8"
},
diff --git a/src/App.tsx b/src/App.tsx
index 300c26e..19593e4 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -23,6 +23,8 @@ import Settings from './pages/admin/Settings';
import CategoriesPage from './pages/CategoriesPage';
import ResetPassword from './pages/ResetPassword';
import NewPassword from './pages/NewPassword';
+import { ProductDetail } from './pages/product/ProductDetail';
+
const App = () => {
const { data, error, isLoading } = useGetProductsQuery();
const dispatch = useDispatch();
@@ -82,6 +84,10 @@ const App = () => {
},
],
},
+ {
+ path: 'products/:id',
+ element: ,
+ },
],
},
{
diff --git a/src/assets/defaultProfile.avif b/src/assets/defaultProfile.avif
new file mode 100644
index 0000000..fdf4ff6
Binary files /dev/null and b/src/assets/defaultProfile.avif differ
diff --git a/src/components/Products/ColorComponent.tsx b/src/components/Products/ColorComponent.tsx
new file mode 100644
index 0000000..bd87afc
--- /dev/null
+++ b/src/components/Products/ColorComponent.tsx
@@ -0,0 +1,10 @@
+interface Props {
+ name: string;
+}
+export const ColorComponent = ({ name }: Props) => {
+ return (
+
+ {name}
+
+ );
+};
diff --git a/src/components/Products/ImageCard.tsx b/src/components/Products/ImageCard.tsx
new file mode 100644
index 0000000..182b7f2
--- /dev/null
+++ b/src/components/Products/ImageCard.tsx
@@ -0,0 +1,26 @@
+import Zoom from 'react-medium-image-zoom';
+import 'react-medium-image-zoom/dist/styles.css';
+
+interface Props {
+ styles: string;
+ image: string;
+ alt?: string;
+ handleClick?: (image: string) => void;
+ enableZoom?: boolean;
+ isSpotted?: boolean;
+}
+
+export const ImageCard = ({ image, styles, alt, handleClick, enableZoom = true, isSpotted = false }: Props) => {
+ const imgClass = isSpotted ? `border border-greenColor rounded-lg p-1 ${styles}` : styles;
+ const imgElement = (
+
handleClick && handleClick(image)} className={imgClass} src={image} alt={alt} />
+ );
+
+ return enableZoom ? (
+
+ {imgElement}
+
+ ) : (
+ imgElement
+ );
+};
diff --git a/src/components/Products/ImageToggle.tsx b/src/components/Products/ImageToggle.tsx
new file mode 100644
index 0000000..b5f7c63
--- /dev/null
+++ b/src/components/Products/ImageToggle.tsx
@@ -0,0 +1,19 @@
+import { IconType } from 'react-icons';
+
+interface Props {
+ icon: IconType;
+ positionClass: string;
+ size?: string;
+ handleClick?: (e: any) => void;
+}
+
+export const ImageToggle = ({ icon: Icon, positionClass, size, handleClick }: Props) => {
+ return (
+
+
+
+ );
+};
diff --git a/src/components/Products/ProductCard.tsx b/src/components/Products/ProductCard.tsx
index 54628c5..de18618 100644
--- a/src/components/Products/ProductCard.tsx
+++ b/src/components/Products/ProductCard.tsx
@@ -2,7 +2,6 @@ import { FaHeart } from 'react-icons/fa6';
import { Product } from '../../types/Types';
import { TiShoppingCart } from 'react-icons/ti';
import StarRating from '../common/Ratings';
-
interface ProductCardProps {
product: Product;
}
@@ -12,29 +11,32 @@ const ProductCard: React.FC = ({ product }) => {
const price = product?.sizes?.[0]?.price ?? '';
return (
-
-
-

+
{
+ 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'
+ >
+
+
-
-
-
-
{product.name}
-
${price}
+
+
+
+
{product.name}
+
${price}
-
{product.manufacturer}
+
{product.manufacturer}
-
-
-
-
-
+
+
+
+
+
-
diff --git a/src/components/Products/ProductReviewCard.tsx b/src/components/Products/ProductReviewCard.tsx
new file mode 100644
index 0000000..b951609
--- /dev/null
+++ b/src/components/Products/ProductReviewCard.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import { Review } from '../../utils/schemas';
+import { FaStar } from 'react-icons/fa';
+import defaultProfile from '../../assets/defaultProfile.avif';
+
+interface Props {
+ review: Review;
+}
+
+const ProductReviewCard: React.FC
= ({ review }) => {
+ const dateReviewed = new Date(review.createdAt).toLocaleDateString();
+ const reviewText = review.feedback;
+
+ const renderStars = (rating: number) => {
+ return Array.from({ length: 5 }, (_, index) => (
+
+ ));
+ };
+
+ return (
+
+
+

+
+
+
+
{review.user.firstName}
+
+ {renderStars(review.rating)}
+ {dateReviewed}
+
+
{reviewText}
+
+
+
+ );
+};
+
+export default ProductReviewCard;
diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx
index 0bff237..472efaf 100644
--- a/src/components/common/Button.tsx
+++ b/src/components/common/Button.tsx
@@ -6,10 +6,12 @@ interface ButtonProps {
type?: 'submit' | 'reset' | 'button';
className?: string;
onClick?: () => void;
+ disabled?: boolean;
}
-const Button = ({ text, type, className, onClick }: ButtonProps) => {
+const Button = ({ text, type, className, disabled, onClick }: ButtonProps) => {
return (