Skip to content

Commit

Permalink
[finishes #187816382] product details page
Browse files Browse the repository at this point in the history
  • Loading branch information
Mag codes committed Jul 2, 2024
1 parent b333734 commit a470891
Show file tree
Hide file tree
Showing 18 changed files with 700 additions and 24 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
},
Expand Down
7 changes: 6 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ 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 { setError, setIsLoading, setProductFetched, setProductsDataList } from './redux/slices/productsSlice';import { ProductDetail } from './pages/product/ProductDetail';

const App = () => {
const { data, error, isLoading } = useGetProductsQuery();
const dispatch = useDispatch();
Expand Down Expand Up @@ -59,6 +60,10 @@ const App = () => {
path: 'auth/success/:token',
element: <GoogleAuthSuccess />,
},
{
path: 'products/:id',
element: <ProductDetail />,
},
],
},
{
Expand Down
10 changes: 10 additions & 0 deletions src/components/Products/ColorComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
interface Props {
name: string;
}
export const ColorComponent = ({ name }: Props) => {
return (
<div className='border hover:border-greenColor hover:border active:border-greenColor rounded-full py-1 px-4 hover:cursor-pointer'>
{name}
</div>
);
};
26 changes: 26 additions & 0 deletions src/components/Products/ImageCard.tsx
Original file line number Diff line number Diff line change
@@ -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 = (
<img onClick={() => handleClick && handleClick(image)} className={imgClass} src={image} alt={alt} />
);

return enableZoom ? (
<Zoom wrapElement='span' a11yNameButtonZoom='Tap to zoom'>
{imgElement}
</Zoom>
) : (
imgElement
);
};
19 changes: 19 additions & 0 deletions src/components/Products/ImageToggle.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
onClick={handleClick}
className={`max-w-fit absolute p-2 shadow-sm rounded-full bg-whiteColor hover:cursor-pointer border ${positionClass}`}
>
<Icon size={size} />
</div>
);
};
34 changes: 19 additions & 15 deletions src/components/Products/ProductCard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useNavigate } from 'react-router-dom';
import { Product } from '../../types/Types';

interface ProductCardProps {
Expand All @@ -7,26 +8,29 @@ interface ProductCardProps {
const ProductCard: React.FC<ProductCardProps> = ({ product }) => {
const imageUrl = product?.images?.[0] ?? 'default-image-url';
const price = product?.sizes?.[0]?.price ?? 'default-image-url';
const navigate = useNavigate();

return (
<div className="product-card">
<div className="product-image">
<img
src={imageUrl}
alt={product.name}
className="h-48 w-full rounded-sm object-cover"
/>
<div
onClick={() => {
navigate(`/products/${product.id}`);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
className='product-card min-w-48 bg-whiteColor hover:cursor-pointer hover:transform hover:transition-all hover:scale-105'
>
<div className='product-image'>
<img src={imageUrl} alt={product.name} className='h-48 w-full rounded-sm object-cover' />
</div>
<div className="product-name-cart-button pt-2">
<div className="product-manufacturer">
<div className="name-price flex justify-between">
<h3 className="text-md font-bold">{product.name}</h3>
<p className="text-md font-bold">${price}</p>
<div className='product-name-cart-button pt-2'>
<div className='product-manufacturer'>
<div className='name-price flex justify-between'>
<h3 className='text-md font-bold'>{product.name}</h3>
<p className='text-md font-bold'>${price}</p>
</div>
<p className="text-sm p-2 text-[#949191]">{product.manufacturer}</p>
<p className='text-sm p-2 text-[#949191]'>{product.manufacturer}</p>
</div>
<div className="cart-button">
<button className="px-5 py-2 text-center bg-[#007A7A] text-whiteColor font-bold rounded-sm">
<div className='cart-button'>
<button className='px-5 py-2 text-center bg-[#007A7A] text-whiteColor font-bold rounded-sm'>
Add to Cart
</button>
</div>
Expand Down
32 changes: 32 additions & 0 deletions src/components/Products/ProductReviewCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
interface Props {
name: string;
profileImage: string;
reviewText: string;
dateReviewed: string;
}

export const ProductReviewCard = ({ name, profileImage, reviewText, dateReviewed }: Props) => {
return (
<div className='flex border-b border-grayColor py-2 snap-center'>
<div className='flex p-2 w-1/5 min-h-14 justify-center'>
<img className='rounded-full h-14 w-14' src={profileImage} alt='Buyer image' />
</div>
<div className=''>
<div className='space-y-1'>
<p className='font-medium'>{name}</p>
<div className='flex gap-4 items-center'>
{/* <span className='flex'>
<FaStar size='15px' />
<FaStar size='15px' />
<FaStar size='15px' />
<FaStar size='15px' />
<FaStar size='15px' />
</span> */}
<span className='text-xs'>{dateReviewed}</span>
</div>
<p className='text-sm'>{reviewText}</p>
</div>
</div>
</div>
);
};
11 changes: 8 additions & 3 deletions src/components/common/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { cn } from "../../utils";
import { cn } from '../../utils';

interface ButtonProps {
text: string;
buttonId?: string;
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 (
<button
disabled={disabled}
type={type}
className={cn('p-2 rounded-lg bg-greenColor hover:bg-darkGreen transition-all text-whiteColor font-bold', className)}
className={cn(
'p-2 rounded-lg bg-greenColor hover:bg-darkGreen transition-all text-whiteColor font-bold',
className
)}
onClick={onClick}
>
{text}
Expand Down
103 changes: 103 additions & 0 deletions src/containers/ProductDetail/ProductDetailSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { IoIosArrowBack, IoIosArrowForward } from 'react-icons/io';
import Navbar from '../../components/navbar/Navbar';
import Footer from '../../components/footer/Footer';
import { ImageToggle } from '../../components/Products/ImageToggle';

const ProductDetailSkeleton = () => {
return (
<div className='w-full space-y-2'>
<Navbar />
<div className='w-full lg:flex lg:px-12'>
<div className='lg:flex md:flex relative lg:flex-auto'>
<div className='hidden lg:max-h-[500px] gap-2 md:flex md:flex-col md:mx-auto md:max-h-[400px] lg:flex lg:flex-col overflow-hidden'>
{[...Array(4)].map((_, index) => (
<div key={index} className='w-full h-20 mx-auto mt-2 bg-slate-700 rounded-md animate-pulse' />
))}
</div>
<div className='relative w-full overflow-hidden md:w-4/5 lg:h-[500px] lg:w-4/5 mx-auto'>
<div className='md:min-h-full max-h-80 lg:min-h-full w-full bg-slate-700 rounded-md animate-pulse' />
<ImageToggle size='20px' icon={IoIosArrowForward} positionClass='right-4 lg:right-10 top-1/2' />
<ImageToggle size='20px' icon={IoIosArrowBack} positionClass='left-4 lg:left-10 top-1/2' />
</div>
</div>
<div className='lg:w-2/5 px-3 space-y-3 mt-3'>
<div className='lg:flex lg:flex-col md:flex md:gap-8'>
<div className='lg:w-full md:w-1/2'>
<div className='w-full gap-3 flex mb-3 justify-between text-xl font-medium'>
<div className='w-3/5 flex-1 bg-slate-700 h-6 rounded-md animate-pulse'></div>
<div className='bg-slate-700 h-6 w-20 rounded-md animate-pulse'></div>
</div>
<div>
<p className='font-medium'>Color</p>
<div className='flex gap-2 w-full p-2 flex-wrap'>
{[...Array(3)].map((_, index) => (
<div key={index} className='bg-slate-700 w-8 h-8 rounded-full animate-pulse'></div>
))}
</div>
</div>
<div className='flex'>
<div className='w-full flex flex-col gap-2'>
<label htmlFor='size' className='leading-none font-medium'>
Size
</label>
<div className='flex w-full gap-2'>
<div className='border-greenColor border-2 lg:py-2 text-sm px-2 bg-slate-700 w-1/2 h-10 rounded-md animate-pulse'></div>
<div className='flex border-2 items-center py-1 px-6 gap-4 w-1/2 bg-slate-700 h-10 rounded-md animate-pulse'></div>
</div>
</div>
</div>
<div className='w-full py-2 my-4 bg-slate-700 h-10 rounded-md animate-pulse'></div>
</div>
<div className='lg:w-full md:w-1/2'>
<div className='border-y border-grayColor gap-3 flex flex-row items-center py-4'>
<span>Review</span>
<span className='flex gap-1'>
{[...Array(5)].map((_, index) => (
<div key={index} className='bg-slate-700 w-5 h-5 rounded-full animate-pulse'></div>
))}
</span>
<span>(8)</span>
</div>
<div className='border-b text-sm border-grayColor py-2'>
<div className='bg-slate-700 h-6 w-full rounded-md animate-pulse mb-2'></div>
<div className='bg-slate-700 h-6 w-3/4 rounded-md animate-pulse'></div>
<div className='text-greenColor mt-4 font-medium text-right hover:cursor-pointer'>Show more</div>
</div>
</div>
</div>
</div>
</div>
<div className='lg:px-12 lg:flex'>
<div className='lg:w-1/2 lg:border border-grayColor lg:rounded-sm lg:p-2'>
<div>
<h1 className='font-bold text-2xl border-b border-grayColor py-4'>Product Reviews</h1>
{[...Array(2)].map((_, index) => (
<div key={index} className='flex gap-4 py-4 border-b border-grayColor'>
<div className='w-12 h-12 rounded-full bg-slate-700 animate-pulse'></div>
<div className='flex-1'>
<div className='bg-slate-700 h-4 w-1/2 rounded-md animate-pulse mb-2'></div>
<div className='bg-slate-700 h-4 w-3/4 rounded-md animate-pulse'></div>
</div>
</div>
))}
</div>
</div>
<div className='lg:w-1/2 lg:border border-grayColor lg:rounded-sm lg:p-2'>
<h1 className='font-bold text-2xl border-b border-grayColor py-4'>Recommended Products</h1>
{[...Array(3)].map((_, index) => (
<div key={index} className='flex gap-4 py-4 border-b border-grayColor'>
<div className='w-20 h-20 bg-slate-700 rounded-md animate-pulse'></div>
<div className='flex-1'>
<div className='bg-slate-700 h-4 w-3/4 rounded-md animate-pulse mb-2'></div>
<div className='bg-slate-700 h-4 w-1/2 rounded-md animate-pulse'></div>
</div>
</div>
))}
</div>
</div>
<Footer />
</div>
);
};

export default ProductDetailSkeleton;
Loading

0 comments on commit a470891

Please sign in to comment.