feat: prepare CMS pages for deployment

This commit is contained in:
Codex
2026-04-29 14:23:00 +08:00
parent 9a55b874a5
commit d4fb7e0973
87 changed files with 5153 additions and 1901 deletions
@@ -0,0 +1,815 @@
import type { Metadata } from 'next'
import Link from 'next/link'
import { setRequestLocale } from 'next-intl/server'
import type { Locale } from '@/i18n/config'
import { PageHero } from '@/components/site/page-hero'
import { getPayloadClient } from '@/lib/payload'
import { buildAbsoluteUrl, buildLocaleAlternates } from '@/lib/seo'
import { normalizeUploadUrl } from '@/lib/upload-url'
import type { Dealer, Media } from '@/payload-types'
export const revalidate = 300
type DealerTab = 'dealers' | 'stores'
type DealerGroup = { dealers: Dealer[]; region: string }
type MapPoint = { code: string; dealers: Dealer[]; region: string; x: number; y: number }
type StoreLink = {
description?: string | null
href?: string | null
image?: number | Media | null
imageUrl?: string | null
label?: string | null
linkLabel?: string | null
openInNewTab?: boolean | null
}
type UploadLike = {
sizes?: Record<string, { url?: string | null } | undefined> | null
thumbnailURL?: string | null
url?: string | null
}
const DEALERS_PAGE_DEFAULTS = {
en: {
businessContact: {
ctaHref: '/en/contact',
ctaLabel: 'Open Contact',
eyebrow: 'Business Contact',
title: 'Distributor and OEM requests now live on the contact page',
},
emptyState: {
message: 'Authorized dealer information is being updated.',
storesMessage: 'Experience store information is being prepared.',
},
map: {
show: true,
},
hero: {
buttonHref: '/en/contact',
buttonLabel: 'Contact Sales',
description:
'Shop online or connect with authorized Eversolo dealers and experience spaces around the world.',
eyebrow: 'Where to Buy',
imageUrl: null,
textColor: null,
textFont: null,
textPosition: 'left',
title: 'Where to Buy',
},
storeLinks: [
{
description: 'Browse Eversolo products and buying options.',
href: '/en/products',
label: 'Products',
linkLabel: 'Learn More',
openInNewTab: false,
},
{
description: 'Contact Eversolo for regional buying support.',
href: '/en/contact',
label: 'Contact Sales',
linkLabel: 'Contact Us',
openInNewTab: false,
},
{
description: 'Find support resources before and after purchase.',
href: '/en/support',
label: 'Support',
linkLabel: 'View Support',
openInNewTab: false,
},
{
description: 'Explore reviews and videos from the Eversolo community.',
href: '/en/reviews',
label: 'Videos & Reviews',
linkLabel: 'Explore',
openInNewTab: false,
},
],
},
zh: {
businessContact: {
ctaHref: '/zh/contact',
ctaLabel: '前往联系我们',
eyebrow: '商务合作',
title: '经销申请与 OEM 需求已移至联系我们页面',
},
emptyState: {
message: '授权经销商信息正在同步中。',
storesMessage: '体验店信息正在整理中。',
},
map: {
show: true,
},
hero: {
buttonHref: '/zh/contact',
buttonLabel: '联系销售',
description: '线上了解购买渠道,或查找 Eversolo 授权经销商与线下体验空间。',
eyebrow: '购买渠道',
imageUrl: null,
textColor: null,
textFont: null,
textPosition: 'left',
title: 'Where to Buy',
},
storeLinks: [
{
description: '浏览 Eversolo 产品与购买信息。',
href: '/zh/products',
label: '产品中心',
linkLabel: '了解更多',
openInNewTab: false,
},
{
description: '提交购买、经销或区域合作咨询。',
href: '/zh/contact',
label: '联系销售',
linkLabel: '联系我们',
openInNewTab: false,
},
{
description: '查看购买前后所需的服务与支持。',
href: '/zh/support',
label: '服务支持',
linkLabel: '查看支持',
openInNewTab: false,
},
{
description: '查看 Eversolo 视频与媒体评价。',
href: '/zh/reviews',
label: '视频与评测',
linkLabel: '查看内容',
openInNewTab: false,
},
],
},
} satisfies Record<
Locale,
{
businessContact: { ctaHref: string; ctaLabel: string; eyebrow: string; title: string }
emptyState: { message: string; storesMessage: string }
hero: {
buttonHref: string
buttonLabel: string
description: string
eyebrow: string
imageUrl?: string | null
textColor: string | null
textFont: 'default' | 'serif' | 'mono' | null
textPosition: 'left' | 'center' | 'right'
title: string
}
map: { show: boolean }
storeLinks: StoreLink[]
}
>
const COUNTRY_CODES_BY_NAME: Record<string, string> = {
Australia: 'AU',
Austria: 'AT',
Canada: 'CA',
China: 'CN',
France: 'FR',
Germany: 'DE',
Italy: 'IT',
Japan: 'JP',
Korea: 'KR',
Singapore: 'SG',
Spain: 'ES',
Thailand: 'TH',
'United Kingdom': 'GB',
'United States': 'US',
: 'CN',
: 'DE',
: 'JP',
: 'SG',
: 'FR',
: 'AU',
: 'US',
: 'GB',
}
const MAP_POINTS: Record<string, { x: number; y: number }> = {
AU: { x: 81, y: 75 },
AT: { x: 49, y: 42 },
CA: { x: 20, y: 28 },
CN: { x: 72, y: 48 },
DE: { x: 49, y: 39 },
ES: { x: 45, y: 49 },
FR: { x: 46, y: 43 },
GB: { x: 44, y: 36 },
IT: { x: 50, y: 48 },
JP: { x: 83, y: 47 },
KR: { x: 78, y: 46 },
SG: { x: 70, y: 65 },
TH: { x: 68, y: 58 },
US: { x: 21, y: 43 },
}
const WORLD_MAP_BLOBS = [
'M13 32 C18 18 36 13 47 21 C54 26 50 42 39 45 C28 49 16 45 13 32 Z',
'M28 49 C36 50 43 55 42 64 C40 76 30 82 24 72 C19 64 21 53 28 49 Z',
'M48 28 C57 16 76 16 87 25 C99 35 96 49 83 53 C70 58 55 51 48 42 C44 37 44 33 48 28 Z',
'M75 54 C84 52 96 58 99 68 C91 73 80 70 75 62 C73 59 73 56 75 54 Z',
'M103 62 C115 55 132 59 140 69 C132 79 113 80 104 72 C100 69 100 65 103 62 Z',
'M83 17 C93 10 109 12 116 22 C111 29 96 31 87 26 C82 23 80 20 83 17 Z',
]
function cmsValue<T>(value: T | null | undefined, fallback: T): T {
return value ?? fallback
}
function resolveCountryCode(dealer: Dealer) {
const maybeCode = 'countryCode' in dealer ? dealer.countryCode : null
const code = typeof maybeCode === 'string' ? maybeCode.trim().toUpperCase() : ''
if (/^[A-Z]{2}$/.test(code)) return code
if (dealer.country && COUNTRY_CODES_BY_NAME[dealer.country]) return COUNTRY_CODES_BY_NAME[dealer.country]
return ''
}
function flagFromCode(code: string) {
if (!/^[A-Z]{2}$/.test(code)) return '•'
return String.fromCodePoint(...Array.from(code).map((letter) => 127397 + letter.charCodeAt(0)))
}
function getDealerImage(dealer: Dealer) {
const image = 'image' in dealer ? dealer.image : null
const media = image || dealer.logo
if (!media || typeof media === 'number') return null
const upload = media as UploadLike
return normalizeUploadUrl(upload.sizes?.card?.url ?? upload.url ?? upload.thumbnailURL ?? null)
}
function getUploadImageUrl(media: number | Media | null | undefined) {
if (!media || typeof media === 'number') return null
const upload = media as UploadLike
return normalizeUploadUrl(upload.sizes?.card?.url ?? upload.url ?? upload.thumbnailURL ?? null)
}
function groupDealers(dealers: Dealer[]) {
const groups = new Map<string, Dealer[]>()
for (const dealer of dealers) {
const key = dealer.region || 'Other'
const existing = groups.get(key) ?? []
existing.push(dealer)
groups.set(key, existing)
}
return [...groups.entries()]
.sort(([left], [right]) => left.localeCompare(right))
.map(([region, groupDealers]) => ({ dealers: groupDealers, region }))
}
function getMapPoints(dealers: Dealer[]) {
const grouped = new Map<string, MapPoint>()
for (const dealer of dealers) {
const code = resolveCountryCode(dealer)
const point = MAP_POINTS[code]
if (!point) continue
const existing = grouped.get(code)
if (existing) {
existing.dealers.push(dealer)
continue
}
grouped.set(code, {
code,
dealers: [dealer],
region: [dealer.country, dealer.region].filter(Boolean).join(' / '),
...point,
})
}
return [...grouped.values()].sort((left, right) => left.region.localeCompare(right.region))
}
async function getDealersPageSettings(locale: Locale) {
const payload = await getPayloadClient()
try {
const settings = await payload.findGlobal({
depth: 1,
locale,
slug: 'dealersPage',
})
const defaults = DEALERS_PAGE_DEFAULTS[locale]
const emptyState = settings.emptyState
return {
businessContact: {
ctaHref: cmsValue(settings.businessContact?.ctaHref, defaults.businessContact.ctaHref),
ctaLabel: cmsValue(settings.businessContact?.ctaLabel, defaults.businessContact.ctaLabel),
eyebrow: cmsValue(settings.businessContact?.eyebrow, defaults.businessContact.eyebrow),
title: cmsValue(settings.businessContact?.title, defaults.businessContact.title),
},
emptyState: {
message: cmsValue(emptyState?.message, defaults.emptyState.message),
storesMessage: cmsValue(emptyState?.storesMessage, defaults.emptyState.storesMessage),
},
hero: {
buttonHref: cmsValue(settings.hero?.buttonHref, defaults.hero.buttonHref),
buttonLabel: cmsValue(settings.hero?.buttonLabel, defaults.hero.buttonLabel),
description: cmsValue(settings.hero?.description, defaults.hero.description),
eyebrow: cmsValue(settings.hero?.eyebrow, defaults.hero.eyebrow),
imageUrl: getUploadImageUrl(settings.hero?.image),
textColor: settings.hero?.textColor || null,
textFont: settings.hero?.textFont || null,
textPosition: settings.hero?.textPosition || defaults.hero.textPosition,
title: cmsValue(settings.hero?.title, defaults.hero.title),
},
map: {
show: (settings as typeof settings & { map?: { show?: boolean | null } }).map?.show ?? defaults.map.show,
},
storeLinks:
settings.storeLinks
?.map((item) => ({
description: item.description || null,
href: item.href || null,
image: item.image || null,
imageUrl: getUploadImageUrl(item.image),
label: item.label || null,
linkLabel: item.linkLabel || null,
openInNewTab: item.openInNewTab ?? false,
}))
.filter((item) => item.label || item.href)
.slice(0, 4) || defaults.storeLinks,
}
} catch {
return DEALERS_PAGE_DEFAULTS[locale]
}
}
async function getDealersPageData(locale: Locale) {
const payload = await getPayloadClient()
const result = await payload.find({
collection: 'dealers',
depth: 1,
limit: 300,
locale,
sort: 'order',
where: {
and: [
{ docLocale: { equals: locale } },
{ isAuthorized: { equals: true } },
{ name: { exists: true } },
],
},
})
const dealerDocs = result.docs.filter((dealer) => {
const type = 'type' in dealer ? dealer.type : 'dealer'
return type !== 'experienceStore'
})
const storeDocs = result.docs.filter((dealer) => {
const type = 'type' in dealer ? dealer.type : 'dealer'
return type === 'experienceStore'
})
return {
dealerDocs,
dealerGroups: groupDealers(dealerDocs),
storeDocs,
}
}
function DealerTabs({
activeTab,
locale,
}: {
activeTab: DealerTab
locale: Locale
}) {
const tabs = [
{ href: `/${locale}/where-to-buy`, label: locale === 'zh' ? '经销商' : 'Dealers', value: 'dealers' },
{
href: `/${locale}/where-to-buy?tab=stores`,
label: locale === 'zh' ? '体验店' : 'Experience Stores',
value: 'stores',
},
] satisfies Array<{ href: string; label: string; value: DealerTab }>
return (
<div className="mt-8 flex flex-wrap gap-2 border-b border-black/10 pb-3">
{tabs.map((tab) => (
<Link
key={tab.value}
href={tab.href}
className={[
'rounded-full px-5 py-2 text-sm font-medium transition',
activeTab === tab.value
? 'bg-black text-white shadow-[0_10px_30px_rgba(15,23,42,0.12)]'
: 'bg-white text-text-secondary ring-1 ring-black/10 hover:text-text-primary',
].join(' ')}
>
{tab.label}
</Link>
))}
</div>
)
}
function StoreLinkCard({ item }: { item: StoreLink }) {
if (!item.href && !item.label) return null
const imageUrl = getUploadImageUrl(item.image)
const content = (
<>
<div className="absolute inset-0">
{imageUrl ? (
<img
alt=""
aria-hidden="true"
className="h-full w-full object-cover transition duration-500 group-hover:scale-[1.025]"
src={imageUrl}
/>
) : (
<div className="h-full w-full bg-[linear-gradient(135deg,#f5f6f6_0%,#e6ecec_100%)]" />
)}
</div>
<div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(255,255,255,0.08)_0%,rgba(255,255,255,0.24)_46%,rgba(255,255,255,0.86)_100%)]" />
<div className="relative z-[1] flex min-h-[120px] flex-col justify-end p-4 md:min-h-[132px] md:p-5">
<div>
{item.label ? (
<h2 className="text-xl font-medium tracking-[-0.03em] text-text-primary md:text-2xl">
{item.label}
</h2>
) : null}
{item.description ? (
<p className="mt-1.5 line-clamp-2 text-sm leading-5 text-text-secondary">
{item.description}
</p>
) : null}
</div>
</div>
</>
)
const className = 'group relative block overflow-hidden bg-subtle focus:outline-none focus-visible:ring-2 focus-visible:ring-black/45'
if (item.href && /^https?:\/\//i.test(item.href)) {
return (
<a className={className} href={item.href} rel="noreferrer" target={item.openInNewTab ? '_blank' : undefined}>
{content}
</a>
)
}
if (item.href) {
return (
<Link className={className} href={item.href}>
{content}
</Link>
)
}
return <div className={className}>{content}</div>
}
function StoreLinksRow({ items }: { items: StoreLink[] }) {
const visibleItems = items.filter((item) => item.label || item.href).slice(0, 4)
if (visibleItems.length === 0) return null
return (
<section className="mt-5 grid gap-3 md:grid-cols-2 xl:grid-cols-4">
{visibleItems.map((item, index) => (
<StoreLinkCard key={`${item.label || 'store-link'}-${index}`} item={item} />
))}
</section>
)
}
function DealerWorldMap({
dealers,
locale,
}: {
dealers: Dealer[]
locale: Locale
}) {
const points = getMapPoints(dealers)
return (
<section className="py-2 md:py-3">
<div className="grid gap-6 lg:grid-cols-[320px_minmax(0,1fr)] lg:items-center">
<div className="lg:pr-4">
<div className="text-xs font-medium uppercase tracking-[0.22em] text-text-muted">
{locale === 'zh' ? '全球网络' : 'Global Network'}
</div>
<h2 className="mt-2 text-3xl font-medium tracking-[-0.04em] text-text-primary md:text-4xl">
{locale === 'zh' ? '按区域连接授权经销商' : 'Authorized partners by region'}
</h2>
<p className="mt-4 max-w-2xl text-sm leading-7 text-text-secondary">
{locale === 'zh'
? '地图用于快速感知覆盖范围,下方按区域列出具体门店与联系方式。'
: 'The map gives a quick sense of coverage, with detailed contacts grouped by region below.'}
</p>
</div>
<div className="relative min-h-[300px] overflow-hidden bg-[#dce3e3] px-5 py-5 md:min-h-[360px]">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_25%_25%,rgba(255,255,255,0.7),transparent_36%),linear-gradient(180deg,rgba(255,255,255,0.45),rgba(255,255,255,0.08))]" />
<div className="absolute inset-0 opacity-45 [background-image:linear-gradient(rgba(15,23,42,0.06)_1px,transparent_1px),linear-gradient(90deg,rgba(15,23,42,0.06)_1px,transparent_1px)] [background-size:44px_44px]" />
<svg
viewBox="0 0 150 90"
className="absolute inset-x-4 top-1/2 h-[78%] -translate-y-1/2 text-[#f8faf9] drop-shadow-[0_16px_26px_rgba(15,23,42,0.12)]"
aria-hidden
>
{WORLD_MAP_BLOBS.map((path) => (
<path key={path} d={path} fill="currentColor" stroke="rgba(15,23,42,0.12)" strokeWidth="0.6" />
))}
<path
d="M10 44 C31 34 47 36 66 42 C87 49 107 41 142 48"
fill="none"
stroke="rgba(15,23,42,0.12)"
strokeDasharray="2 4"
strokeLinecap="round"
strokeWidth="0.8"
/>
</svg>
{points.map((point) => (
<div
key={point.code}
className="group absolute"
style={{ left: `${point.x}%`, top: `${point.y}%` }}
>
<span
className="block h-3.5 w-3.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-black shadow-[0_0_0_7px_rgba(0,0,0,0.08),0_0_22px_rgba(0,0,0,0.22)] transition group-hover:scale-125"
aria-hidden
/>
<div
className="pointer-events-none absolute left-1/2 top-0 z-10 w-64 -translate-x-1/2 -translate-y-[calc(100%+14px)] rounded-[10px] bg-black px-4 py-3 text-white opacity-0 shadow-[0_18px_50px_rgba(15,23,42,0.22)] transition group-hover:opacity-100"
role="tooltip"
>
<div className="flex items-center gap-2 text-xs uppercase tracking-[0.18em] text-white/58">
<span>{flagFromCode(point.code)}</span>
<span>{point.region || point.code}</span>
</div>
<div className="mt-2 text-sm font-medium">
{locale === 'zh' ? `${point.dealers.length} 个经销商` : `${point.dealers.length} partner${point.dealers.length > 1 ? 's' : ''}`}
</div>
<div className="mt-2 space-y-1 text-xs leading-5 text-white/72">
{point.dealers.slice(0, 3).map((dealer) => (
<div key={dealer.id}>{[dealer.name, dealer.city].filter(Boolean).join(' · ')}</div>
))}
</div>
</div>
</div>
))}
{points.length === 0 ? (
<div className="absolute inset-0 flex items-center justify-center px-6 text-center text-sm text-text-secondary">
{locale === 'zh' ? '补充国家代码后,地图会自动显示经销商亮点。' : 'Add country codes to show dealer highlights on the map.'}
</div>
) : null}
</div>
</div>
</section>
)
}
function DealerCard({ dealer, locale }: { dealer: Dealer; locale: Locale }) {
return (
<article className="rounded-[12px] bg-white p-5 shadow-[0_12px_36px_rgba(15,23,42,0.05)] ring-1 ring-black/7">
{dealer.logo && typeof dealer.logo !== 'number' ? (
<div className="mb-4 flex h-10 items-center">
<img
src={normalizeUploadUrl(dealer.logo.url ?? dealer.logo.thumbnailURL ?? null) || ''}
alt={dealer.name}
className="h-9 w-auto object-contain"
/>
</div>
) : null}
<h3 className="text-lg font-medium tracking-[-0.02em]">{dealer.name}</h3>
<div className="mt-3 space-y-2 text-sm leading-7 text-text-secondary">
<p>{[dealer.country, dealer.city].filter(Boolean).join(', ')}</p>
{dealer.address ? <p>{dealer.address}</p> : null}
{dealer.email ? <p>{dealer.email}</p> : null}
{dealer.phone ? <p>{dealer.phone}</p> : null}
</div>
{dealer.website ? (
<div className="mt-4">
<a
href={dealer.website}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center rounded-full border border-black/12 px-4 py-2 text-sm font-medium text-text-primary hover:border-black/25"
>
{locale === 'zh' ? '访问网站' : 'Visit Website'}
</a>
</div>
) : null}
</article>
)
}
function ExperienceStoreCard({ dealer, locale }: { dealer: Dealer; locale: Locale }) {
const code = resolveCountryCode(dealer)
const imageUrl = getDealerImage(dealer)
return (
<article className="overflow-hidden rounded-[14px] bg-white shadow-[0_16px_45px_rgba(15,23,42,0.07)] ring-1 ring-black/7">
<div className="aspect-[16/10] bg-[linear-gradient(135deg,#e5e9e9,#f8f9f9)]">
{imageUrl ? (
<img src={imageUrl} alt={dealer.name} className="h-full w-full object-cover" />
) : null}
</div>
<div className="p-5 md:p-6">
<div className="flex items-center gap-2 text-xs font-medium uppercase tracking-[0.18em] text-text-muted">
<span className="text-base leading-none">{flagFromCode(code)}</span>
<span>{[dealer.country, dealer.city].filter(Boolean).join(' / ')}</span>
</div>
<h2 className="mt-3 text-2xl font-medium tracking-[-0.04em] text-text-primary">
{dealer.name}
</h2>
{dealer.address ? (
<p className="mt-3 text-sm leading-7 text-text-secondary">{dealer.address}</p>
) : null}
<div className="mt-5 flex flex-wrap gap-2 text-sm text-text-secondary">
{dealer.phone ? <span className="rounded-full bg-subtle px-3 py-1.5">{dealer.phone}</span> : null}
{dealer.email ? <span className="rounded-full bg-subtle px-3 py-1.5">{dealer.email}</span> : null}
{dealer.website ? (
<a
href={dealer.website}
target="_blank"
rel="noopener noreferrer"
className="rounded-full bg-black px-3 py-1.5 font-medium text-white"
>
{locale === 'zh' ? '查看详情' : 'View Details'}
</a>
) : null}
</div>
</div>
</article>
)
}
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: Locale }>
}): Promise<Metadata> {
const { locale } = await params
const settings = await getDealersPageSettings(locale)
return {
alternates: buildLocaleAlternates('/where-to-buy', locale),
description: settings.hero.description,
title: settings.hero.title,
}
}
export default async function WhereToBuyPage({
params,
searchParams,
}: {
params: Promise<{ locale: Locale }>
searchParams?: Promise<{ tab?: string }>
}) {
const { locale } = await params
const query = await searchParams
setRequestLocale(locale)
const { dealerDocs, dealerGroups, storeDocs } = await getDealersPageData(locale)
const settings = await getDealersPageSettings(locale)
const hasDealers = dealerDocs.length > 0
const hasStores = storeDocs.length > 0
const shouldShowTabs = hasDealers && hasStores
const requestedTab: DealerTab = query?.tab === 'stores' ? 'stores' : 'dealers'
const activeTab: DealerTab = shouldShowTabs
? requestedTab
: hasStores
? 'stores'
: 'dealers'
const dealerItems = dealerDocs.map((dealer) => ({
'@type': 'Organization',
address: dealer.address || undefined,
areaServed: [dealer.region, dealer.country].filter(Boolean).join(', ') || undefined,
email: dealer.email || undefined,
name: dealer.name,
telephone: dealer.phone || undefined,
url: dealer.website || undefined,
}))
return (
<div className="mx-auto max-w-page px-6 pb-12 pt-4 md:px-12 md:pb-16 md:pt-5">
{dealerItems.length > 0 ? (
<script
type="application/ld+json"
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'ItemList',
itemListElement: dealerItems.map((item, index) => ({
'@type': 'ListItem',
item,
position: index + 1,
})),
name: locale === 'zh' ? 'Eversolo 授权经销商网络' : 'Eversolo Authorized Dealer Network',
url: buildAbsoluteUrl(`/${locale}/where-to-buy`),
}),
}}
/>
) : null}
<PageHero
eyebrow={settings.hero.eyebrow}
imageUrl={settings.hero.imageUrl}
textColor={settings.hero.textColor}
textFont={settings.hero.textFont}
textPosition={settings.hero.textPosition}
size="tall"
title={settings.hero.title}
description={settings.hero.description}
>
{settings.hero.buttonHref && settings.hero.buttonLabel ? (
<Link
href={settings.hero.buttonHref}
className="inline-flex min-h-11 items-center rounded-full bg-white px-5 text-sm font-medium text-black shadow-[0_16px_40px_rgba(0,0,0,0.18)] transition hover:bg-white/88"
>
{settings.hero.buttonLabel}
</Link>
) : null}
</PageHero>
<StoreLinksRow items={settings.storeLinks} />
{shouldShowTabs ? <DealerTabs activeTab={activeTab} locale={locale} /> : null}
{activeTab === 'dealers' ? (
<div className="mt-8 space-y-8">
{settings.map.show ? <DealerWorldMap dealers={dealerDocs} locale={locale} /> : null}
{dealerGroups.map((group: DealerGroup) => (
<section key={group.region}>
<div className="mb-4 flex items-end justify-between gap-4 border-b border-black/10 pb-3">
<div>
<div className="text-xs font-medium uppercase tracking-[0.2em] text-text-muted">
{locale === 'zh' ? '区域' : 'Region'}
</div>
<h2 className="mt-1 text-2xl font-medium tracking-[-0.04em] md:text-3xl">
{group.region}
</h2>
</div>
</div>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
{group.dealers.map((dealer) => (
<DealerCard key={dealer.id} dealer={dealer} locale={locale} />
))}
</div>
</section>
))}
{dealerGroups.length === 0 ? (
<div className="rounded-[12px] border border-dashed border-black/15 bg-subtle/30 px-6 py-10 text-text-secondary">
{settings.emptyState.message}
</div>
) : null}
</div>
) : (
<div className="mt-8">
{storeDocs.length > 0 ? (
<div className="grid gap-6 lg:grid-cols-2">
{storeDocs.map((dealer) => (
<ExperienceStoreCard key={dealer.id} dealer={dealer} locale={locale} />
))}
</div>
) : (
<div className="rounded-[12px] border border-dashed border-black/15 bg-subtle/30 px-6 py-10 text-text-secondary">
{settings.emptyState.storesMessage}
</div>
)}
</div>
)}
<section className="mt-10 rounded-[14px] bg-white px-6 py-6 shadow-[0_16px_45px_rgba(15,23,42,0.05)] ring-1 ring-black/7 md:px-8">
<div className="flex flex-col gap-5 md:flex-row md:items-center md:justify-between">
<div>
<div className="text-sm uppercase tracking-[0.18em] text-text-muted">
{settings.businessContact.eyebrow}
</div>
<h2 className="mt-2 text-2xl font-medium tracking-tight">
{settings.businessContact.title}
</h2>
</div>
<Link
href={settings.businessContact.ctaHref}
className="inline-flex items-center rounded-full bg-black px-5 py-3 text-sm font-medium text-white"
>
{settings.businessContact.ctaLabel}
<span aria-hidden className="ml-2"></span>
</Link>
</div>
</section>
</div>
)
}