70 lines
2.5 KiB
TypeScript
70 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import styles from "@/app/features/[slug]/feature-page.module.css";
|
|
import SafeImage from "./SafeImage";
|
|
import { Star } from "lucide-react";
|
|
|
|
interface Testimonial {
|
|
quote: string;
|
|
author: string;
|
|
role: string;
|
|
rating: number;
|
|
image: string;
|
|
}
|
|
|
|
export default function TestimonialSlider({ testimonials }: { testimonials: Testimonial[] }) {
|
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
|
|
useEffect(() => {
|
|
const interval = setInterval(() => {
|
|
setCurrentIndex((prev) => (prev + 1) % testimonials.length);
|
|
}, 5000); // 5 seconds per slide
|
|
return () => clearInterval(interval);
|
|
}, [testimonials.length]);
|
|
|
|
const current = testimonials[currentIndex];
|
|
|
|
return (
|
|
<div className={styles.testimonialSliderContainer}>
|
|
<div className={styles.testSliderContent}>
|
|
<div className={styles.testStars}>
|
|
{[...Array(5)].map((_, i) => (
|
|
<Star
|
|
key={i}
|
|
fill={i < current.rating ? "#FFB800" : "none"}
|
|
stroke={i < current.rating ? "#FFB800" : "#CBD5E1"}
|
|
size={20}
|
|
/>
|
|
))}
|
|
</div>
|
|
<blockquote className={styles.testQuoteFade}>
|
|
"{current.quote}"
|
|
</blockquote>
|
|
<div className={styles.testAuthorBlock}>
|
|
<SafeImage
|
|
src={current.image}
|
|
alt={current.author}
|
|
className={styles.testAuthorAvatar}
|
|
fallbackSrc={`https://placehold.co/100x100/png?text=${current.author.charAt(0)}`}
|
|
/>
|
|
<div className={styles.testAuthorInfo}>
|
|
<h4>{current.author}</h4>
|
|
<span>{current.role}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className={styles.sliderDots}>
|
|
{testimonials.map((_, idx) => (
|
|
<button
|
|
key={idx}
|
|
className={`${styles.dot} ${idx === currentIndex ? styles.activeDot : ""}`}
|
|
onClick={() => setCurrentIndex(idx)}
|
|
aria-label={`Go to slide ${idx + 1}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|