feat: update eversolo site cms and media workflows
This commit is contained in:
@@ -2,12 +2,12 @@ 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 { getPageHeroOverlay, PageHero } from '@/components/site/page-hero'
|
||||
import { cacheCmsQuery } from '@/lib/cms-cache'
|
||||
import { getPayloadClient } from '@/lib/payload'
|
||||
import { buildAbsoluteUrl, buildLocaleAlternates } from '@/lib/seo'
|
||||
import { normalizeUploadUrl } from '@/lib/upload-url'
|
||||
import type { Dealer, Media } from '@/payload-types'
|
||||
import type { Dealer, DealersPage as DealersPageSettings, Media } from '@/payload-types'
|
||||
|
||||
export const revalidate = 300
|
||||
|
||||
@@ -29,141 +29,6 @@ type UploadLike = {
|
||||
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',
|
||||
@@ -215,10 +80,6 @@ const WORLD_MAP_BLOBS = [
|
||||
'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() : ''
|
||||
@@ -242,14 +103,14 @@ function getDealerImage(dealer: Dealer) {
|
||||
if (!media || typeof media === 'number') return null
|
||||
|
||||
const upload = media as UploadLike
|
||||
return normalizeUploadUrl(upload.sizes?.card?.url ?? upload.url ?? upload.thumbnailURL ?? null)
|
||||
return normalizeUploadUrl(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)
|
||||
return normalizeUploadUrl(upload.url ?? upload.thumbnailURL ?? null)
|
||||
}
|
||||
|
||||
function groupDealers(dealers: Dealer[]) {
|
||||
@@ -301,35 +162,38 @@ async function getDealersPageSettingsUncached(locale: Locale) {
|
||||
depth: 1,
|
||||
locale,
|
||||
slug: 'dealersPage',
|
||||
})
|
||||
const defaults = DEALERS_PAGE_DEFAULTS[locale]
|
||||
}) as DealersPageSettings
|
||||
const emptyState = settings.emptyState
|
||||
const hero = settings.hero
|
||||
|
||||
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),
|
||||
ctaHref: settings.businessContact?.ctaHref || null,
|
||||
ctaLabel: settings.businessContact?.ctaLabel || null,
|
||||
eyebrow: settings.businessContact?.eyebrow || null,
|
||||
title: settings.businessContact?.title || null,
|
||||
},
|
||||
emptyState: {
|
||||
message: cmsValue(emptyState?.message, defaults.emptyState.message),
|
||||
storesMessage: cmsValue(emptyState?.storesMessage, defaults.emptyState.storesMessage),
|
||||
message: emptyState?.message || null,
|
||||
storesMessage: emptyState?.storesMessage || null,
|
||||
},
|
||||
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),
|
||||
buttonHref: hero?.buttonHref || null,
|
||||
buttonLabel: hero?.buttonLabel || null,
|
||||
description: hero?.description || null,
|
||||
eyebrow: hero?.eyebrow || null,
|
||||
imageUrl: getUploadImageUrl(hero?.image),
|
||||
mobileImageUrl: getUploadImageUrl(hero?.mobileImage),
|
||||
showOverlay: getPageHeroOverlay(hero),
|
||||
textColor: hero?.textColor || null,
|
||||
textFont: hero?.textFont || null,
|
||||
textPosition: hero?.textPosition || null,
|
||||
title: hero?.title || null,
|
||||
},
|
||||
map: {
|
||||
show: (settings as typeof settings & { map?: { show?: boolean | null } }).map?.show ?? defaults.map.show,
|
||||
show: (settings as typeof settings & { map?: { show?: boolean | null } }).map?.show ?? false,
|
||||
},
|
||||
showDealers: (settings as typeof settings & { showDealers?: boolean | null }).showDealers ?? false,
|
||||
storeLinks:
|
||||
settings.storeLinks
|
||||
?.map((item) => ({
|
||||
@@ -342,10 +206,29 @@ async function getDealersPageSettingsUncached(locale: Locale) {
|
||||
openInNewTab: item.openInNewTab ?? false,
|
||||
}))
|
||||
.filter((item) => item.label || item.href)
|
||||
.slice(0, 4) || defaults.storeLinks,
|
||||
.slice(0, 4) || [],
|
||||
}
|
||||
} catch {
|
||||
return DEALERS_PAGE_DEFAULTS[locale]
|
||||
return {
|
||||
businessContact: { ctaHref: null, ctaLabel: null, eyebrow: null, title: null },
|
||||
emptyState: { message: null, storesMessage: null },
|
||||
hero: {
|
||||
buttonHref: null,
|
||||
buttonLabel: null,
|
||||
description: null,
|
||||
eyebrow: null,
|
||||
imageUrl: null,
|
||||
mobileImageUrl: null,
|
||||
showOverlay: true,
|
||||
textColor: null,
|
||||
textFont: null,
|
||||
textPosition: null,
|
||||
title: null,
|
||||
},
|
||||
map: { show: false },
|
||||
showDealers: false,
|
||||
storeLinks: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,15 +575,15 @@ export default async function WhereToBuyPage({
|
||||
getDealersPageData(locale),
|
||||
getDealersPageSettings(locale),
|
||||
])
|
||||
const hasDealers = dealerDocs.length > 0
|
||||
const showDealers = settings.showDealers
|
||||
const hasStores = storeDocs.length > 0
|
||||
const shouldShowTabs = hasDealers && hasStores
|
||||
const shouldShowTabs = showDealers && hasStores
|
||||
const requestedTab: DealerTab = query?.tab === 'stores' ? 'stores' : 'dealers'
|
||||
const activeTab: DealerTab = shouldShowTabs
|
||||
? requestedTab
|
||||
: hasStores
|
||||
? 'stores'
|
||||
: 'dealers'
|
||||
: showDealers
|
||||
? 'dealers'
|
||||
: 'stores'
|
||||
const dealerItems = dealerDocs.map((dealer) => ({
|
||||
'@type': 'Organization',
|
||||
address: dealer.address || undefined,
|
||||
@@ -713,7 +596,7 @@ export default async function WhereToBuyPage({
|
||||
|
||||
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 ? (
|
||||
{showDealers && dealerItems.length > 0 ? (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
suppressHydrationWarning
|
||||
@@ -736,6 +619,8 @@ export default async function WhereToBuyPage({
|
||||
<PageHero
|
||||
eyebrow={settings.hero.eyebrow}
|
||||
imageUrl={settings.hero.imageUrl}
|
||||
mobileImageUrl={settings.hero.mobileImageUrl}
|
||||
overlay={settings.hero.showOverlay}
|
||||
textColor={settings.hero.textColor}
|
||||
textFont={settings.hero.textFont}
|
||||
textPosition={settings.hero.textPosition}
|
||||
@@ -757,7 +642,7 @@ export default async function WhereToBuyPage({
|
||||
|
||||
{shouldShowTabs ? <DealerTabs activeTab={activeTab} locale={locale} /> : null}
|
||||
|
||||
{activeTab === 'dealers' ? (
|
||||
{showDealers && activeTab === 'dealers' ? (
|
||||
<div className="mt-8 space-y-8">
|
||||
{settings.map.show ? <DealerWorldMap dealers={dealerDocs} locale={locale} /> : null}
|
||||
|
||||
@@ -804,25 +689,35 @@ export default async function WhereToBuyPage({
|
||||
</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}
|
||||
{settings.businessContact.eyebrow ||
|
||||
settings.businessContact.title ||
|
||||
(settings.businessContact.ctaHref && settings.businessContact.ctaLabel) ? (
|
||||
<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>
|
||||
{settings.businessContact.eyebrow ? (
|
||||
<div className="text-sm uppercase tracking-[0.18em] text-text-muted">
|
||||
{settings.businessContact.eyebrow}
|
||||
</div>
|
||||
) : null}
|
||||
{settings.businessContact.title ? (
|
||||
<h2 className="mt-2 text-2xl font-medium tracking-tight">
|
||||
{settings.businessContact.title}
|
||||
</h2>
|
||||
) : null}
|
||||
</div>
|
||||
<h2 className="mt-2 text-2xl font-medium tracking-tight">
|
||||
{settings.businessContact.title}
|
||||
</h2>
|
||||
{settings.businessContact.ctaHref && settings.businessContact.ctaLabel ? (
|
||||
<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>
|
||||
) : null}
|
||||
</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>
|
||||
</section>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user