"use client" import { Card, CardContent, CardHeader, CardFooter } from "@/components/ui/card" import { Avatar, AvatarFallback } from "@/components/ui/avatar" import { Star, Quote, ChevronLeft, ChevronRight } from "lucide-react" import { useRef, useState, useEffect, useCallback } from "react" type Testimonial = { id: string clientName: string company: string | null content: string rating: number imageUrl: string | null } /* Static fallback used when the API is unreachable (build-time, etc.) */ const fallbackTestimonials: Testimonial[] = [ { id: "fallback-1", clientName: "Sarah Johnson", company: "TechFlow", content: "NexaFlow transformed our online presence. Their attention to detail and technical expertise is unmatched. They delivered exactly what we needed.", rating: 5, imageUrl: null, }, { id: "fallback-2", clientName: "Michael Chen", company: "StartupX", content: "The team delivered our MVP ahead of schedule and it looks fantastic. They truly understood our vision and brought it to life.", rating: 5, imageUrl: null, }, { id: "fallback-3", clientName: "Emily Davis", company: "Creative Studio", content: "Professional, responsive, and incredibly talented. They truly understand modern web design and the latest technologies.", rating: 5, imageUrl: null, }, { id: "fallback-4", clientName: "David Wilson", company: "InnovateCo", content: "Working with NexaFlow was a game-changer for our business. The website they built exceeded our expectations in every way.", rating: 5, imageUrl: null, }, { id: "fallback-5", clientName: "Lisa Anderson", company: "GrowthHub", content: "From concept to launch, the process was smooth and professional. Our new app has received amazing feedback from users.", rating: 5, imageUrl: null, }, ] function getInitials(name: string): string { return name .split(" ") .map((w) => w[0]) .join("") .toUpperCase() .slice(0, 2) } function TestimonialCard({ testimonial, isActive }: { testimonial: Testimonial; isActive: boolean }) { return ( {/* Animated gradient background */} {/* Quote decoration */} {[...Array(testimonial.rating)].map((_, j) => ( ))} “{testimonial.content}” {getInitials(testimonial.clientName)} {testimonial.clientName} {testimonial.company && ( {testimonial.company} )} {/* Bottom accent line */} {/* Shine effect */} ) } export function TestimonialsCarousel() { const [testimonials, setTestimonials] = useState(fallbackTestimonials) const [currentIndex, setCurrentIndex] = useState(0) const [isVisible, setIsVisible] = useState(false) const [isPaused, setIsPaused] = useState(false) const sectionRef = useRef(null) // Fetch testimonials useEffect(() => { const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000/api" fetch(`${apiUrl}/testimonials`) .then((res) => res.json()) .then((data: Testimonial[]) => { if (data.length > 0) setTestimonials(data) }) .catch(() => { // Use fallback testimonials }) }, []) // Intersection observer for reveal animation useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setIsVisible(true) } }, { threshold: 0.1 } ) if (sectionRef.current) { observer.observe(sectionRef.current) } return () => observer.disconnect() }, []) // Auto-scroll carousel useEffect(() => { if (isPaused || testimonials.length <= 3) return const interval = setInterval(() => { setCurrentIndex((prev) => (prev + 1) % testimonials.length) }, 5000) return () => clearInterval(interval) }, [isPaused, testimonials.length]) const goToPrevious = useCallback(() => { setCurrentIndex((prev) => (prev - 1 + testimonials.length) % testimonials.length) }, [testimonials.length]) const goToNext = useCallback(() => { setCurrentIndex((prev) => (prev + 1) % testimonials.length) }, [testimonials.length]) const goToSlide = useCallback((index: number) => { setCurrentIndex(index) }, []) // Get visible testimonials for carousel const getVisibleTestimonials = () => { const visible = [] for (let i = 0; i < 3; i++) { const index = (currentIndex + i) % testimonials.length visible.push({ testimonial: testimonials[index], position: i }) } return visible } return ( setIsPaused(true)} onMouseLeave={() => setIsPaused(false)} > {/* Background decorations */} {/* Floating quote icons */} {[...Array(6)].map((_, i) => ( ))} {/* Section header */} Testimonials What Our Clients Say Don't just take our word for it — hear from the businesses we've helped succeed. {/* Carousel */} {/* Navigation buttons */} {/* Cards container */} {getVisibleTestimonials().map(({ testimonial, position }) => ( ))} {/* Dots navigation */} {testimonials.slice(0, 6).map((_, index) => ( goToSlide(index)} className={`w-2 h-2 rounded-full transition-all duration-300 ${ index === currentIndex ? "w-8 bg-primary" : "bg-muted-foreground/30 hover:bg-muted-foreground/50" }`} aria-label={`Go to testimonial ${index + 1}`} /> ))} {/* Trust indicator */} Trusted by 50+ satisfied clients ) }
“{testimonial.content}”
{testimonial.clientName}
{testimonial.company}
Don't just take our word for it — hear from the businesses we've helped succeed.
Trusted by 50+ satisfied clients