2025-12-18 18:47:56 +05:30

219 lines
9.2 KiB
TypeScript

'use client';
import Image from 'next/image';
import { useState, useEffect, useRef } from 'react';
import styles from './Testimonials.module.css';
export default function Testimonials() {
const [activeIndex, setActiveIndex] = useState(0);
const [isResetting, setIsResetting] = useState(false);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const testimonials = [
{
name: 'Rahul Mehta',
role: 'Growth Manager',
reviewTitle: 'Highly Impressive Results!',
text: 'The performance insights are extremely clear. We can now adjust campaigns quickly and make confident marketing decisions.',
image: '/images/home/testimonial-1.webp'
},
{
name: 'Emily Carter',
role: 'Content Strategist',
reviewTitle: 'Smooth and Reliable Platform!',
text: 'Managing posts feels effortless now. Planning content ahead of time has significantly improved our publishing consistency.',
image: '/images/home/testimonial-2.webp'
},
{
name: 'Arjun Nair',
role: 'Digital Consultant',
reviewTitle: 'Excellent Tool for Teams!',
text: 'Collaboration is seamless and approvals are faster. It has simplified how we handle multiple client accounts.',
image: '/images/home/testimonial-3.webp'
},
{
name: 'Sophia Williams',
role: 'Brand Manager',
reviewTitle: 'Well Designed and Effective!',
text: 'The interface is clean and easy to navigate. Tracking engagement across platforms has never been this simple.',
image: '/images/home/testimonial-4.webp'
},
{
name: 'Daniel Rodrigues',
role: ' Marketing Lead',
reviewTitle: 'Strong Value for Growth!',
text: 'The reporting features save us hours every week. Insights are easy to understand and useful for strategy planning.',
image: '/images/home/testimonial-5.webp'
},
];
// Clone the first few items to create the infinite illusion
// We need enough clones to fill the visible area. 4 is safe.
const extendedTestimonials = [...testimonials, ...testimonials.slice(0, 4)];
const totalOriginal = testimonials.length;
useEffect(() => {
const interval = setInterval(() => {
handleNext();
}, 3000);
return () => clearInterval(interval);
}, [activeIndex, isResetting]); // Re-create interval if state changes to avoid stale closures, though functional update handles it.
const handleNext = () => {
if (isResetting) return;
setActiveIndex((prev) => {
// If already at totalOriginal, wait for reset effect
if (prev >= totalOriginal) return prev;
return prev + 1;
});
};
// Handle seamless reset
useEffect(() => {
if (activeIndex === totalOriginal) {
// We have just slid TO the first cloned item (visually identical to index 0)
// Wait for transition to finish, then snap back to real 0
timeoutRef.current = setTimeout(() => {
setIsResetting(true); // Disable transition
setActiveIndex(0); // Jump to 0
// Re-enable transition after a brief moment to allow DOM update
requestAnimationFrame(() => {
requestAnimationFrame(() => {
setIsResetting(false);
});
});
}, 500); // Must match CSS transition duration
}
return () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
};
}, [activeIndex, totalOriginal]);
const slideNext = () => {
if (isResetting) return;
if (activeIndex >= totalOriginal) return; // Wait for reset
setActiveIndex(prev => prev + 1);
};
const slidePrev = () => {
if (isResetting) return;
if (activeIndex === 0) {
// Jump to end clone without transition, then slide back
setIsResetting(true);
setActiveIndex(totalOriginal);
requestAnimationFrame(() => {
requestAnimationFrame(() => {
setIsResetting(false);
setActiveIndex(totalOriginal - 1);
});
});
} else {
setActiveIndex(prev => prev - 1);
}
};
return (
<section className={styles.testimonialSection} id="testimonials">
<div className={styles.layoutContainer}>
{/* Left Side Static Card - High Z-Index */}
<div className={styles.leftCard}>
<div className={styles.cardContent}>
<h2 className={styles.cardTitle}>
Unlock Your Growth Opportunities
</h2>
<p className={styles.cardDescription}>
Connect with a global community of marketers and creators who have elevated their digital presence using Social Buddy. Real progress, real outcomes - getting started takes only a moment.
</p>
<div className={styles.miniStats}>
<div className={styles.statBadge}>
<Image
src="/images/home/active-members.webp"
alt="Unified Message Center"
width={29}
height={29}
/>
<div>
<strong>10,000+</strong>
<span>Active Members</span>
</div>
</div>
<div className={styles.statBadge}>
<Image
src="/images/home/user-satisfaction.webp"
alt="Unified Message Center"
width={29}
height={29}
/>
<div>
<strong>4.9/5</strong>
<span>User Satisfaction</span>
</div>
</div>
</div>
<button className={styles.readMoreBtn}>
View Customer Stories
</button>
</div>
</div>
{/* Right Side Slider - Lower Z-Index, moves behind Left Card */}
<div className={styles.sliderContainer}>
<div
className={styles.sliderTrack}
style={{
transform: `translateX(calc(-${activeIndex * 352}px))`,
transition: isResetting ? 'none' : 'transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1)'
}}
>
{extendedTestimonials.map((t, i) => (
<div key={i} className={styles.sliderCard}>
<div className={styles.cardHeader}>
<div className={styles.userImage}>
<Image
src={t.image}
alt="User"
width={42}
height={48}
/>
</div>
<div className={styles.userInfo}>
<h4>{t.name}</h4>
<span className={styles.userCompany}>{t.role}</span>
<div className={styles.rating}></div>
</div>
</div>
<h5 className={styles.reviewTitle}>{t.reviewTitle}</h5>
<p className={styles.reviewText}>{t.text}</p>
</div>
))}
</div>
{/* Navigation Arrows */}
<div className={styles.sliderNav}>
<button
className={styles.navBtn}
onClick={slidePrev}
aria-label="Previous testimonial"
>
</button>
<button
className={styles.navBtn}
onClick={slideNext}
aria-label="Next testimonial"
>
</button>
</div>
</div>
</div>
</section>
);
}