Initial commit

This commit is contained in:
Francesco
2025-05-14 14:35:15 +02:00
commit c482b6b254
130 changed files with 13171 additions and 0 deletions

178
components/testimonials.tsx Normal file
View File

@@ -0,0 +1,178 @@
"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>
)
}