Files
reui-blocks/blocks/reviews2.tsx
T

228 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { BadgeCheck } from "lucide-react";
import { Rating } from "@/components/shadcnblocks/rating";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Card, CardContent } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import { cn } from "@/lib/utils";
interface Review {
id: string;
rating: number;
title: string;
content: string;
author: {
name: string;
avatar?: string;
};
date: string;
verified?: boolean;
}
const DEFAULT_REVIEWS: Review[] = [
{
id: "1",
rating: 5,
title: "فراتر از انتظارم بود",
content:
"اول کمی تردید داشتم، ولی این محصول واقعاً جواب داد. کیفیت ساخت عالی است و زودتر از موعد به دستم رسید. اگر هنوز مردد هستید، حتماً پیشنهاد می‌کنم امتحان کنید.",
author: {
name: "سارا احمدی",
avatar: "/block-media/shadcnblocks/block/avatar-1.webp",
},
date: "۲۰ آذر ۱۴۰۳",
verified: true,
},
{
id: "2",
rating: 4,
title: "ارزش خرید بالا",
content:
"به‌طور کلی محصول خوبی است و دقیقاً همان کاری را می‌کند که باید. یک ستاره کم کردم چون بسته‌بندی می‌توانست بهتر باشد، ولی خود محصول عالی است.",
author: {
name: "مازیار رضایی",
avatar: "/block-media/shadcnblocks/block/avatar-2.webp",
},
date: "۱۸ آذر ۱۴۰۳",
verified: true,
},
{
id: "3",
rating: 5,
title: "عالی برای استفاده روزمره",
content:
"یک ماه است هر روز از آن استفاده می‌کنم و هنوز مثل روز اول به نظر می‌رسد و درست کار می‌کند. با این قیمت، کیفیت ساخت واقعاً قابل تحسین است. برای خواهرم هم یکی خریدم.",
author: {
name: "نیلوفر حسینی",
avatar: "/block-media/shadcnblocks/block/avatar-3.webp",
},
date: "۱۵ آذر ۱۴۰۳",
verified: true,
},
{
id: "4",
rating: 4,
title: "خوب، ولی کامل نیست",
content:
"محصول خوب است و درست کار می‌کند. تنها نکته کوچک این است که رنگ کمی با عکس‌ها فرق دارد، ولی در مجموع خرید رضایت‌بخشی بود.",
author: {
name: "امیر محمدی",
},
date: "۱۲ آذر ۱۴۰۳",
verified: false,
},
{
id: "5",
rating: 5,
title: "بهترین خرید امسالم",
content:
"واقعاً عاشقش شدم! به جزئیات با دقت زیادی توجه شده. وقتی سوال داشتم، پشتیبانی هم بسیار مفید بود. از هر نظر پنج ستاره.",
author: {
name: "رؤیا کاظمی",
avatar: "/block-media/shadcnblocks/block/avatar-1.webp",
},
date: "۸ آذر ۱۴۰۳",
verified: true,
},
{
id: "6",
rating: 3,
title: "قابل قبول، ولی انتظار بیشتری داشتم",
content:
"با توجه به قیمت، محصول معمولی است؛ چیز خاصی نیست ولی کارش را انجام می‌دهد. ارسال سریع بود که نکته مثبتش بود.",
author: {
name: "پارسا موسوی",
},
date: "۵ آذر ۱۴۰۳",
verified: true,
},
];
interface Reviews2Props {
reviews?: Review[];
title?: string;
className?: string;
}
const Reviews2 = ({
reviews = DEFAULT_REVIEWS,
title = "نظرات مشتریان",
className,
}: Reviews2Props) => {
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 ratingCounts = [5, 4, 3, 2, 1].map((star) => ({
star,
count: reviews.filter((r) => r.rating === star).length,
percentage:
(reviews.filter((r) => r.rating === star).length / totalReviews) * 100,
}));
return (
<section
dir="rtl"
lang="fa"
className={cn("py-16 md:py-24", className)}
>
<div className="container max-w-5xl">
<h2 className="mb-8 text-xl font-semibold tracking-tight md:text-3xl">
{title}
</h2>
<div className="grid gap-8 lg:grid-cols-[280px_1fr] lg:gap-12">
<div className="space-y-6">
<div className="text-center lg:text-start">
<div className="text-2xll font-bold">{formattedAverage}</div>
<Rating
rate={averageRating}
className="mt-2 justify-center lg:justify-start [&_svg]:size-5"
/>
<p className="mt-1 text-sm text-muted-foreground">
بر اساس {totalReviews.toLocaleString("fa-IR")} نظر
</p>
</div>
<div className="space-y-2">
{ratingCounts.map(({ star, count, percentage }) => (
<div key={star} className="flex items-center gap-3">
<span className="w-10 shrink-0 text-sm text-muted-foreground">
{star.toLocaleString("fa-IR")} ★
</span>
<Progress value={percentage} className="h-2 flex-1" />
<span className="w-8 shrink-0 text-end text-sm text-muted-foreground">
{count.toLocaleString("fa-IR")}
</span>
</div>
))}
</div>
</div>
<div className="grid gap-4 sm:grid-cols-2">
{reviews.map((review) => (
<Card key={review.id} className="gap-0 p-0 shadow-none">
<CardContent className="p-5">
<div className="space-y-3">
<div className="flex items-start justify-between gap-2">
<div className="flex items-center gap-2.5">
<Avatar className="size-9">
<AvatarImage
src={review.author.avatar}
alt={review.author.name}
/>
<AvatarFallback className="text-xs">
{review.author.name
.split(/\s+/)
.slice(0, 2)
.map((part) => part[0])
.join("")}
</AvatarFallback>
</Avatar>
<div>
<div className="flex items-center gap-1.5">
<span className="text-sm font-medium">
{review.author.name}
</span>
{review.verified && (
<BadgeCheck
className="size-4 text-emerald-600"
aria-label="خرید تأییدشده"
/>
)}
</div>
<p className="text-xs text-muted-foreground">
{review.date}
</p>
</div>
</div>
<Rating
rate={review.rating}
className="[&_svg]:size-3.5 [&>div]:size-3.5"
/>
</div>
<div>
<h3 className="font-medium">{review.title}</h3>
<p className="mt-1.5 line-clamp-3 text-sm leading-relaxed text-muted-foreground">
{review.content}
</p>
</div>
</div>
</CardContent>
</Card>
))}
</div>
</div>
</div>
</section>
);
};
export { Reviews2 };