209 lines
8.4 KiB
TypeScript

'use client';
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: 'Sarah Johnson',
role: 'Marketing Director',
company: 'TechFlow Inc.',
text: 'SocialBuddy has completely transformed how we manage our social media. The scheduling features and analytics are game-changers.',
image: '👩‍💼'
},
{
name: 'Michael Chen',
role: 'Content Creator',
company: 'Digital Nomad',
text: 'As a solo creator managing multiple platforms, SocialBuddy saves me hours every week. The AI content suggestions are incredibly helpful.',
image: '👨‍💻'
},
{
name: 'Emily Rodriguez',
role: 'Social Media Manager',
company: 'BrandBoost Agency',
text: 'Managing 20+ client accounts used to be overwhelming. SocialBuddy\'s team collaboration features have streamlined our process.',
image: '👩‍🎨'
},
{
name: 'David Park',
role: 'CEO',
company: 'StartupHub',
text: 'The analytics and reporting features are outstanding. We can now make data-driven decisions about our social media strategy.',
image: '👨‍💼'
},
{
name: 'Lisa Thompson',
role: 'Influencer',
company: 'Lifestyle & Travel',
text: 'I love how intuitive SocialBuddy is! The content calendar helps me plan my posts weeks in advance.',
image: '👩‍🦰'
},
{
name: 'James Wilson',
role: 'E-commerce Manager',
company: 'ShopSmart',
text: 'SocialBuddy has helped us maintain a consistent brand presence across all platforms. The bulk scheduling feature is amazing.',
image: '👨‍🏫'
}
];
// 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 we are at the end of the original list (showing the first clone)
// We animate TO the clone.
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;
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}>
Discover your <br />
entrepreneurial potential
</h2>
<p className={styles.cardDescription}>
Join countless marketers and influencers who have transformed their social media presence with SocialBuddy.
Success is just a click away.
</p>
<div className={styles.miniStats}>
<div className={styles.statBadge}>
<span className={styles.statIcon}>👥</span>
<div>
<strong>10,000+</strong>
<span>Happy Users</span>
</div>
</div>
<div className={styles.statBadge}>
<span className={styles.statIcon}></span>
<div>
<strong>4.9/5</strong>
<span>Ratings</span>
</div>
</div>
</div>
<button className={styles.readMoreBtn}>
Read Success 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}>{t.image}</div>
<div className={styles.userInfo}>
<h4>{t.name}</h4>
<span className={styles.userCompany}>{t.company}</span>
</div>
<div className={styles.rating}></div>
</div>
<h5 className={styles.reviewTitle}>Amazing Experience!</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>
);
}