testimonials-section.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. "use client"
  2. import { useState, useEffect } from "react"
  3. import { Card, CardContent } from "@/components/ui/card"
  4. import { Button } from "@/components/ui/button"
  5. import { ChevronLeftIcon, ChevronRightIcon, StarIcon } from "lucide-react"
  6. import Image from "next/image"
  7. import { useTranslations } from "next-intl"
  8. interface TestimonialsSectionProps {
  9. locale: string
  10. }
  11. export default function TestimonialsSection({ locale }: TestimonialsSectionProps) {
  12. const [currentIndex, setCurrentIndex] = useState(0)
  13. const [isTransitioning, setIsTransitioning] = useState(false)
  14. const t = useTranslations("testimonials")
  15. const testimonials = [
  16. {
  17. name: "Alex Doe",
  18. role: t("testimonial1.role"),
  19. content: t("testimonial1.content"),
  20. avatar: "/images/Alex Doe.jpeg",
  21. rating: 5,
  22. },
  23. {
  24. name: "Li Jing",
  25. role: t("testimonial2.role"),
  26. content: t("testimonial2.content"),
  27. avatar: "/images/lijing.jpg",
  28. rating: 5,
  29. },
  30. {
  31. name: "Sarah Johnson",
  32. role: t("testimonial3.role"),
  33. content: t("testimonial3.content"),
  34. avatar: "/images/Sarah Johnson.jpg",
  35. rating: 5,
  36. },
  37. ]
  38. const changeTestimonial = (newIndex: number) => {
  39. if (isTransitioning || newIndex === currentIndex) return
  40. setIsTransitioning(true)
  41. setTimeout(() => {
  42. setCurrentIndex(newIndex)
  43. setTimeout(() => {
  44. setIsTransitioning(false)
  45. }, 50)
  46. }, 150)
  47. }
  48. const nextTestimonial = () => {
  49. const newIndex = (currentIndex + 1) % testimonials.length
  50. changeTestimonial(newIndex)
  51. }
  52. const prevTestimonial = () => {
  53. const newIndex = (currentIndex - 1 + testimonials.length) % testimonials.length
  54. changeTestimonial(newIndex)
  55. }
  56. useEffect(() => {
  57. const interval = setInterval(() => {
  58. if (!isTransitioning) {
  59. nextTestimonial()
  60. }
  61. }, 5000)
  62. return () => clearInterval(interval)
  63. }, [currentIndex, isTransitioning])
  64. return (
  65. <section className="py-20">
  66. <div className="container mx-auto px-4 sm:px-6 lg:px-8">
  67. <div className="text-center mb-16">
  68. <h2 className="text-3xl md:text-4xl font-bold mb-4">{t("title")}</h2>
  69. <p className="text-xl text-muted-foreground max-w-3xl mx-auto">{t("subtitle")}</p>
  70. </div>
  71. <div className="max-w-4xl mx-auto relative">
  72. <Card className="bg-background/60 backdrop-blur-sm border-0 shadow-lg">
  73. <CardContent className="p-8 md:p-12">
  74. <div
  75. className={`text-center transition-all duration-300 ease-in-out ${
  76. isTransitioning ? 'opacity-0 transform translate-y-2' : 'opacity-100 transform translate-y-0'
  77. }`}
  78. >
  79. {/* Stars */}
  80. <div className="flex justify-center mb-6">
  81. {[...Array(testimonials[currentIndex].rating)].map((_, i) => (
  82. <StarIcon key={i} className="w-6 h-6 text-yellow-400 fill-current" />
  83. ))}
  84. </div>
  85. {/* Content */}
  86. <blockquote className="text-xl md:text-2xl font-medium text-foreground mb-8 leading-relaxed">
  87. "{testimonials[currentIndex].content}"
  88. </blockquote>
  89. {/* Author */}
  90. <div className="flex items-center justify-center space-x-4">
  91. <Image
  92. src={testimonials[currentIndex].avatar || "/placeholder.svg"}
  93. alt={testimonials[currentIndex].name}
  94. width={60}
  95. height={60}
  96. className="rounded-full object-cover w-[60px] h-[60px] border-2 border-white shadow-lg"
  97. priority={currentIndex === 0}
  98. />
  99. <div className="text-left">
  100. <div className="font-semibold text-lg">{testimonials[currentIndex].name}</div>
  101. <div className="text-muted-foreground">{testimonials[currentIndex].role}</div>
  102. </div>
  103. </div>
  104. </div>
  105. </CardContent>
  106. </Card>
  107. {/* Navigation */}
  108. <div className="flex justify-center items-center mt-8 space-x-4">
  109. <Button
  110. variant="outline"
  111. size="icon"
  112. onClick={prevTestimonial}
  113. className="rounded-full transition-all duration-200 hover:scale-105"
  114. disabled={isTransitioning}
  115. >
  116. <ChevronLeftIcon className="w-5 h-5" />
  117. </Button>
  118. {/* Dots */}
  119. <div className="flex space-x-2">
  120. {testimonials.map((_, index) => (
  121. <button
  122. key={index}
  123. onClick={() => changeTestimonial(index)}
  124. disabled={isTransitioning}
  125. className={`w-3 h-3 rounded-full transition-all duration-300 hover:scale-110 ${
  126. index === currentIndex ? "bg-primary" : "bg-muted-foreground/30"
  127. }`}
  128. />
  129. ))}
  130. </div>
  131. <Button
  132. variant="outline"
  133. size="icon"
  134. onClick={nextTestimonial}
  135. className="rounded-full transition-all duration-200 hover:scale-105"
  136. disabled={isTransitioning}
  137. >
  138. <ChevronRightIcon className="w-5 h-5" />
  139. </Button>
  140. </div>
  141. </div>
  142. </div>
  143. </section>
  144. )
  145. }