testimonial added
This commit is contained in:
parent
d68dc98244
commit
3ed0667d99
8 changed files with 218 additions and 14 deletions
|
|
@ -2,6 +2,22 @@ import type { NextConfig } from "next";
|
|||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "api.dicebear.com",
|
||||
port: "",
|
||||
pathname: "/**",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "static.wikia.nocookie.net",
|
||||
port: "",
|
||||
pathname: "/**",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
|
|
|||
26
package-lock.json
generated
26
package-lock.json
generated
|
|
@ -14,7 +14,7 @@
|
|||
"@radix-ui/react-switch": "^1.1.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^12.4.7",
|
||||
"framer-motion": "^12.23.22",
|
||||
"lucide-react": "^0.476.0",
|
||||
"next": "15.1.7",
|
||||
"next-sitemap": "^4.2.3",
|
||||
|
|
@ -3217,13 +3217,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "12.4.7",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.4.7.tgz",
|
||||
"integrity": "sha512-VhrcbtcAMXfxlrjeHPpWVu2+mkcoR31e02aNSR7OUS/hZAciKa8q6o3YN2mA1h+jjscRsSyKvX6E1CiY/7OLMw==",
|
||||
"version": "12.23.22",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.22.tgz",
|
||||
"integrity": "sha512-ZgGvdxXCw55ZYvhoZChTlG6pUuehecgvEAJz0BHoC5pQKW1EC5xf1Mul1ej5+ai+pVY0pylyFfdl45qnM1/GsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "^12.4.5",
|
||||
"motion-utils": "^12.0.0",
|
||||
"motion-dom": "^12.23.21",
|
||||
"motion-utils": "^12.23.6",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
@ -4348,18 +4348,18 @@
|
|||
}
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "12.4.5",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.4.5.tgz",
|
||||
"integrity": "sha512-Q2xmhuyYug1CGTo0jdsL05EQ4RhIYXlggFS/yPhQQRNzbrhjKQ1tbjThx5Plv68aX31LsUQRq4uIkuDxdO5vRQ==",
|
||||
"version": "12.23.21",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.21.tgz",
|
||||
"integrity": "sha512-5xDXx/AbhrfgsQmSE7YESMn4Dpo6x5/DTZ4Iyy4xqDvVHWvFVoV+V2Ri2S/ksx+D40wrZ7gPYiMWshkdoqNgNQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-utils": "^12.0.0"
|
||||
"motion-utils": "^12.23.6"
|
||||
}
|
||||
},
|
||||
"node_modules/motion-utils": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.0.0.tgz",
|
||||
"integrity": "sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA==",
|
||||
"version": "12.23.6",
|
||||
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
|
||||
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"@radix-ui/react-switch": "^1.1.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^12.4.7",
|
||||
"framer-motion": "^12.23.22",
|
||||
"lucide-react": "^0.476.0",
|
||||
"next": "15.1.7",
|
||||
"next-sitemap": "^4.2.3",
|
||||
|
|
|
|||
BIN
public/assets/images/testimonial/Abdus-Salam-Jomadder.webp
Normal file
BIN
public/assets/images/testimonial/Abdus-Salam-Jomadder.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
BIN
public/assets/images/testimonial/business-women.jpg
Normal file
BIN
public/assets/images/testimonial/business-women.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
public/assets/images/testimonial/mahmudulhasan.webp
Normal file
BIN
public/assets/images/testimonial/mahmudulhasan.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
|
|
@ -4,6 +4,7 @@ import FeatureSection from "@/components/FeatureSection";
|
|||
import HeroSection from "@/components/HeroSection";
|
||||
import PricingSection from "@/components/PricingSection";
|
||||
import SocialSection from "@/components/SocialSection";
|
||||
import TestimonialSection from "@/components/TestimonialSection";
|
||||
import React from "react";
|
||||
|
||||
const Home = () => {
|
||||
|
|
@ -238,6 +239,7 @@ const Home = () => {
|
|||
|
||||
<FeatureCardSection {...allInOneToolsData} />
|
||||
<PricingSection />
|
||||
<TestimonialSection />
|
||||
<FAQSection />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
186
src/components/TestimonialSection.tsx
Normal file
186
src/components/TestimonialSection.tsx
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
"use client";
|
||||
|
||||
import { ChevronLeft, ChevronRight, Star } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
|
||||
interface Testimonial {
|
||||
id: string;
|
||||
name: string;
|
||||
role: string;
|
||||
company: string;
|
||||
image: string;
|
||||
rating: number;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const testimonials: Testimonial[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Mahmud Hasan – ",
|
||||
role: "Marketer",
|
||||
company: "Freelancer",
|
||||
image: "/assets/images/testimonial/mahmudulhasan.webp",
|
||||
rating: 5,
|
||||
content:
|
||||
"I used to spend 50-60 hours every week managing my client's social media content and scheduling. With PlanPost AI, I now finish everything in just 2 hours! This has saved me huge amounts of time and allowed me to take on more clients—boosting both my productivity and income.",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Abdus Salam Jomadder",
|
||||
role: "E-commerce Entrepreneu",
|
||||
company: "ফ্লাভিয়া",
|
||||
image: "/assets/images/testimonial/Abdus-Salam-Jomadder.webp",
|
||||
rating: 5,
|
||||
content:
|
||||
"My product images improved significantly with PlanPost AI. They look more professional and eye-catching, which directly helped me increase sales. The best part? I don’t need any design skills—it’s simple, fast, and effective!",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "Farhana Akter",
|
||||
role: "Owner",
|
||||
company: "Small Business",
|
||||
image: "/assets/images/testimonial/business-women.jpg",
|
||||
rating: 5,
|
||||
content:
|
||||
"Staying consistent on social media was always tough. With PlanPost AI’s bulk generation, I now create a month’s content in minutes and schedule it instantly. My brand engagement has grown by 3x, and I can focus more on scaling my business.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function TestimonialSection() {
|
||||
const [currentIndex, setCurrentIndex] = React.useState(0);
|
||||
const [direction, setDirection] = React.useState(0);
|
||||
|
||||
const slidesPerView = 3;
|
||||
const maxIndex = Math.max(0, testimonials.length - slidesPerView);
|
||||
|
||||
const handlePrevious = () => {
|
||||
setDirection(-1);
|
||||
setCurrentIndex((prev) => Math.max(0, prev - 1));
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
setDirection(1);
|
||||
setCurrentIndex((prev) => Math.min(maxIndex, prev + 1));
|
||||
};
|
||||
|
||||
const visibleTestimonials = testimonials.slice(
|
||||
currentIndex,
|
||||
currentIndex + slidesPerView
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="py-16 px-4 md:px-6 lg:px-8 bg-white">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4">
|
||||
What Our <span className="text-[#7c3aed]">Customers</span> Say
|
||||
</h2>
|
||||
<p className="text-gray-600 text-lg max-w-2xl mx-auto">
|
||||
Join thousands of satisfied customers who have transformed their
|
||||
social media presence with Planpost AI
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div className="overflow-hidden">
|
||||
<AnimatePresence initial={false} custom={direction} mode="wait">
|
||||
<motion.div
|
||||
key={currentIndex}
|
||||
custom={direction}
|
||||
initial={{ opacity: 0, x: direction > 0 ? 100 : -100 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: direction > 0 ? -100 : 100 }}
|
||||
transition={{
|
||||
duration: 0.4,
|
||||
ease: [0.4, 0, 0.2, 1],
|
||||
}}
|
||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
|
||||
>
|
||||
{visibleTestimonials.map((testimonial) => (
|
||||
<motion.div
|
||||
key={testimonial.id}
|
||||
whileHover={{ y: -8 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="bg-[#f5f1ff] rounded-lg p-6 flex flex-col"
|
||||
>
|
||||
<div className="flex items-center gap-1 mb-4">
|
||||
{[...Array(testimonial.rating)].map((_, i) => (
|
||||
<Star
|
||||
key={i}
|
||||
className="w-5 h-5 fill-[#7c3aed] text-[#7c3aed]"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="text-gray-700 mb-6 flex-grow">
|
||||
{testimonial.content}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Image
|
||||
src={testimonial.image}
|
||||
alt={testimonial.name}
|
||||
height={50}
|
||||
width={50}
|
||||
className="w-16 h-16 rounded-full bg-white"
|
||||
/>
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-900">
|
||||
{testimonial.name}
|
||||
</h4>
|
||||
<p className="text-sm text-gray-600">
|
||||
{testimonial.role} at {testimonial.company}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-4 mt-8">
|
||||
<motion.button
|
||||
onClick={handlePrevious}
|
||||
disabled={currentIndex === 0}
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="w-12 h-12 rounded-full bg-[#7c3aed] text-white flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed hover:bg-[#6d28d9] transition-colors"
|
||||
>
|
||||
<ChevronLeft className="w-6 h-6" />
|
||||
</motion.button>
|
||||
|
||||
<div className="flex gap-2">
|
||||
{[...Array(maxIndex + 1)].map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setDirection(index > currentIndex ? 1 : -1);
|
||||
setCurrentIndex(index);
|
||||
}}
|
||||
className={`h-2 rounded-full transition-all duration-300 ${
|
||||
index === currentIndex
|
||||
? "w-8 bg-[#7c3aed]"
|
||||
: "w-2 bg-gray-300 hover:bg-gray-400"
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<motion.button
|
||||
onClick={handleNext}
|
||||
disabled={currentIndex === maxIndex}
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="w-12 h-12 rounded-full bg-[#7c3aed] text-white flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed hover:bg-[#6d28d9] transition-colors"
|
||||
>
|
||||
<ChevronRight className="w-6 h-6" />
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue