feat: update navigation and add new block components

- Disabled development indicators in next.config.ts for cleaner output.
- Updated English and Chinese message files to include new block titles for logo, login, signup, and contact sections.
- Refactored routes to include new paths for login, signup, and contact blocks.
- Introduced new layout and page components for marketing sections, including logo cloud and various call-to-action pages.
- Enhanced blocks navigation component to utilize updated paths and improve user experience.
This commit is contained in:
javayhu 2025-03-25 23:57:51 +08:00
parent aefe37068b
commit af94ab6dd0
78 changed files with 237 additions and 135 deletions

View File

@ -227,26 +227,47 @@
"hero": { "hero": {
"title": "Hero Blocks" "title": "Hero Blocks"
}, },
"pricing": { "logo": {
"title": "Pricing Blocks" "title": "Logo Cloud Blocks"
}, },
"features": { "features": {
"title": "Features Blocks" "title": "Features Blocks"
}, },
"faq": { "content": {
"title": "FAQ Blocks" "title": "Content Blocks"
},
"testimonials": {
"title": "Testimonials Blocks"
}, },
"stats": { "stats": {
"title": "Stats Blocks" "title": "Stats Blocks"
}, },
"team": {
"title": "Team Blocks"
},
"testimonials": {
"title": "Testimonials Blocks"
},
"callToAction": { "callToAction": {
"title": "Call to Action Blocks" "title": "Call to Action Blocks"
}, },
"content": { "footer": {
"title": "Content Blocks" "title": "Footer Blocks"
},
"pricing": {
"title": "Pricing Blocks"
},
"comparator": {
"title": "Comparator Blocks"
},
"faq": {
"title": "FAQ Blocks"
},
"login": {
"title": "Login Blocks"
},
"signup": {
"title": "Signup Blocks"
},
"contact": {
"title": "Contact Blocks"
} }
} }
} }

View File

@ -222,26 +222,47 @@
"hero": { "hero": {
"title": "Hero组件" "title": "Hero组件"
}, },
"pricing": { "logoCloud": {
"title": "Pricing组件" "title": "Logo Cloud组件"
}, },
"features": { "features": {
"title": "Features组件" "title": "Features组件"
}, },
"faq": { "content": {
"title": "FAQ组件" "title": "Content组件"
},
"testimonials": {
"title": "Testimonials组件"
}, },
"stats": { "stats": {
"title": "Stats组件" "title": "Stats组件"
}, },
"team": {
"title": "Team组件"
},
"testimonials": {
"title": "Testimonials组件"
},
"callToAction": { "callToAction": {
"title": "Call to Action组件" "title": "Call to Action组件"
}, },
"content": { "footer": {
"title": "Content组件" "title": "Footer组件"
},
"pricing": {
"title": "Pricing组件"
},
"comparator": {
"title": "Comparator组件"
},
"faqs": {
"title": "FAQs组件"
},
"login": {
"title": "Login组件"
},
"signup": {
"title": "Signup组件"
},
"contact": {
"title": "Contact组件"
} }
} }
} }

View File

@ -7,6 +7,7 @@ import { withContentCollections } from "@content-collections/next";
*/ */
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ /* config options here */
devIndicators: false,
// https://nextjs.org/docs/architecture/nextjs-compiler#remove-console // https://nextjs.org/docs/architecture/nextjs-compiler#remove-console
// Remove all console.* calls in production only // Remove all console.* calls in production only

View File

@ -1,15 +0,0 @@
import CategoryNavigation from '@/components/blocks/blocks-nav';
import { categories } from '@/components/blocks/blocks';
export default function CategoryLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<>
<CategoryNavigation categories={categories} />
<main>{children}</main>
</>
);
}

View File

@ -1,51 +0,0 @@
import BlockPreview from '@/components/blocks/block-preview';
import { blocks, categories } from '@/components/blocks/blocks';
import { notFound } from 'next/navigation';
interface PageProps {
params: Promise<{ category: string }>;
}
export const dynamic = 'force-static';
export const revalidate = 3600;
export async function generateStaticParams() {
return categories.map((category) => ({
category: category,
}));
}
export async function generateMetadata({ params }: PageProps) {
const { category } = await params;
return {
title: `Shadcn ${category} Blocks`,
};
}
export default async function CategoryPage({ params }: PageProps) {
const { category } = await params;
const categoryBlocks = blocks.filter((b) => b.category === category);
if (categoryBlocks.length === 0) {
notFound();
}
return (
<>
<section>
<h1 className="sr-only text-3xl font-bold sm:text-4xl md:text-nowrap">
Shadcn <span className="capitalize">{category}</span> blocks
</h1>
<p className="sr-only text-base md:text-lg">
Speed up your workflow with responsive, pre-built UI blocks designed
for marketing websites.
</p>
<div className="h-6 w-full bg-[repeating-linear-gradient(-45deg,var(--color-border),var(--color-border)_1px,transparent_1px,transparent_6px)] opacity-35"></div>
</section>
{categoryBlocks.map((block, index) => (
<BlockPreview {...block} key={index} />
))}
</>
);
}

View File

@ -0,0 +1,12 @@
import { categories } from '@/components/blocks/blocks';
import BlocksNav from '@/components/blocks/blocks-nav';
import { PropsWithChildren } from 'react';
export default function CategoryLayout({ children }: PropsWithChildren) {
return (
<>
<BlocksNav categories={categories} />
<main>{children}</main>
</>
);
}

View File

@ -0,0 +1,54 @@
import BlockPreview from '@/components/blocks/block-preview';
import { blocks, categories } from '@/components/blocks/blocks';
import { constructMetadata } from '@/lib/metadata';
import { getBaseUrlWithLocale } from '@/lib/urls/get-base-url';
import { Metadata } from 'next';
import { Locale } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import { notFound } from 'next/navigation';
interface BlockCategoryPageProps {
params: Promise<{ category: string }>;
}
export const dynamic = 'force-static';
export const revalidate = 3600;
export async function generateStaticParams() {
return categories.map((category) => ({
category: category,
}));
}
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: Locale; category: string }>;
}): Promise<Metadata | undefined> {
const { locale, category } = await params;
const t = await getTranslations({ locale, namespace: 'Metadata' });
return constructMetadata({
title: category + ' | ' + t('title'),
description: t('description'),
canonicalUrl: `${getBaseUrlWithLocale(locale)}/blocks/${category}`,
});
}
export default async function BlockCategoryPage({
params,
}: BlockCategoryPageProps) {
const { category } = await params;
const categoryBlocks = blocks.filter((b) => b.category === category);
if (categoryBlocks.length === 0) {
notFound();
}
return (
<>
{categoryBlocks.map((block, index) => (
<BlockPreview {...block} key={index} />
))}
</>
);
}

View File

@ -6,9 +6,9 @@ import { notFound } from 'next/navigation';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Toaster } from 'sonner'; import { Toaster } from 'sonner';
import { Providers } from './providers'; import { Providers } from './providers';
import { TailwindIndicator } from '@/components/tailwind-indicator';
import '@/styles/globals.css'; import '@/styles/globals.css';
import { TailwindIndicator } from '@/components/tailwind-indicator';
interface LocaleLayoutProps { interface LocaleLayoutProps {
children: ReactNode; children: ReactNode;
@ -52,7 +52,7 @@ export default async function LocaleLayout({
<Toaster richColors position="top-right" offset={64} /> <Toaster richColors position="top-right" offset={64} />
<TailwindIndicator /> {/* <TailwindIndicator /> */}
</Providers> </Providers>
</NextIntlClientProvider> </NextIntlClientProvider>
</body> </body>

View File

@ -1,43 +1,46 @@
'use client'; 'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation'; import { LocaleLink, useLocalePathname } from '@/i18n/navigation';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
const BlocksNav = ({ categories }: { categories: string[] }) => { export default function BlocksNav({ categories }: { categories: string[] }) {
const pathname = usePathname(); const pathname = useLocalePathname();
return ( return (
<div className="dark:border-border/50 relative z-50 border-b"> <div className="mt-4 dark:border-border/50 relative z-20 border-t">
<div className="mx-auto max-w-7xl"> <div className="mx-auto max-w-7xl">
<nav className="flex items-center lg:-mx-3"> <nav className="flex items-center lg:-mx-3">
<ul className="relative -mb-px flex h-11 snap-x snap-proximity scroll-px-6 items-center gap-6 overflow-x-auto overflow-y-hidden px-6 lg:scroll-px-2 lg:gap-5"> <ul className="relative -mb-px flex h-12 snap-x snap-proximity scroll-px-6 items-center gap-6 overflow-x-auto overflow-y-hidden px-6 lg:scroll-px-2 lg:gap-5">
{categories.map((category) => ( {categories.map((category) => {
<li const href = `/blocks/${category}`;
key={category} const isActive = pathname.startsWith(href);
className={cn(
'flex h-full snap-start items-center border-b border-b-transparent', return (
pathname === `/nsui/${category}` && 'border-primary' <li
)} key={category}
>
<Link
href={`/nsui/${category}`}
prefetch={true}
className={cn( className={cn(
pathname === `/nsui/${category}` && 'text-foreground!', 'flex h-full snap-start items-center border-b border-b-transparent',
'hover:bg-muted dark:text-muted-foreground hover:text-foreground flex h-7 w-fit items-center text-nowrap rounded-full px-1 text-sm text-zinc-700 lg:-mx-2 lg:px-3' isActive && 'border-primary'
)} )}
> >
<span className="block w-max text-nowrap capitalize"> <LocaleLink
{category} href={href}
</span> prefetch={true}
</Link> className={cn(
</li> isActive && 'text-foreground!',
))} 'hover:bg-muted dark:text-muted-foreground hover:text-foreground flex h-7 w-fit items-center text-nowrap rounded-full px-2 text-sm text-zinc-700 lg:-mx-2 lg:px-3'
)}
>
<span className="block w-max text-nowrap capitalize">
{category}
</span>
</LocaleLink>
</li>
)
})}
</ul> </ul>
</nav> </nav>
</div> </div>
</div> </div>
); );
}; }
export default BlocksNav;

View File

@ -35,7 +35,13 @@ import {
SquareKanbanIcon, SquareKanbanIcon,
SquarePenIcon, SquarePenIcon,
ThumbsUpIcon, ThumbsUpIcon,
WandSparklesIcon WandSparklesIcon,
SquareCodeIcon,
UsersIcon,
FootprintsIcon,
SplitSquareVerticalIcon,
LogInIcon,
UserPlusIcon
} from 'lucide-react'; } from 'lucide-react';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
@ -199,9 +205,9 @@ export function getMenuLinks(): NestedMenuItem[] {
external: false, external: false,
}, },
{ {
title: t('Marketing.navbar.blocks.items.pricing.title'), title: t('Marketing.navbar.blocks.items.logo.title'),
icon: <CircleDollarSignIcon className="site-4 shrink-0" />, icon: <SquareCodeIcon className="site-4 shrink-0" />,
href: Routes.PricingBlocks, href: Routes.LogoBlocks,
external: false, external: false,
}, },
{ {
@ -211,15 +217,9 @@ export function getMenuLinks(): NestedMenuItem[] {
external: false, external: false,
}, },
{ {
title: t('Marketing.navbar.blocks.items.faq.title'), title: t('Marketing.navbar.blocks.items.content.title'),
icon: <CircleHelpIcon className="site-4 shrink-0" />, icon: <NewspaperIcon className="site-4 shrink-0" />,
href: Routes.FAQBlocks, href: Routes.ContentBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.testimonials.title'),
icon: <ThumbsUpIcon className="site-4 shrink-0" />,
href: Routes.TestimonialsBlocks,
external: false, external: false,
}, },
{ {
@ -228,6 +228,18 @@ export function getMenuLinks(): NestedMenuItem[] {
href: Routes.StatsBlocks, href: Routes.StatsBlocks,
external: false, external: false,
}, },
{
title: t('Marketing.navbar.blocks.items.team.title'),
icon: <UsersIcon className="site-4 shrink-0" />,
href: Routes.TeamBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.testimonials.title'),
icon: <ThumbsUpIcon className="site-4 shrink-0" />,
href: Routes.TestimonialsBlocks,
external: false,
},
{ {
title: t('Marketing.navbar.blocks.items.callToAction.title'), title: t('Marketing.navbar.blocks.items.callToAction.title'),
icon: <RocketIcon className="site-4 shrink-0" />, icon: <RocketIcon className="site-4 shrink-0" />,
@ -235,9 +247,45 @@ export function getMenuLinks(): NestedMenuItem[] {
external: false, external: false,
}, },
{ {
title: t('Marketing.navbar.blocks.items.content.title'), title: t('Marketing.navbar.blocks.items.footer.title'),
icon: <NewspaperIcon className="site-4 shrink-0" />, icon: <FootprintsIcon className="site-4 shrink-0" />,
href: Routes.ContentBlocks, href: Routes.FooterBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.pricing.title'),
icon: <CircleDollarSignIcon className="site-4 shrink-0" />,
href: Routes.PricingBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.comparator.title'),
icon: <SplitSquareVerticalIcon className="site-4 shrink-0" />,
href: Routes.ComparatorBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.faq.title'),
icon: <CircleHelpIcon className="site-4 shrink-0" />,
href: Routes.FAQBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.login.title'),
icon: <LogInIcon className="site-4 shrink-0" />,
href: Routes.LoginBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.signup.title'),
icon: <UserPlusIcon className="site-4 shrink-0" />,
href: Routes.SignupBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.contact.title'),
icon: <MailIcon className="site-4 shrink-0" />,
href: Routes.ContactBlocks,
external: false, external: false,
}, },
], ],

View File

@ -38,14 +38,22 @@ export enum Routes {
AIVideo = '/ai/video', AIVideo = '/ai/video',
AIAudio = '/ai/audio', AIAudio = '/ai/audio',
// Block routes
HeroBlocks = '/blocks/hero-section', HeroBlocks = '/blocks/hero-section',
PricingBlocks = '/blocks/pricing', LogoBlocks = '/blocks/logo-cloud',
FeaturesBlocks = '/blocks/features', FeaturesBlocks = '/blocks/features',
FAQBlocks = '/blocks/faq',
TestimonialsBlocks = '/blocks/testimonials',
StatsBlocks = '/blocks/stats',
CallToActionBlocks = '/blocks/call-to-action',
ContentBlocks = '/blocks/content', ContentBlocks = '/blocks/content',
StatsBlocks = '/blocks/stats',
TeamBlocks = '/blocks/team',
TestimonialsBlocks = '/blocks/testimonials',
CallToActionBlocks = '/blocks/call-to-action',
FooterBlocks = '/blocks/footer',
PricingBlocks = '/blocks/pricing',
ComparatorBlocks = '/blocks/comparator',
FAQBlocks = '/blocks/faqs',
LoginBlocks = '/blocks/login',
SignupBlocks = '/blocks/signup',
ContactBlocks = '/blocks/contact',
} }
/** /**