Files
reui-blocks/blocks/testimonial26.tsx
T

200 lines
7.1 KiB
TypeScript

"use client";
import { type SVGProps, useEffect, useId, useState } from "react";
import type { CarouselApi } from "@/components/ui/carousel";
import {
Carousel,
CarouselContent,
CarouselItem,
} from "@/components/ui/carousel";
import { cn } from "@/lib/utils";
const TESTIMONIALS = [
{
id: 1,
quote:
"سامانه مدیریت ارتباط با مشتری یکپارچه، مسیریابی لید هوشمند و داشبورد قیف شفاف دارد. درک عمیق از عملیات فروش برای مقیاس‌دهی درآمد ما حیاتی بوده.",
author: "علی رحمانی",
role: "بنیان‌گذار، میزبانی ابری",
company: "Vercel",
logo: "/block-media/shadcnblocks/block/logos/vercel-wordmark.svg",
},
{
id: 2,
quote:
"اتوماسیون بازاریابی و انتقال یکپارچه به فروش، عملیات درآمد ما را متحول کرد. انعطاف و سرعت پلتفرم به ما امکان داده قیف را کارآمد مقیاس دهیم.",
author: "مازیار رضایی",
role: "مدیر عملیات درآمد، Astro",
company: "Astro",
logo: "/block-media/shadcnblocks/block/logos/astro-wordmark.svg",
},
{
id: 3,
quote:
"از انتظارات فراتر رفت؛ یکپارچه‌سازی سامانه مدیریت ارتباط با مشتری روان و پشتیبانی فروش استثنایی. نمره‌دهی لید و گزارش قیف در مسیر رشد ما نقش کلیدی داشته.",
author: "سارا احمدی",
role: "مدیر فروش، طراحی رابط",
company: "Figma",
logo: "/block-media/shadcnblocks/block/logos/figma-wordmark.svg",
},
{
id: 4,
quote:
"نحوه مدیریت حساب‌های کلیدی و تمدید قرارداد را متحول کرد. دید بلادرنگ از فرصت‌ها، انعطاف ساخت فرایندهای فروش سفارشی را به ما داده.",
author: "کاوه مرادی",
role: "مدیرعامل، پایگاه داده ابری",
company: "Supabase",
logo: "/block-media/shadcnblocks/block/logos/supabase-wordmark.svg",
},
{
id: 5,
quote:
"ترکیب اتوماسیون کمپین و داشبورد فروش بلادرنگ، هماهنگی بازاریابی و فروش را متحول کرد. سرعت پاسخ به لید و دقت پیش‌بینی درآمد بی‌رقیب است.",
author: "نیلوفر حسینی",
role: "مدیر بازاریابی، ری یو آی",
company: "Shadcn/ui",
logo: "/block-media/shadcnblocks/block/logos/shadcn-ui-wordmark.svg",
},
];
interface Testimonial26Props {
className?: string;
}
const Testimonial26 = ({ className }: Testimonial26Props) => {
const [api, setApi] = useState<CarouselApi>();
const [current, setCurrent] = useState(0);
useEffect(() => {
if (!api) {
return;
}
api.on("select", () => {
setCurrent(api.selectedScrollSnap());
});
}, [api]);
return (
<section dir="rtl" lang="fa" className={cn("relative py-32", className)}>
<div className="container max-w-4xl">
<Carousel
opts={{
align: "center",
loop: true,
}}
setApi={setApi}
>
<CarouselContent>
{TESTIMONIALS.map((testimonial) => (
<CarouselItem
key={testimonial.id}
className="flex cursor-grab flex-col gap-6 lg:gap-8"
>
<blockquote className="font-hubot-sans pointer-events-none text-xl leading-relaxed font-semibold tracking-tight text-balance select-none md:text-3xl lg:text-xl">
{testimonial.quote}
</blockquote>
<div className="flex items-center gap-4">
{testimonial.logo && (
<div className="relative h-8 w-24">
<img
src={testimonial.logo}
alt={`لوگوی ${testimonial.company}`}
className="object-contain dark:invert"
aria-hidden="true"
/>
</div>
)}
<div className="h-8 w-px bg-border" aria-hidden="true" />
<div>
<cite className="font-semibold not-italic">
{testimonial.author}
</cite>
<p className="text-sm font-medium text-muted-foreground">
{testimonial.role}
</p>
</div>
</div>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
<div
className="mt-10 flex gap-2 lg:mt-16"
role="group"
aria-label="ناوبری نظرات"
>
{TESTIMONIALS.map((_, index) => (
<button
key={index}
type="button"
className={cn(
"size-4 cursor-pointer rounded-full transition-colors duration-300",
index === current
? "bg-muted-foreground"
: "bg-muted-foreground/20 hover:bg-muted-foreground/50",
)}
onClick={() => api?.scrollTo(index)}
aria-label={`رفتن به نظر ${index + 1}`}
aria-current={index === current ? "true" : undefined}
/>
))}
</div>
</div>
<div className="absolute inset-x-0 top-16 isolate z-[-1] h-[300px] md:top-28 lg:top-32">
<div className="absolute inset-x-0 bottom-0 z-10 h-40 bg-linear-to-t from-background via-background/40 to-transparent" />
<div className="absolute inset-x-0 top-0 z-10 h-40 bg-linear-to-b from-background via-background/40 to-transparent" />
<PlusSigns className="size-full text-foreground/5" />
</div>
</section>
);
};
interface PlusSignsProps extends SVGProps<SVGSVGElement> {
className?: string;
}
const PlusSigns = ({ className, ...props }: PlusSignsProps) => {
const GAP = 18;
const STROKE_WIDTH = 1;
const PLUS_SIZE = 4;
const id = useId();
const patternId = `plus-pattern-${id}`;
return (
<svg width={GAP * 2} height={GAP * 2} className={className} {...props}>
<defs>
<pattern
id={patternId}
x="0"
y="0"
width={GAP}
height={GAP}
patternUnits="userSpaceOnUse"
>
<line
x1={GAP / 2}
y1={(GAP - PLUS_SIZE) / 2}
x2={GAP / 2}
y2={(GAP + PLUS_SIZE) / 2}
stroke="currentColor"
strokeWidth={STROKE_WIDTH}
/>
<line
x1={(GAP - PLUS_SIZE) / 2}
y1={GAP / 2}
x2={(GAP + PLUS_SIZE) / 2}
y2={GAP / 2}
stroke="currentColor"
strokeWidth={STROKE_WIDTH}
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill={`url(#${patternId})`} />
</svg>
);
};
export { Testimonial26 };