298 lines
10 KiB
TypeScript
298 lines
10 KiB
TypeScript
"use client";
|
|
|
|
import { BadgeCheck, ThumbsUp } from "lucide-react";
|
|
import { useState } from "react";
|
|
|
|
import { Rating } from "@/components/shadcnblocks/rating";
|
|
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface Review {
|
|
id: string;
|
|
rating: number;
|
|
title: string;
|
|
content: string;
|
|
author: {
|
|
name: string;
|
|
avatar?: string;
|
|
};
|
|
date: string;
|
|
verified?: boolean;
|
|
images?: string[];
|
|
helpful?: number;
|
|
variant?: string;
|
|
}
|
|
|
|
const DEFAULT_REVIEWS: Review[] = [
|
|
{
|
|
id: "1",
|
|
rating: 5,
|
|
title: "کیفیت فوقالعاده",
|
|
content:
|
|
"دوخت و جزئیاتش باورنکردنی است. عکسها حقش را نمیدهند؛ حضوری حتی بهتر به نظر میرسد. جنسش لوکس است و اندازهاش دقیقاً مناسب است. تا حالا خیلی تعریف شنیدهام!",
|
|
author: {
|
|
name: "سارا احمدی",
|
|
avatar: "/block-media/shadcnblocks/block/avatar-1.webp",
|
|
},
|
|
date: "۲۰ آذر ۱۴۰۳",
|
|
verified: true,
|
|
images: [
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Person-in-Activewear-1.png",
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Man-in-White-Hoodie-1.png",
|
|
],
|
|
helpful: 24,
|
|
variant: "سایز M، آبی نفتی",
|
|
},
|
|
{
|
|
id: "2",
|
|
rating: 4,
|
|
title: "محصول عالی، اندازه کمی کوچک",
|
|
content:
|
|
"کیفیت و طراحی را دوست دارم. کمی کوچکتر از اندازه معمول است؛ پیشنهاد میکنم یک سایز بزرگتر بگیرید. بهجز آن دقیقاً همان چیزی بود که میخواستم. ارسال هم سریع بود!",
|
|
author: {
|
|
name: "مازیار رضایی",
|
|
avatar: "/block-media/shadcnblocks/block/avatar-2.webp",
|
|
},
|
|
date: "۱۸ آذر ۱۴۰۳",
|
|
verified: true,
|
|
helpful: 18,
|
|
variant: "سایز L، مشکی",
|
|
},
|
|
{
|
|
id: "3",
|
|
rating: 5,
|
|
title: "علاقه جدیدم!",
|
|
content:
|
|
"ماهها دنبال چنین محصولی بودم. به جزئیات با دقت زیادی توجه شده؛ از دوخت تا یراقآلات همهچیز عالی است. ارزش هر ریالش را دارد.",
|
|
author: {
|
|
name: "نیلوفر حسینی",
|
|
avatar: "/block-media/shadcnblocks/block/avatar-3.webp",
|
|
},
|
|
date: "۱۵ آذر ۱۴۰۳",
|
|
verified: true,
|
|
images: [
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/elegant-peach-scarf.png",
|
|
],
|
|
helpful: 31,
|
|
variant: "یک سایز، کرم",
|
|
},
|
|
{
|
|
id: "4",
|
|
rating: 5,
|
|
title: "فراتر از انتظار",
|
|
content:
|
|
"اول برای خرید آنلاین مردد بودم، ولی خوشحالم که سفارش دادم. رنگ دقیقاً مثل عکس است و کیفیت عالی. وقتی درباره نگهداری سوال داشتم، پشتیبانی هم سریع پاسخ داد.",
|
|
author: {
|
|
name: "امیر محمدی",
|
|
},
|
|
date: "۱۲ آذر ۱۴۰۳",
|
|
verified: false,
|
|
helpful: 12,
|
|
},
|
|
{
|
|
id: "5",
|
|
rating: 4,
|
|
title: "زیبا ولی گران",
|
|
content:
|
|
"محصول زیبا و بادوام است. مدتی بهخاطر قیمت مردد بودم، ولی در نهایت از خریدم راضیام. دوست دارم در آینده گزینههای رنگی بیشتری هم اضافه شود.",
|
|
author: {
|
|
name: "رؤیا کاظمی",
|
|
avatar: "/block-media/shadcnblocks/block/avatar-3.webp",
|
|
},
|
|
date: "۸ آذر ۱۴۰۳",
|
|
verified: true,
|
|
images: [
|
|
"/block-media/shadcnblocks/block/ecommerce/accessories/Elegant-Gold-Earrings-1.png",
|
|
],
|
|
helpful: 8,
|
|
variant: "طلایی",
|
|
},
|
|
];
|
|
|
|
interface Reviews3Props {
|
|
reviews?: Review[];
|
|
title?: string;
|
|
className?: string;
|
|
}
|
|
|
|
const Reviews3 = ({
|
|
reviews = DEFAULT_REVIEWS,
|
|
title = "نظرات مشتریان",
|
|
className,
|
|
}: Reviews3Props) => {
|
|
const [helpfulClicked, setHelpfulClicked] = useState<Set<string>>(new Set());
|
|
|
|
const totalReviews = reviews.length;
|
|
const averageRating =
|
|
reviews.reduce((sum, review) => sum + review.rating, 0) / totalReviews;
|
|
|
|
const formattedAverage = averageRating.toLocaleString("fa-IR", {
|
|
minimumFractionDigits: 1,
|
|
maximumFractionDigits: 1,
|
|
});
|
|
|
|
const handleHelpful = (reviewId: string) => {
|
|
setHelpfulClicked((prev) => {
|
|
const newSet = new Set(prev);
|
|
if (newSet.has(reviewId)) {
|
|
newSet.delete(reviewId);
|
|
} else {
|
|
newSet.add(reviewId);
|
|
}
|
|
return newSet;
|
|
});
|
|
};
|
|
|
|
const getHelpfulCount = (review: Review) => {
|
|
const base = review.helpful ?? 0;
|
|
const total = base + (helpfulClicked.has(review.id) ? 1 : 0);
|
|
return total.toLocaleString("fa-IR");
|
|
};
|
|
|
|
return (
|
|
<section
|
|
dir="rtl"
|
|
lang="fa"
|
|
className={cn("py-16 md:py-24", className)}
|
|
>
|
|
<div className="container max-w-3xl">
|
|
<div className="mb-8 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
|
<div>
|
|
<h2 className="text-xl font-semibold tracking-tight md:text-3xl">
|
|
{title}
|
|
</h2>
|
|
<div className="mt-2 flex items-center gap-2">
|
|
<Rating rate={averageRating} className="[&_svg]:size-5" />
|
|
<span className="text-lg font-semibold">{formattedAverage}</span>
|
|
<span className="text-sm text-muted-foreground">
|
|
({totalReviews.toLocaleString("fa-IR")} نظر)
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<Button variant="outline">ثبت نظر</Button>
|
|
</div>
|
|
|
|
<div className="space-y-0">
|
|
{reviews.map((review, index) => (
|
|
<div key={review.id}>
|
|
{index > 0 && <Separator className="my-6" />}
|
|
<Card className="border-0 p-0 shadow-none">
|
|
<CardContent className="space-y-4 p-0">
|
|
<div className="flex items-start justify-between gap-4">
|
|
<div className="flex items-center gap-3">
|
|
<Avatar className="size-10">
|
|
<AvatarImage
|
|
src={review.author.avatar}
|
|
alt={review.author.name}
|
|
/>
|
|
<AvatarFallback>
|
|
{review.author.name
|
|
.split(/\s+/)
|
|
.slice(0, 2)
|
|
.map((part) => part[0])
|
|
.join("")}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="font-medium">
|
|
{review.author.name}
|
|
</span>
|
|
{review.verified && (
|
|
<span
|
|
className="flex items-center gap-1 text-emerald-600"
|
|
title="خرید تأییدشده"
|
|
>
|
|
<BadgeCheck className="size-4" />
|
|
<span className="sr-only">خرید تأییدشده</span>
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
<span>{review.date}</span>
|
|
{review.variant && (
|
|
<>
|
|
<span>·</span>
|
|
<span>{review.variant}</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Rating
|
|
rate={review.rating}
|
|
className="[&_svg]:size-4 [&>div]:size-4"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<h3 className="font-medium">{review.title}</h3>
|
|
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
|
|
{review.content}
|
|
</p>
|
|
</div>
|
|
|
|
{review.images && review.images.length > 0 && (
|
|
<div className="flex gap-2">
|
|
{review.images.map((image, imgIndex) => (
|
|
<div
|
|
key={imgIndex}
|
|
className="size-20 overflow-hidden rounded-lg sm:size-24"
|
|
>
|
|
<AspectRatio ratio={1}>
|
|
<img
|
|
src={image}
|
|
alt={`تصویر نظر ${(imgIndex + 1).toLocaleString("fa-IR")}`}
|
|
className="size-full object-cover"
|
|
/>
|
|
</AspectRatio>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex items-center gap-4">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className={cn(
|
|
"h-8 gap-1.5 text-muted-foreground",
|
|
helpfulClicked.has(review.id) && "text-foreground",
|
|
)}
|
|
onClick={() => handleHelpful(review.id)}
|
|
>
|
|
<ThumbsUp
|
|
className={cn(
|
|
"size-4",
|
|
helpfulClicked.has(review.id) && "fill-current",
|
|
)}
|
|
/>
|
|
مفید بود
|
|
{review.helpful !== undefined && (
|
|
<span>({getHelpfulCount(review)})</span>
|
|
)}
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-8 text-muted-foreground"
|
|
>
|
|
گزارش
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { Reviews3 };
|