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:
hamid zarghami
2025-09-07 09:17:58 +03:30
parent 5ec2b621bc
commit 2995b11900
17 changed files with 667 additions and 343 deletions
+44 -66
View File
@@ -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>