Skip to content

Commit 659faf6

Browse files
enhanced testimonal section
1 parent 1571019 commit 659faf6

5 files changed

Lines changed: 337 additions & 221 deletions

File tree

src/components/testimonials/TestimonialCard.tsx

Lines changed: 93 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from "react";
22
import { motion } from "framer-motion";
33
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
44
import { useSafeColorMode } from "../../utils/useSafeColorMode";
5+
import { ExternalLink, Quote } from "lucide-react";
56

67
interface TestimonialCardProps {
78
name: string;
@@ -22,92 +23,120 @@ const TestimonialCard: React.FC<TestimonialCardProps> = ({
2223
}) => {
2324
const { colorMode, isDark } = useSafeColorMode();
2425

25-
// Function to format the link display
2626
const formatLinkDisplay = (url: string) => {
2727
try {
2828
const urlObj = new URL(url);
2929
return urlObj.hostname + urlObj.pathname;
3030
} catch {
31-
// If URL parsing fails, return the original link
3231
return url;
3332
}
3433
};
3534

3635
return (
3736
<motion.div
38-
initial={{ opacity: 0 }}
39-
animate={{ opacity: 1 }}
40-
exit={{ opacity: 0 }}
41-
className={`flex h-[250px] flex-col justify-between rounded-2xl p-6 shadow-lg transition-shadow duration-300 hover:shadow-xl ${
42-
isDark ? "bg-[#1a1a1a] text-white" : "bg-white text-gray-900"
37+
initial={{ opacity: 0, y: 20 }}
38+
animate={{ opacity: 1, y: 0 }}
39+
exit={{ opacity: 0, y: -20 }}
40+
whileHover={{ y: -5 }}
41+
transition={{ duration: 0.3 }}
42+
className={`group relative h-full overflow-hidden rounded-2xl border backdrop-blur-sm transition-all duration-300 hover:shadow-2xl ${
43+
isDark
44+
? "border-gray-700/50 bg-gray-900/80 shadow-xl"
45+
: "border-gray-200/50 bg-white/90 shadow-lg"
4346
}`}
4447
>
45-
{/* Header with Avatar and Name */}
46-
<div className="flex items-center gap-4">
47-
<Avatar className="h-24 w-24 rounded-full">
48-
<AvatarImage src={avatar} className="object-contain" />
49-
<AvatarFallback>CN</AvatarFallback>
50-
</Avatar>
51-
<div>
52-
<h3
53-
className={`text-lg font-semibold ${isDark ? "text-white" : "text-gray-900"}`}
54-
>
55-
{name}
56-
</h3>
57-
<p
58-
className={`text-sm ${isDark ? "text-gray-400" : "text-gray-500"}`}
59-
>
60-
@{username}
48+
{/* Gradient Background */}
49+
<div className="absolute inset-0 bg-gradient-to-br from-purple-500/5 via-blue-500/5 to-pink-500/5" />
50+
51+
{/* Quote Icon */}
52+
<div className="absolute top-4 right-4 opacity-20">
53+
<Quote size={32} className="text-purple-500" />
54+
</div>
55+
56+
<div className="relative flex h-full flex-col p-6">
57+
{/* Header */}
58+
<div className="mb-6 flex items-center gap-4">
59+
<div className="relative">
60+
<Avatar className="h-16 w-16 border-2 border-gradient-to-r from-purple-500 to-pink-500">
61+
<AvatarImage src={avatar} className="object-contain" />
62+
<AvatarFallback className="bg-gradient-to-br from-purple-500 to-pink-500 text-white font-semibold">
63+
{name.charAt(0)}
64+
</AvatarFallback>
65+
</Avatar>
66+
<div className="absolute -bottom-1 -right-1 h-5 w-5 rounded-full bg-green-500 border-2 border-white" />
67+
</div>
68+
<div className="flex-1">
69+
<h3 className={`text-lg font-bold ${
70+
isDark ? "text-white" : "text-gray-900"
71+
}`}>
72+
{name}
73+
</h3>
74+
<p className={`text-sm ${
75+
isDark ? "text-gray-400" : "text-gray-500"
76+
}`}>
77+
@{username}
78+
</p>
79+
</div>
80+
</div>
81+
82+
{/* Content */}
83+
<div className="flex-1">
84+
<p className={`text-base leading-relaxed ${
85+
isDark ? "text-gray-300" : "text-gray-700"
86+
}`}>
87+
{content.replace(/#\w+/g, '').trim()}
6188
</p>
6289
</div>
63-
</div>
6490

65-
{/* Content */}
66-
<p
67-
className={`my-4 line-clamp-3 flex-grow ${isDark ? "text-gray-300" : "text-gray-700"}`}
68-
>
69-
{content}
70-
</p>
91+
{/* Footer */}
92+
<div className={`mt-6 space-y-4 border-t pt-4 ${
93+
isDark ? "border-gray-700/50" : "border-gray-200/50"
94+
}`}>
95+
{/* Hashtags */}
96+
<div className="flex flex-wrap gap-2">
97+
{content.match(/#\w+/g)?.map((hashtag, index) => (
98+
<span
99+
key={index}
100+
className={`rounded-full px-3 py-1 text-xs font-medium transition-colors hover:scale-105 ${
101+
isDark
102+
? "bg-blue-500/20 text-blue-400 hover:bg-blue-500/30"
103+
: "bg-blue-100 text-blue-600 hover:bg-blue-200"
104+
}`}
105+
>
106+
{hashtag}
107+
</span>
108+
))}
109+
</div>
71110

72-
{/* Footer with Hashtags and Date */}
73-
<div
74-
className={`flex flex-col gap-2 border-t pt-2 text-sm ${
75-
isDark ? "border-gray-700" : "border-gray-100"
76-
}`}
77-
>
78-
{/* Hashtags */}
79-
<div className="flex flex-wrap gap-2">
80-
{content.match(/#\w+/g)?.map((hashtag, index) => (
81-
<span
82-
key={index}
83-
className="cursor-pointer text-blue-500 hover:text-blue-600"
111+
{/* Link and Date */}
112+
<div className="flex items-center justify-between">
113+
<a
114+
href={link}
115+
target="_blank"
116+
rel="noopener noreferrer"
117+
className={`group/link flex items-center gap-2 text-sm font-medium transition-colors ${
118+
isDark
119+
? "text-purple-400 hover:text-purple-300"
120+
: "text-purple-600 hover:text-purple-700"
121+
}`}
84122
>
85-
{hashtag}
123+
<span className="truncate">{formatLinkDisplay(link)}</span>
124+
<ExternalLink size={14} className="transition-transform group-hover/link:translate-x-0.5 group-hover/link:-translate-y-0.5" />
125+
</a>
126+
<span className={`text-xs ${
127+
isDark ? "text-gray-500" : "text-gray-400"
128+
}`}>
129+
{date}
86130
</span>
87-
))}
88-
</div>
89-
90-
{/* Link and Date Row */}
91-
<div className="flex items-center justify-between">
92-
<a
93-
href={link}
94-
target="_blank"
95-
rel="noopener noreferrer"
96-
className={`cursor-pointer hover:underline ${
97-
isDark
98-
? "text-blue-400 hover:text-blue-300"
99-
: "text-blue-600 hover:text-blue-700"
100-
}`}
101-
>
102-
{formatLinkDisplay(link)}
103-
</a>
104-
<span className={isDark ? "text-gray-500" : "text-gray-400"}>
105-
{date}
106-
</span>
131+
</div>
107132
</div>
108133
</div>
134+
135+
{/* Hover Effect Border */}
136+
<div className="absolute inset-0 rounded-2xl border-2 border-transparent bg-gradient-to-r from-purple-500/20 via-blue-500/20 to-pink-500/20 opacity-0 transition-opacity duration-300 group-hover:opacity-100"
137+
style={{ mask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)', maskComposite: 'xor' }} />
109138
</motion.div>
110139
);
111140
};
112141

113-
export default TestimonialCard;
142+
export default TestimonialCard;

src/components/testimonials/TestimonialCarousel.tsx

Lines changed: 100 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import {
1010
import { Button } from "../ui/button";
1111
import TestimonialCard from "./TestimonialCard";
1212
import Autoplay from "embla-carousel-autoplay";
13+
import { motion } from "framer-motion";
14+
import { useSafeColorMode } from "../../utils/useSafeColorMode";
1315

14-
// Sample testimonial data
15-
const testimonials = [
16+
const baseTestimonials = [
1617
{
1718
name: "Rashi Chouhan",
1819
username: "RashiChouhan",
@@ -42,10 +43,13 @@ const testimonials = [
4243
},
4344
];
4445

46+
const testimonials = [...baseTestimonials, ...baseTestimonials];
47+
4548
export function TestimonialCarousel() {
4649
const [api, setApi] = useState<CarouselApi>();
4750
const [current, setCurrent] = useState(0);
4851
const [count, setCount] = useState(0);
52+
const { colorMode } = useSafeColorMode();
4953

5054
useEffect(() => {
5155
if (!api) {
@@ -61,52 +65,102 @@ export function TestimonialCarousel() {
6165
}, [api]);
6266

6367
return (
64-
<div className="w-full">
65-
<div className="mb-10 text-center">
66-
<h2 className="mb-2 text-3xl font-bold">Loved by Many Users</h2>
67-
<div className="mx-auto h-1 w-32 rounded-full bg-blue-500"></div>
68+
<div className="relative w-full py-20">
69+
{/* Background Elements */}
70+
<div className="absolute inset-0 overflow-hidden">
71+
<div className="absolute top-20 right-10 h-64 w-64 rounded-full bg-gradient-to-br from-purple-400/10 to-pink-600/10 blur-3xl" />
72+
<div className="absolute bottom-20 left-10 h-64 w-64 rounded-full bg-gradient-to-tr from-blue-400/10 to-cyan-600/10 blur-3xl" />
6873
</div>
6974

70-
<Carousel
71-
setApi={setApi}
72-
className="w-full"
73-
opts={{
74-
align: "start",
75-
loop: true,
76-
}}
77-
plugins={[
78-
Autoplay({
79-
delay: 4000,
80-
}),
81-
]}
82-
>
83-
<CarouselContent className="my-16 -ml-2 md:-ml-4">
84-
{testimonials.map((testimonial, index) => (
85-
<CarouselItem
86-
key={index}
87-
className="h-full pl-2 md:basis-1/2 md:pl-4"
88-
>
89-
<TestimonialCard {...testimonial} />
90-
</CarouselItem>
91-
))}
92-
</CarouselContent>
93-
94-
<div className="mt-8 flex items-center justify-center gap-2">
95-
<CarouselPrevious className="static translate-y-0" />
96-
<div className="flex gap-2">
97-
{Array.from({ length: count }).map((_, index) => (
98-
<Button
99-
key={index}
100-
variant={current === index + 1 ? "default" : "outline"}
101-
size="icon"
102-
className="h-2 w-2 rounded-full p-0"
103-
onClick={() => api?.scrollTo(index)}
104-
/>
105-
))}
75+
<div className="relative mx-auto max-w-7xl px-4">
76+
<motion.div
77+
initial={{ opacity: 0, y: 30 }}
78+
animate={{ opacity: 1, y: 0 }}
79+
transition={{ duration: 0.6 }}
80+
className="mb-16 text-center"
81+
>
82+
<div className="mb-4">
83+
<span className={`inline-block rounded-full px-4 py-2 text-sm font-medium ${
84+
colorMode === "dark"
85+
? "bg-purple-500/10 text-purple-400 border border-purple-500/20"
86+
: "bg-purple-50 text-purple-600 border border-purple-200"
87+
}`}>
88+
⭐ Client Testimonials
89+
</span>
10690
</div>
107-
<CarouselNext className="static translate-y-0" />
108-
</div>
109-
</Carousel>
91+
<h2 className={`mb-6 text-5xl font-bold leading-tight ${
92+
colorMode === "dark" ? "text-white" : "text-gray-900"
93+
}`}>
94+
Loved by <span className="bg-gradient-to-r from-purple-600 to-pink-600 bg-clip-text text-transparent">Many Users</span>
95+
</h2>
96+
97+
</motion.div>
98+
99+
<motion.div
100+
initial={{ opacity: 0, y: 40 }}
101+
animate={{ opacity: 1, y: 0 }}
102+
transition={{ duration: 0.6, delay: 0.2 }}
103+
>
104+
<Carousel
105+
setApi={setApi}
106+
className="w-full"
107+
opts={{
108+
align: "center",
109+
loop: true,
110+
}}
111+
plugins={[
112+
Autoplay({
113+
delay: 2500,
114+
stopOnInteraction: false,
115+
stopOnMouseEnter: false,
116+
}),
117+
]}
118+
>
119+
<CarouselContent className="my-8 -ml-2 md:-ml-4 flex items-stretch">
120+
{testimonials.map((testimonial, index) => (
121+
<CarouselItem
122+
key={index}
123+
className="flex pl-2 md:basis-1/2 lg:basis-1/3 md:pl-4"
124+
>
125+
<TestimonialCard {...testimonial} />
126+
</CarouselItem>
127+
))}
128+
</CarouselContent>
129+
130+
{/* Navigation */}
131+
<div className="mt-12 flex items-center justify-center gap-6">
132+
<CarouselPrevious className={`static translate-y-0 h-12 w-12 ${
133+
colorMode === "dark"
134+
? "border-gray-700 bg-gray-800 hover:bg-gray-700"
135+
: "border-gray-200 bg-white hover:bg-gray-50"
136+
}`} />
137+
138+
{/* Dots Indicator */}
139+
<div className="flex gap-2">
140+
{Array.from({ length: count }).map((_, index) => (
141+
<button
142+
key={index}
143+
onClick={() => api?.scrollTo(index)}
144+
className={`h-3 w-3 rounded-full transition-all duration-300 ${
145+
current === index + 1
146+
? "bg-gradient-to-r from-purple-600 to-pink-600 scale-125"
147+
: colorMode === "dark"
148+
? "bg-gray-600 hover:bg-gray-500"
149+
: "bg-gray-300 hover:bg-gray-400"
150+
}`}
151+
/>
152+
))}
153+
</div>
154+
155+
<CarouselNext className={`static translate-y-0 h-12 w-12 ${
156+
colorMode === "dark"
157+
? "border-gray-700 bg-gray-800 hover:bg-gray-700"
158+
: "border-gray-200 bg-white hover:bg-gray-50"
159+
}`} />
160+
</div>
161+
</Carousel>
162+
</motion.div>
163+
</div>
110164
</div>
111165
);
112-
}
166+
}

0 commit comments

Comments
 (0)