feat: add product detail page with components and infinite scroll
- Add SingleProduct component for product detail page - Implement product data hooks and services - Add infinite scroll functionality for products list - Update ProductCard component with new features - Add product types and specifications - Fix TypeScript errors in product page - Update profile page layout - Add global CSS improvements
This commit is contained in:
@@ -3,65 +3,33 @@ import { FC, useMemo, useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Star1 } from 'iconsax-react'
|
||||
import { Product, Review } from '@/types/product.types'
|
||||
|
||||
type ReviewItem = {
|
||||
id: number
|
||||
author: string
|
||||
date: string
|
||||
rating: number
|
||||
text: string
|
||||
isBuyer: boolean
|
||||
interface ReviewsProps {
|
||||
product: Product
|
||||
reviews?: Review[]
|
||||
}
|
||||
|
||||
const REVIEWS: ReviewItem[] = [
|
||||
{
|
||||
id: 1,
|
||||
author: 'علی رضوانی',
|
||||
date: '1401 آذر 18',
|
||||
rating: 3.2,
|
||||
text: 'قیمتش خوب بود',
|
||||
isBuyer: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
author: 'علی رضوانی',
|
||||
date: '1401 آذر 18',
|
||||
rating: 3.0,
|
||||
text: 'گوشیشه همه چی تمام',
|
||||
isBuyer: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
author: 'علی رضوانی',
|
||||
date: '1401 آذر 18',
|
||||
rating: 1.5,
|
||||
text: 'با کیفیت، خوش دست. بدون هیچ امتیاز قابل توجه نسبت به s23',
|
||||
isBuyer: true,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
author: 'علی رضوانی',
|
||||
date: '1401 آذر 18',
|
||||
rating: 4.0,
|
||||
text: 'واقعاً گوشی فوقالعادهای هست هیچ نکته ی منفی ندارد',
|
||||
isBuyer: true,
|
||||
},
|
||||
]
|
||||
|
||||
const Reviews: FC = () => {
|
||||
const Reviews: FC<ReviewsProps> = ({ reviews = [] }) => {
|
||||
const [sort, setSort] = useState<'new' | 'helpful'>('new')
|
||||
|
||||
const sortedReviews = useMemo(() => {
|
||||
if (sort === 'new') {
|
||||
return [...REVIEWS].sort((a, b) => b.id - a.id)
|
||||
return [...reviews].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
||||
}
|
||||
return [...REVIEWS].sort((a, b) => b.rating - a.rating)
|
||||
}, [sort])
|
||||
return [...reviews].sort((a, b) => b.helpful - a.helpful)
|
||||
}, [sort, reviews])
|
||||
|
||||
const average = useMemo(() => {
|
||||
const sum = REVIEWS.reduce((acc, r) => acc + r.rating, 0)
|
||||
return (sum / REVIEWS.length).toFixed(1)
|
||||
}, [])
|
||||
if (reviews.length === 0) return '0.0'
|
||||
const sum = reviews.reduce((acc, r) => acc + r.rating, 0)
|
||||
return (sum / reviews.length).toFixed(1)
|
||||
}, [reviews])
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleDateString('fa-IR')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-12 sm:mt-16 w-full">
|
||||
@@ -97,26 +65,36 @@ const Reviews: FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="mt-4 sm:mt-6 rounded-xl bg-white">
|
||||
{sortedReviews.map((r, idx) => (
|
||||
<div key={r.id} className="px-4 sm:px-6 py-4 sm:py-5">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-start gap-2 sm:gap-3">
|
||||
<div className="px-2 py-0.5 rounded-full bg-[#02A55A] text-white text-xs w-fit">{r.rating}</div>
|
||||
<div className="flex flex-wrap items-center gap-1 sm:gap-2 text-xs text-[#999999]">
|
||||
{r.isBuyer && (
|
||||
<span className="px-2 py-0.5 rounded bg-[#F1F1F1] text-[#7F7F7F]">خریدار</span>
|
||||
)}
|
||||
<span className="size-1.5 rounded-full bg-[#E5E5E5]"></span>
|
||||
<span>{r.date}</span>
|
||||
<span className="size-1.5 rounded-full bg-[#E5E5E5]"></span>
|
||||
<span>{r.author}</span>
|
||||
{sortedReviews.length > 0 ? (
|
||||
sortedReviews.map((review, idx) => (
|
||||
<div key={review._id} className="px-4 sm:px-6 py-4 sm:py-5">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-start gap-2 sm:gap-3">
|
||||
<div className="px-2 py-0.5 rounded-full bg-[#02A55A] text-white text-xs w-fit">
|
||||
{review.rating.toFixed(1)}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-1 sm:gap-2 text-xs text-[#999999]">
|
||||
{review.isVerified && (
|
||||
<span className="px-2 py-0.5 rounded bg-[#F1F1F1] text-[#7F7F7F]">خریدار</span>
|
||||
)}
|
||||
<span className="size-1.5 rounded-full bg-[#E5E5E5]"></span>
|
||||
<span>{formatDate(review.date)}</span>
|
||||
<span className="size-1.5 rounded-full bg-[#E5E5E5]"></span>
|
||||
<span>{review.user.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 text-[#333333] text-xs sm:text-sm leading-6 sm:leading-7 text-right">
|
||||
{review.comment}
|
||||
</div>
|
||||
|
||||
{idx !== sortedReviews.length - 1 && <div className="mt-4 sm:mt-5"> <Separator /> </div>}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 text-[#333333] text-xs sm:text-sm leading-6 sm:leading-7 text-right">{r.text}</div>
|
||||
|
||||
{idx !== sortedReviews.length - 1 && <div className="mt-4 sm:mt-5"> <Separator /> </div>}
|
||||
))
|
||||
) : (
|
||||
<div className="px-4 sm:px-6 py-8 text-center text-[#999999] text-sm">
|
||||
هنوز نظری ثبت نشده است
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user