feat: add testimonials section to homepage

- Introduced a new TestimonialsSection component to display customer testimonials, enhancing user engagement and trust.
- Updated English and Chinese JSON files to include testimonials titles, descriptions, and individual customer quotes.
- Refactored the homepage layout to integrate the new testimonials section, improving overall user experience.
This commit is contained in:
javayhu 2025-04-13 00:06:31 +08:00
parent 5c48725110
commit 50aacbed11
7 changed files with 481 additions and 106 deletions

View File

@ -159,6 +159,99 @@
"answer": "Please contact our customer support team at support@mksaas.com"
}
}
},
"testimonials": {
"title": "Testimonials",
"description": "What our customers say about us",
"items": {
"item-1": {
"name": "Jonathan Yombo",
"role": "Software Engineer",
"image": "https://randomuser.me/api/portraits/men/1.jpg",
"quote": "MkSaaS is really extraordinary and very practical, no need to break your head. A real gold mine."
},
"item-2": {
"name": "Yves Kalume",
"role": "GDE - Android",
"image": "https://randomuser.me/api/portraits/men/6.jpg",
"quote": "With no experience in webdesign I just redesigned my entire website in a few minutes with tailwindcss thanks to MkSaaS."
},
"item-3": {
"name": "Yucel Faruksahan",
"role": "Tailkits Creator",
"image": "https://randomuser.me/api/portraits/men/7.jpg",
"quote": "Great work on tailfolio template. This is one of the best personal website that I have seen so far :)"
},
"item-4": {
"name": "Anonymous author",
"role": "Product Manager",
"image": "https://randomuser.me/api/portraits/men/8.jpg",
"quote": "I am really new to Tailwind and I want to give a go to make some page on my own. I searched a lot of hero pages and blocks online. However, most of them are not giving me a clear view or needed some HTML/CSS coding background to make some changes from the original or too expensive to have. I downloaded the one of MkSaaS template which is very clear to understand at the start and you could modify the codes/blocks to fit perfectly on your purpose of the page."
},
"item-5": {
"name": "Shekinah Tshiokufila",
"role": "Senior Software Engineer",
"image": "https://randomuser.me/api/portraits/men/4.jpg",
"quote": "MkSaaS is redefining the standard of web design, with these blocks it provides an easy and efficient way for those who love beauty but may lack the time to implement it. I can only recommend this incredible wonder."
},
"item-6": {
"name": "Oketa Fred",
"role": "Fullstack Developer",
"image": "https://randomuser.me/api/portraits/men/2.jpg",
"quote": "I absolutely love MkSaaS! The component blocks are beautifully designed and easy to use, which makes creating a great-looking website a breeze."
},
"item-7": {
"name": "Zeki",
"role": "Founder of ChatExtend",
"image": "https://randomuser.me/api/portraits/men/5.jpg",
"quote": "Using TailsUI has been like unlocking a secret design superpower. It's the perfect fusion of simplicity and versatility, enabling us to create UIs that are as stunning as they are user-friendly."
},
"item-8": {
"name": "Joseph Kitheka",
"role": "Fullstack Developer",
"image": "https://randomuser.me/api/portraits/men/9.jpg",
"quote": "MkSaaS has transformed the way I develop web applications. Their extensive collection of UI components, blocks, and templates has significantly accelerated my workflow. The flexibility to customize every aspect allows me to create unique user experiences. MkSaaS is a game-changer for modern web development!"
},
"item-9": {
"name": "Khatab Wedaa",
"role": "MerakiUI Creator",
"image": "https://randomuser.me/api/portraits/men/10.jpg",
"quote": "MkSaaS is an elegant, clean, and responsive tailwind css components it's very helpful to start fast with your project."
},
"item-10": {
"name": "Rodrigo Aguilar",
"role": "TailwindAwesome Creator",
"image": "https://randomuser.me/api/portraits/men/11.jpg",
"quote": "I love MkSaaS ❤️. The component blocks are well-structured, simple to use, and beautifully designed. It makes it really easy to have a good-looking website in no time."
},
"item-11": {
"name": "Eric Ampire",
"role": "@GoogleDevExpert for Android",
"image": "https://randomuser.me/api/portraits/men/12.jpg",
"quote": "MkSaaS templates are the perfect solution for anyone who wants to create a beautiful and functional website without any web design experience. The templates are easy to use, customizable, and responsive, and the support team is always available to help. I highly recommend MkSaaS templates to anyone who is looking to create a website."
},
"item-12": {
"name": "Roland Tubonge",
"role": "Software Engineer",
"image": "https://randomuser.me/api/portraits/men/13.jpg",
"quote": "MkSaaS is so well designed that even with a very poor knowledge of web design you can do miracles. Let yourself be seduced!"
}
}
},
"stats": {
"title": "MkSaaS in numbers",
"description": "MkSaaS lets you make AI SaaS in days, simply and effortlessly",
"items": {
"item-1": {
"title": "Stars on GitHub"
},
"item-2": {
"title": "Active Users"
},
"item-3": {
"title": "Powered Apps"
}
}
}
},
"PricingPage": {

View File

@ -159,6 +159,99 @@
"answer": "请联系我们的客户支持团队邮箱地址support@mksaas.com"
}
}
},
"testimonials": {
"title": "客户评价",
"description": "我们的客户对我们的评价",
"items": {
"item-1": {
"name": "Jonathan Yombo",
"role": "软件工程师",
"image": "https://randomuser.me/api/portraits/men/1.jpg",
"quote": "MkSaaS 非常出色且实用,无需费心,一个真正的金矿。"
},
"item-2": {
"name": "Yves Kalume",
"role": "Android GDE",
"image": "https://randomuser.me/api/portraits/men/6.jpg",
"quote": "没有网页设计经验,我只需几分钟就可以用 Tailwindcss 重新设计我的整个网站,感谢 MkSaaS。"
},
"item-3": {
"name": "Yucel Faruksahan",
"role": "Tailkits 创建者",
"image": "https://randomuser.me/api/portraits/men/7.jpg",
"quote": "MkSaaS 模板做得很好,这是我见过最好的 SaaS 模板,没有之一 :)"
},
"item-4": {
"name": "Anonymous author",
"role": "产品经理",
"image": "https://randomuser.me/api/portraits/men/8.jpg",
"quote": "我真的很新到 Tailwind 和我想自己做一些页面,我在网上搜索了很多英雄页面和区块。然而,大多数都没有给我一个清晰的想法,或者需要一些 HTML/CSS 编码背景来从原始文件中做一些更改,或者太贵了。我下载了其中一个 Tailus 模板,它非常容易理解,你可以在开始时修改代码/区块以完美地适应你的页面目的。"
},
"item-5": {
"name": "Shekinah Tshiokufila",
"role": "高级软件工程师",
"image": "https://randomuser.me/api/portraits/men/4.jpg",
"quote": "MkSaaS 正在重新定义网页设计标准,这些区块为那些喜欢美丽但可能缺乏时间实现它的人提供了简单且高效的方式。我只能推荐这个不可思议的奇迹。"
},
"item-6": {
"name": "Oketa Fred",
"role": "全栈开发工程师",
"image": "https://randomuser.me/api/portraits/men/2.jpg",
"quote": "我绝对喜欢 MkSaaS这些组件区块设计精美且易于使用使创建一个出色的网站变得轻而易举。"
},
"item-7": {
"name": "Zeki",
"role": "ChatExtend 创始人",
"image": "https://randomuser.me/api/portraits/men/5.jpg",
"quote": "使用 MkSaaS 就像解锁了一个秘密的设计超能力。它是简单性和多功能性的完美融合,使我们能够创建既令人惊叹又用户友好的界面。"
},
"item-8": {
"name": "Joseph Kitheka",
"role": "全栈开发工程师",
"image": "https://randomuser.me/api/portraits/men/9.jpg",
"quote": "MkSaaS 改变了我的网页开发方式。他们的 UI 组件、区块和模板极大地加速了我的工作流程。定制每个方面的灵活性使我能够创建独特的用户体验。MkSaaS 是现代网页开发的游戏规则改变者!"
},
"item-9": {
"name": "Khatab Wedaa",
"role": "MerakiUI 创建者",
"image": "https://randomuser.me/api/portraits/men/10.jpg",
"quote": "MkSaaS 是一个优雅、干净且响应式的 SaaS 模板,它非常有助于快速开始您的项目。"
},
"item-10": {
"name": "Rodrigo Aguilar",
"role": "TailwindAwesome 创建者",
"image": "https://randomuser.me/api/portraits/men/11.jpg",
"quote": "我爱 MkSaaS ❤️。这些组件区块结构良好,易于使用,设计精美。它使创建一个出色的网站变得非常容易。"
},
"item-11": {
"name": "Eric Ampire",
"role": "Google 开发者专家",
"image": "https://randomuser.me/api/portraits/men/12.jpg",
"quote": "MkSaaS 模板是任何想要创建一个美丽且功能齐全的网站但没有网页设计经验的人的完美解决方案。这些模板易于使用,可定制,并且响应迅速,我们的支持团队随时为您提供帮助。我强烈推荐 MkSaaS 模板给任何想要创建网站的人。"
},
"item-12": {
"name": "Roland Tubonge",
"role": "软件工程师",
"image": "https://randomuser.me/api/portraits/men/13.jpg",
"quote": "MkSaaS 设计得如此出色,即使没有网页设计经验,您也可以创造奇迹。让自己被吸引!"
}
}
},
"stats": {
"title": "MkSaaS 相关的数字",
"description": "MkSaaS 可让您在几天内轻松构建您的 AI SaaS",
"items": {
"item-1": {
"title": "GitHub 上的星星"
},
"item-2": {
"title": "使用 MkSaaS 的活跃用户"
},
"item-3": {
"title": "使用 MkSaaS 启动的应用"
}
}
}
},
"PricingPage": {

View File

@ -1,21 +1,20 @@
import FaqSection from '@/components/blocks/faqs/faqs';
import Features2Section from '@/components/blocks/features/features2';
import FeaturesSection from '@/components/blocks/features/features';
import Features3Section from '@/components/blocks/features/features3';
import Features4Section from '@/components/blocks/features/features4';
import HeroSection from '@/components/blocks/hero/hero';
import IntegrationSection from '@/components/blocks/integration/integration';
import LogoCloud from '@/components/blocks/logo-cloud/logo-cloud';
import TestimonialsSection from '@/components/blocks/testimonials/testimonials';
import { constructMetadata } from '@/lib/metadata';
import { getUrlWithLocale } from '@/lib/urls/urls';
import { Metadata } from 'next';
import { Locale } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import CallToAction from '../../preview/call-to-action/one/page';
import ContentSection from '../../preview/content/one/page';
import FeaturesSection from '@/components/blocks/features/features';
import Pricing from '../../preview/pricing/three/page';
import StatsSection from '../../preview/stats/one/page';
import Testimonials from '../../preview/testimonials/one/page';
import Features3Section from '@/components/blocks/features/features3';
import IntegrationSection from '@/components/blocks/integration/integration';
import Features4Section from '@/components/blocks/features/features4';
import StatsSection from '@/components/blocks/stats/stats';
/**
* https://next-intl.dev/docs/environments/actions-metadata-route-handlers#metadata-api
@ -47,55 +46,51 @@ export default async function HomePage(props: HomePageProps) {
return (
<>
<div className="flex flex-col">
<div id="hero" className="">
<div id="hero">
<HeroSection />
</div>
<div id="logo-cloud" className="">
<div id="logo-cloud">
<LogoCloud />
</div>
<div id="integration" className="">
<IntegrationSection />
</div>
<div id="features" className="">
<FeaturesSection />
</div>
<div id="features2" className="">
<Features2Section />
</div>
<div id="features3" className="">
<Features3Section />
</div>
<div id="features4" className="">
<Features4Section />
</div>
<div id="content" className="">
<ContentSection />
</div>
<div id="pricing" className="">
<Pricing />
</div>
<div id="faqs" className="">
<FaqSection />
</div>
<div id="testimonials" className="">
<Testimonials />
</div>
<div id="stats" className="">
<div id="stats">
<StatsSection />
</div>
<div id="call-to-action" className="">
<div id="integration">
<IntegrationSection />
</div>
<div id="features2">
<Features2Section />
</div>
<div id="features">
<FeaturesSection />
</div>
<div id="features3">
<Features3Section />
</div>
<div id="features4">
<Features4Section />
</div>
<div id="pricing">
<Pricing />
</div>
<div id="faqs">
<FaqSection />
</div>
<div id="testimonials">
<TestimonialsSection />
</div>
<div id="call-to-action">
<CallToAction />
</div>
</div>

View File

@ -59,62 +59,63 @@ export default function Features2Section() {
</p>
</div>
<div className="grid gap-12 sm:px-12 md:grid-cols-2 lg:gap-20 lg:px-0">
<Accordion
type="single"
value={activeItem}
onValueChange={(value) => setActiveItem(value as ImageKey)}
className="w-full"
>
<AccordionItem value="item-1">
<AccordionTrigger>
<div className="flex items-center gap-2 text-base">
<Database className="size-4" />
{t('items.item-1.title')}
</div>
</AccordionTrigger>
<AccordionContent>
{t('items.item-1.description')}
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>
<div className="flex items-center gap-2 text-base">
<Fingerprint className="size-4" />
{t('items.item-2.title')}
</div>
</AccordionTrigger>
<AccordionContent>
{t('items.item-2.description')}
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger>
<div className="flex items-center gap-2 text-base">
<IdCard className="size-4" />
{t('items.item-3.title')}
</div>
</AccordionTrigger>
<AccordionContent>
{t('items.item-3.description')}
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-4">
<AccordionTrigger>
<div className="flex items-center gap-2 text-base">
<ChartBarIncreasingIcon className="size-4" />
{t('items.item-4.title')}
</div>
</AccordionTrigger>
<AccordionContent>
{t('items.item-4.description')}
</AccordionContent>
</AccordionItem>
</Accordion>
<div className="grid gap-12 sm:px-12 md:grid-cols-2 lg:grid-cols-12 lg:gap-8 lg:px-0">
<div className="lg:col-span-5 flex flex-col items-center justify-center">
<Accordion
type="single"
value={activeItem}
onValueChange={(value) => setActiveItem(value as ImageKey)}
className="w-full"
>
<AccordionItem value="item-1">
<AccordionTrigger>
<div className="flex items-center gap-2 text-base">
<Database className="size-4" />
{t('items.item-1.title')}
</div>
</AccordionTrigger>
<AccordionContent>
{t('items.item-1.description')}
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>
<div className="flex items-center gap-2 text-base">
<Fingerprint className="size-4" />
{t('items.item-2.title')}
</div>
</AccordionTrigger>
<AccordionContent>
{t('items.item-2.description')}
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger>
<div className="flex items-center gap-2 text-base">
<IdCard className="size-4" />
{t('items.item-3.title')}
</div>
</AccordionTrigger>
<AccordionContent>
{t('items.item-3.description')}
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-4">
<AccordionTrigger>
<div className="flex items-center gap-2 text-base">
<ChartBarIncreasingIcon className="size-4" />
{t('items.item-4.title')}
</div>
</AccordionTrigger>
<AccordionContent>
{t('items.item-4.description')}
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
<div className="bg-background relative flex overflow-hidden rounded-2xl border p-2">
{/* <div className="w-15 absolute inset-0 right-0 ml-auto border-l bg-[repeating-linear-gradient(-45deg,var(--color-border),var(--color-border)_1px,transparent_1px,transparent_8px)]"></div> */}
<div className="aspect-76/59 bg-background relative rounded-2xl">
<div className="bg-background w-full relative flex overflow-hidden rounded-2xl border p-2 md:h-auto lg:col-span-7">
<div className="aspect-76/59 bg-background relative w-full rounded-2xl">
<AnimatePresence mode="wait">
<motion.div
key={`${activeItem}-id`}

View File

@ -18,16 +18,16 @@ export default function Features4Section() {
<div className="border-border/50 relative rounded-3xl border p-3 lg:col-span-3">
<div className="bg-linear-to-b aspect-76/59 relative rounded-2xl from-zinc-300 to-transparent p-px dark:from-zinc-700">
<Image
src="/blocks/dark-card.webp"
src="/blocks/payments-light.png"
className="hidden rounded-[15px] dark:block"
alt="card illustration dark"
alt="payments illustration dark"
width={1207}
height={929}
/>
<Image
src="/blocks/card.png"
src="/blocks/payments.png"
className="rounded-[15px] shadow dark:hidden"
alt="card illustration light"
alt="payments illustration light"
width={1207}
height={929}
/>

View File

@ -0,0 +1,35 @@
import { useTranslations } from "next-intl";
export default function StatsSection() {
const t = useTranslations('HomePage.stats');
return (
<section className="py-12 md:py-20 w-full bg-muted">
<div className="mx-auto max-w-5xl space-y-8 px-6 md:space-y-16">
<div className="relative z-10 mx-auto max-w-xl space-y-6 text-center">
<h2 className="text-4xl font-medium lg:text-5xl">
{t('title')}
</h2>
<p>
{t('description')}
</p>
</div>
<div className="grid gap-12 divide-y-0 *:text-center md:grid-cols-3 md:gap-2 md:divide-x">
<div className="space-y-4">
<div className="text-5xl font-bold">+1200</div>
<p>{t('items.item-1.title')}</p>
</div>
<div className="space-y-4">
<div className="text-5xl font-bold">22 Million</div>
<p>{t('items.item-2.title')}</p>
</div>
<div className="space-y-4">
<div className="text-5xl font-bold">+500</div>
<p>{t('items.item-3.title')}</p>
</div>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,158 @@
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Card, CardContent } from '@/components/ui/card';
import { useTranslations } from 'next-intl';
type Testimonial = {
name: string;
role: string;
image: string;
quote: string;
};
const chunkArray = (
array: Testimonial[],
chunkSize: number
): Testimonial[][] => {
const result: Testimonial[][] = [];
for (let i = 0; i < array.length; i += chunkSize) {
result.push(array.slice(i, i + chunkSize));
}
return result;
};
export default function TestimonialsSection() {
const t = useTranslations('HomePage.testimonials');
const testimonials: Testimonial[] = [
{
name: t('items.item-1.name'),
role: t('items.item-1.role'),
image: t('items.item-1.image'),
quote: t('items.item-1.quote'),
},
{
name: t('items.item-2.name'),
role: t('items.item-2.role'),
image: t('items.item-2.image'),
quote: t('items.item-2.quote'),
},
{
name: t('items.item-3.name'),
role: t('items.item-3.role'),
image: t('items.item-3.image'),
quote: t('items.item-3.quote'),
},
{
name: t('items.item-4.name'),
role: t('items.item-4.role'),
image: t('items.item-4.image'),
quote: t('items.item-4.quote'),
},
{
name: t('items.item-5.name'),
role: t('items.item-5.role'),
image: t('items.item-5.image'),
quote: t('items.item-5.quote'),
},
{
name: t('items.item-6.name'),
role: t('items.item-6.role'),
image: t('items.item-6.image'),
quote: t('items.item-6.quote'),
},
{
name: t('items.item-7.name'),
role: t('items.item-7.role'),
image: t('items.item-7.image'),
quote: t('items.item-7.quote'),
},
{
name: t('items.item-8.name'),
role: t('items.item-8.role'),
image: t('items.item-8.image'),
quote: t('items.item-8.quote'),
},
{
name: t('items.item-9.name'),
role: t('items.item-9.role'),
image: t('items.item-9.image'),
quote: t('items.item-9.quote'),
},
{
name: t('items.item-10.name'),
role: t('items.item-10.role'),
image: t('items.item-10.image'),
quote: t('items.item-10.quote'),
},
{
name: t('items.item-11.name'),
role: t('items.item-11.role'),
image: t('items.item-11.image'),
quote: t('items.item-11.quote'),
},
{
name: t('items.item-12.name'),
role: t('items.item-12.role'),
image: t('items.item-12.image'),
quote: t('items.item-12.quote'),
}
];
const testimonialChunks = chunkArray(
testimonials,
Math.ceil(testimonials.length / 3)
);
return (
<section>
<div className="py-16">
<div className="mx-auto max-w-6xl px-6">
<div className="text-center">
<h2 className="text-title text-4xl lg:text-5xl font-semibold">
{t('title')}
</h2>
<p className="text-body mt-6">
{t('description')}
</p>
</div>
<div className="mt-8 grid gap-3 sm:grid-cols-2 md:mt-12 lg:grid-cols-3">
{testimonialChunks.map((chunk, chunkIndex) => (
<div key={chunkIndex} className="space-y-3">
{chunk.map(({ name, role, quote, image }, index) => (
<Card key={index}>
<CardContent className="grid grid-cols-[auto_1fr] gap-3 pt-4">
<Avatar className="size-9 border-2 border-gray-200">
<AvatarImage
alt={name}
src={image}
loading="lazy"
width="120"
height="120"
/>
<AvatarFallback></AvatarFallback>
</Avatar>
<div>
<h3 className="font-medium">{name}</h3>
<span className="text-muted-foreground block text-sm tracking-wide">
{role}
</span>
<blockquote className="mt-3">
<p className="text-gray-700 dark:text-gray-300">
{quote}
</p>
</blockquote>
</div>
</CardContent>
</Card>
))}
</div>
))}
</div>
</div>
</div>
</section>
);
}