mirror of
https://github.com/Techtonic-Fault/homepage.git
synced 2026-01-23 05:26:30 +00:00
179 lines
7.4 KiB
TypeScript
179 lines
7.4 KiB
TypeScript
"use client"
|
|
|
|
import { useMemo, useState } from "react"
|
|
import { Card, CardContent } from "@/components/ui/card"
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
|
import { Button } from "@/components/ui/button"
|
|
import { ChevronLeft, ChevronRight, Quote } from "lucide-react"
|
|
import { motion, AnimatePresence } from "framer-motion"
|
|
import { useWindowSize } from "@uidotdev/usehooks"
|
|
import { Animate } from "@/components/animations/animate"
|
|
|
|
interface Testimonial {
|
|
quote: string;
|
|
author: string;
|
|
role: string;
|
|
avatar: string;
|
|
}
|
|
|
|
const testimonials: Testimonial[] = [
|
|
// {
|
|
// quote:
|
|
// "Working with TECHTONIC FAULT was a game-changer for our business. They delivered a custom e-commerce solution that increased our online sales by 40% in the first quarter.",
|
|
// author: "Sarah Johnson",
|
|
// role: "CEO, FashionForward",
|
|
// avatar: "/placeholder.svg?height=40&width=40",
|
|
// },
|
|
// {
|
|
// quote:
|
|
// "The team at TECHTONIC FAULT understood our complex requirements and delivered a solution that exceeded our expectations. Their attention to detail and commitment to quality is unmatched.",
|
|
// author: "Michael Chen",
|
|
// role: "CTO, HealthTech Solutions",
|
|
// avatar: "/placeholder.svg?height=40&width=40",
|
|
// },
|
|
// {
|
|
// quote:
|
|
// "We've worked with several development firms in the past, but none have matched the level of expertise and dedication that TECHTONIC FAULT brings to the table.",
|
|
// author: "Jessica Williams",
|
|
// role: "Product Manager, FinanceApp",
|
|
// avatar: "/placeholder.svg?height=40&width=40",
|
|
// },
|
|
// {
|
|
// quote:
|
|
// "The mobile app TECHTONIC FAULT developed for us has transformed how we engage with our customers. User-friendly, reliable, and exactly what we needed.",
|
|
// author: "David Rodriguez",
|
|
// role: "Marketing Director, TravelEase",
|
|
// avatar: "/placeholder.svg?height=40&width=40",
|
|
// },
|
|
// {
|
|
// quote:
|
|
// "From concept to deployment, TECHTONIC FAULT guided us through every step of the process with professionalism and expertise. I couldn't recommend them more highly.",
|
|
// author: "Emily Thompson",
|
|
// role: "Founder, EdTech Innovations",
|
|
// avatar: "/placeholder.svg?height=40&width=40",
|
|
// },
|
|
]
|
|
|
|
export function TestimonialsSection() {
|
|
if (!testimonials.length) return <></>;
|
|
return (
|
|
<section className="w-full py-12 md:py-24 lg:py-32 bg-primary-50 dark:bg-gray-900" id="testimonials">
|
|
<div className="container px-4 md:px-6 mx-auto">
|
|
<div className="flex flex-col items-center justify-center space-y-4 text-center">
|
|
<div className="space-y-2">
|
|
<Animate animation="fadeInUp" delay={0.1}>
|
|
<h2 className="text-3xl font-bold tracking-tighter sm:text-5xl text-primary-900 dark:text-primary-400">
|
|
Dicono di noi
|
|
</h2>
|
|
</Animate>
|
|
<Animate animation="fadeInUp" delay={0.2}>
|
|
<p className="max-w-[900px] text-gray-500 dark:text-gray-400 md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed">
|
|
Ecco cosa hanno da dire i clienti che hanno lavorato di noi.
|
|
</p>
|
|
</Animate>
|
|
</div>
|
|
</div>
|
|
<Testimonials />
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export function Testimonials() {
|
|
const [currentIndex, setCurrentIndex] = useState(0)
|
|
const size = useWindowSize();
|
|
const displayCount = useMemo(() => {
|
|
if (typeof window !== "undefined") {
|
|
if (window.innerWidth >= 1024) return 3
|
|
if (window.innerWidth >= 768) return 2
|
|
return 1
|
|
}
|
|
return 1
|
|
}, [size.width]);
|
|
|
|
const handlePrev = () => {
|
|
console.log(currentIndex, (currentIndex === 0 ? testimonials.length - displayCount : currentIndex - 1));
|
|
setCurrentIndex((prevIndex) => (prevIndex === 0 ? testimonials.length - displayCount : prevIndex - 1))
|
|
}
|
|
|
|
const handleNext = () => {
|
|
console.log(currentIndex, (currentIndex + 1) % (testimonials.length - displayCount + 1));
|
|
setCurrentIndex((prevIndex) => (prevIndex + 1) % (testimonials.length - displayCount + 1))
|
|
}
|
|
|
|
const visibleTestimonials = testimonials.slice(currentIndex, currentIndex + displayCount)
|
|
|
|
return (
|
|
<div className="mt-16 relative">
|
|
<div className="flex flex-wrap gap-6 justify-center">
|
|
<AnimatePresence mode="popLayout">
|
|
{visibleTestimonials.map((testimonial, index) => (
|
|
<motion.div
|
|
key={currentIndex + index}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -20 }}
|
|
transition={{ duration: 0.5 }}
|
|
className="w-full md:w-[calc(50%-12px)] lg:w-[calc(33.33%-16px)]"
|
|
>
|
|
<Card className="h-full border-primary-100 dark:border-primary-800 hover:shadow-md transition-shadow">
|
|
<CardContent className="p-6">
|
|
<div className="flex flex-col gap-4 h-full">
|
|
<motion.div
|
|
initial={{ opacity: 0, scale: 0.8 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
transition={{ duration: 0.3, delay: 0.2 }}
|
|
>
|
|
<Quote className="h-8 w-8 text-primary-300" />
|
|
</motion.div>
|
|
<p className="text-gray-600 dark:text-gray-300 italic">{testimonial.quote}</p>
|
|
<div className="flex items-center gap-4 mt-auto pt-4">
|
|
<Avatar>
|
|
<AvatarImage src={testimonial.avatar || "/placeholder.svg"} alt={testimonial.author} />
|
|
<AvatarFallback>
|
|
{testimonial.author
|
|
.split(" ")
|
|
.map((n) => n[0])
|
|
.join("")}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div>
|
|
<p className="font-semibold text-primary-900 dark:text-primary-300">{testimonial.author}{currentIndex + index}</p>
|
|
<p className="text-sm text-gray-500 dark:text-gray-400">{testimonial.role}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</motion.div>
|
|
))}
|
|
</AnimatePresence>
|
|
</div>
|
|
<div className="flex justify-center mt-8 gap-2">
|
|
<motion.div whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.95 }}>
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
onClick={handlePrev}
|
|
className="rounded-full border-primary-200 dark:border-primary-800 text-primary-900 dark:text-primary-300 hover:bg-primary-100 hover:text-primary-900 dark:hover:bg-primary-900 dark:hover:text-primary-100"
|
|
>
|
|
<ChevronLeft className="h-4 w-4" />
|
|
<span className="sr-only">Previous</span>
|
|
</Button>
|
|
</motion.div>
|
|
<motion.div whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.95 }}>
|
|
<Button
|
|
variant="outline"
|
|
size="icon"
|
|
onClick={handleNext}
|
|
className="rounded-full border-primary-200 dark:border-primary-800 text-primary-900 dark:text-primary-300 hover:bg-primary-100 hover:text-primary-900 dark:hover:bg-primary-900 dark:hover:text-primary-100"
|
|
>
|
|
<ChevronRight className="h-4 w-4" />
|
|
<span className="sr-only">Next</span>
|
|
</Button>
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|