200 lines
7.0 KiB
TypeScript
200 lines
7.0 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:
|
|
"CRM یکپارچه، مسیریابی لید هوشمند و داشبورد قیف شفاف دارد. درک عمیق از عملیات فروش برای مقیاسدهی درآمد ما حیاتی بوده.",
|
|
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:
|
|
"از انتظارات فراتر رفت؛ یکپارچهسازی CRM روان و پشتیبانی فروش استثنایی. نمرهدهی لید و گزارش قیف در مسیر رشد ما نقش کلیدی داشته.",
|
|
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 };
|