176 lines
6.7 KiB
TypeScript
176 lines
6.7 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useRef, useEffect } from "react";
|
|
import { motion } from "framer-motion";
|
|
import { Quote, ChevronLeft, ChevronRight } from "lucide-react";
|
|
import Image from "next/image";
|
|
import { getCookie } from "cookies-next";
|
|
|
|
interface TestimonialData {
|
|
name: string;
|
|
position: string;
|
|
image: string;
|
|
quote: string;
|
|
}
|
|
|
|
interface TestimonialsDict {
|
|
title: string;
|
|
description: string;
|
|
data: TestimonialData[];
|
|
}
|
|
|
|
interface TestimonialsProps {
|
|
dict: {
|
|
website: {
|
|
homePage: {
|
|
testimonials: TestimonialsDict;
|
|
};
|
|
};
|
|
};
|
|
}
|
|
|
|
export default function Testimonials({ dict }: TestimonialsProps) {
|
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
const [locale, setLocale] = useState("en");
|
|
|
|
useEffect(() => {
|
|
const sl = getCookie("selectedLocale");
|
|
const dl = getCookie("defaultLocale");
|
|
setLocale(sl || dl || "en");
|
|
}, []);
|
|
|
|
const isRTL = locale === "fa";
|
|
const tData = dict.website.homePage.testimonials.data || [];
|
|
const maxIndex = Math.max(0, tData.length - 3);
|
|
|
|
const scrollTo = (index: number) => {
|
|
const idx = Math.max(0, Math.min(index, maxIndex));
|
|
setCurrentIndex(idx);
|
|
scrollRef.current?.children[idx]?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "start" });
|
|
};
|
|
|
|
const handlePrev = () => scrollTo(currentIndex - 1);
|
|
const handleNext = () => scrollTo(currentIndex + 1);
|
|
|
|
return (
|
|
<section
|
|
className="relative flex w-full max-w-[1200px] flex-col items-center py-20"
|
|
id="testimonials"
|
|
>
|
|
<div className="text-center mb-16 space-y-4">
|
|
<motion.h2
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6 }}
|
|
viewport={{ once: true }}
|
|
className="text-4xl md:text-5xl font-bold text-foreground tracking-tight"
|
|
>
|
|
{dict.website.homePage.testimonials.title}
|
|
</motion.h2>
|
|
<motion.p
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6, delay: 0.2 }}
|
|
viewport={{ once: true }}
|
|
className="text-lg text-muted-foreground max-w-2xl mx-auto text-balance"
|
|
>
|
|
{dict.website.homePage.testimonials.description}
|
|
</motion.p>
|
|
</div>
|
|
|
|
<div className="relative w-full">
|
|
<div
|
|
ref={scrollRef}
|
|
className="flex gap-8 overflow-x-auto snap-x snap-mandatory scrollbar-none pb-4"
|
|
style={{ scrollbarWidth: "none", msOverflowStyle: "none" }}
|
|
>
|
|
{tData.map((testimonial, index) => (
|
|
<div
|
|
key={index}
|
|
className="w-full md:w-[calc(50%-1rem)] lg:w-[calc(33.333%-1.1rem)] snap-start flex-shrink-0"
|
|
>
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 30 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: index * 0.1 }}
|
|
viewport={{ once: true }}
|
|
whileHover={{ y: -10 }}
|
|
className="relative p-6 md:p-8 rounded-3xl glass-panel flex flex-col h-full overflow-visible"
|
|
>
|
|
<div className="absolute -top-6 -start-6 w-12 h-12 bg-primary rounded-full flex items-center justify-center text-white shadow-lg">
|
|
<Quote className="w-6 h-6" />
|
|
</div>
|
|
|
|
<div className="flex-grow pt-2 overflow-visible">
|
|
<p className="w-full min-w-0 normal-case whitespace-normal break-words tracking-normal block mb-8 italic text-sm md:text-base text-start px-8 md:px-14 leading-relaxed">
|
|
"{testimonial.quote}"
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4 mt-auto pt-6 border-t border-white/10">
|
|
<div className="relative w-12 h-12 overflow-hidden rounded-full border-2 border-primary/50 flex-shrink-0">
|
|
<Image
|
|
src={testimonial.image}
|
|
alt={testimonial.name}
|
|
fill
|
|
className="object-cover"
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col min-w-0">
|
|
<h5 className="font-bold text-foreground leading-none mb-1 truncate">
|
|
{testimonial.name}
|
|
</h5>
|
|
<p className="text-xs text-muted-foreground leading-tight truncate">
|
|
{testimonial.position}
|
|
</p>
|
|
</div>
|
|
<div className="ms-auto bg-primary/10 text-primary text-[10px] font-bold px-2 py-1 rounded-md border border-primary/20 flex-shrink-0">
|
|
Verified
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{tData.length > 3 && (
|
|
<>
|
|
<button
|
|
onClick={isRTL ? handleNext : handlePrev}
|
|
disabled={isRTL ? currentIndex >= maxIndex : currentIndex === 0}
|
|
className="absolute -left-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-background border border-border shadow-lg flex items-center justify-center text-foreground hover:text-primary transition-colors disabled:opacity-30 disabled:cursor-not-allowed z-10"
|
|
aria-label="Previous testimonials"
|
|
>
|
|
<ChevronLeft className={`w-5 h-5 ${isRTL ? "rotate-180" : ""}`} />
|
|
</button>
|
|
<button
|
|
onClick={isRTL ? handlePrev : handleNext}
|
|
disabled={isRTL ? currentIndex === 0 : currentIndex >= maxIndex}
|
|
className="absolute -right-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-background border border-border shadow-lg flex items-center justify-center text-foreground hover:text-primary transition-colors disabled:opacity-30 disabled:cursor-not-allowed z-10"
|
|
aria-label="Next testimonials"
|
|
>
|
|
<ChevronRight className={`w-5 h-5 ${isRTL ? "rotate-180" : ""}`} />
|
|
</button>
|
|
|
|
<div className="flex items-center justify-center gap-2 mt-8">
|
|
{Array.from({ length: maxIndex + 1 }).map((_, i) => (
|
|
<button
|
|
key={i}
|
|
onClick={() => scrollTo(i)}
|
|
className={`w-2.5 h-2.5 rounded-full transition-all duration-300 ${
|
|
i === currentIndex
|
|
? "bg-primary w-6"
|
|
: "bg-muted-foreground/30 hover:bg-muted-foreground/50"
|
|
}`}
|
|
aria-label={`Go to testimonial group ${i + 1}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|