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:
parent
aefe37068b
commit
af94ab6dd0
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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组件"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -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} />
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
12
src/app/[locale]/(marketing)/blocks/[category]/layout.tsx
Normal file
12
src/app/[locale]/(marketing)/blocks/[category]/layout.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
54
src/app/[locale]/(marketing)/blocks/[category]/page.tsx
Normal file
54
src/app/[locale]/(marketing)/blocks/[category]/page.tsx
Normal 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} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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;
|
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -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',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user