x
This commit is contained in:
1 parent
305b593698
commit
2a9dd8ddec
13 files changed
+1231
-4
No files matched your search
@@ -0,0 +1,125 @@
|
||||
import Image from 'next/image'
|
||||
import Link from '../LocalizedLink'
|
||||
import type { CustomBlockComponentProps } from '../../config/customRegistry'
|
||||
import { resolveLocalized } from '../../lib/localization'
|
||||
|
||||
type LocalizedValue = string | Record<string, string>
|
||||
|
||||
type BakeryCategory = {
|
||||
title: LocalizedValue
|
||||
description?: LocalizedValue
|
||||
href: string
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
type ResolvedBakeryCategory = {
|
||||
title: string
|
||||
description: string
|
||||
href: string
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
const defaultItems: BakeryCategory[] = [
|
||||
{
|
||||
title: 'Breads',
|
||||
description: 'Daily loaves, sourdough, and soft sandwich breads.',
|
||||
href: '/shop',
|
||||
},
|
||||
{
|
||||
title: 'Pastries',
|
||||
description: 'Layered butter pastries for breakfast and gifting.',
|
||||
href: '/shop',
|
||||
},
|
||||
{
|
||||
title: 'Cakes',
|
||||
description: 'Celebration cakes and slices for quiet afternoons.',
|
||||
href: '/shop',
|
||||
},
|
||||
{
|
||||
title: 'Cookies',
|
||||
description: 'Small-batch cookies with crisp edges and soft centers.',
|
||||
href: '/shop',
|
||||
},
|
||||
]
|
||||
|
||||
function readString(value: unknown, fallback: string) {
|
||||
return typeof value === 'string' && value.trim() ? value : fallback
|
||||
}
|
||||
|
||||
function readLocalized(value: unknown, lang: string, fallback: string) {
|
||||
const text = resolveLocalized(value as LocalizedValue | null | undefined, lang)
|
||||
return text.trim() ? text : fallback
|
||||
}
|
||||
|
||||
function readItems(value: unknown, lang: string): ResolvedBakeryCategory[] {
|
||||
const sourceItems = Array.isArray(value) ? value : defaultItems
|
||||
|
||||
const items = sourceItems.reduce<ResolvedBakeryCategory[]>((acc, item) => {
|
||||
if (!item || typeof item !== 'object' || Array.isArray(item)) return acc
|
||||
|
||||
const record = item as Record<string, unknown>
|
||||
const title = readLocalized(record.title, lang, '')
|
||||
if (!title) return acc
|
||||
|
||||
acc.push({
|
||||
title,
|
||||
description: readLocalized(record.description, lang, ''),
|
||||
href: readString(record.href, '/shop'),
|
||||
imageUrl: typeof record.imageUrl === 'string' ? record.imageUrl : undefined,
|
||||
})
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
return items.length > 0 ? items : readItems(defaultItems, lang)
|
||||
}
|
||||
|
||||
export default function BakeryCategories({ props, lang }: CustomBlockComponentProps) {
|
||||
const title = readLocalized(props?.title, lang, 'Bakery Categories')
|
||||
const subtitle = readLocalized(props?.subtitle, lang, 'Explore fresh-baked favorites by collection.')
|
||||
const layout = readString(props?.layout, 'grid')
|
||||
const items = readItems(props?.items, lang)
|
||||
const gridClass = layout === 'horizontal'
|
||||
? 'grid-cols-1 md:grid-cols-4'
|
||||
: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4'
|
||||
|
||||
return (
|
||||
<section className="ui-section-band bg-surface-page">
|
||||
<div className="ui-section-shell space-y-12">
|
||||
<div className="mx-auto max-w-2xl space-y-4 text-center">
|
||||
<h2 className="ui-section-title">{title}</h2>
|
||||
<p className="ui-section-subtitle">{subtitle}</p>
|
||||
</div>
|
||||
|
||||
<div className={`grid gap-5 ${gridClass}`}>
|
||||
{items.map((item) => (
|
||||
<Link
|
||||
key={item.title}
|
||||
href={item.href}
|
||||
className="group overflow-hidden rounded-2xl border border-line-primary/20 bg-surface-card shadow-card transition hover:-translate-y-1 hover:shadow-card-hover"
|
||||
>
|
||||
<div className="relative flex aspect-[4/3] items-center justify-center overflow-hidden bg-surface-muted">
|
||||
{item.imageUrl ? (
|
||||
<Image
|
||||
src={item.imageUrl}
|
||||
alt={item.title}
|
||||
fill
|
||||
unoptimized
|
||||
className="object-cover transition duration-700 group-hover:scale-105"
|
||||
/>
|
||||
) : (
|
||||
<span className="font-serif text-6xl font-semibold text-text-main/20">
|
||||
{item.title.slice(0, 1)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-3 p-6">
|
||||
<h3 className="text-xl font-semibold text-text-main">{item.title}</h3>
|
||||
<p className="text-sm leading-relaxed text-muted-text">{item.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import ProductCard from '../ProductCard'
|
||||
import { localizedFromRecord, resolveCategoryName, resolveLocalized, toRecord } from '../../lib/localization'
|
||||
import { useLang } from '../../hooks/useLang'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { customRegistry } from '../../config/customRegistry'
|
||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||
import { Autoplay, Pagination, Navigation } from 'swiper/modules'
|
||||
import 'swiper/css'
|
||||
@@ -1127,6 +1128,24 @@ export default function DynamicPageRenderer({ sections, categories, lang: langPr
|
||||
<ContactFormSection payload={payload} lang={lang} />
|
||||
</ScrollReveal>
|
||||
)
|
||||
case 'CUSTOM': {
|
||||
const componentId = typeof payload.componentId === 'string' ? payload.componentId : ''
|
||||
const props = toRecord(payload.props)
|
||||
const CustomComponent = customRegistry[componentId]
|
||||
|
||||
if (!CustomComponent) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.warn(`[DynamicPageRenderer] Unknown custom component: ${componentId}`)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollReveal key={section.id}>
|
||||
<CustomComponent props={props} lang={lang} />
|
||||
</ScrollReveal>
|
||||
)
|
||||
}
|
||||
default:
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.warn(`[DynamicPageRenderer] Unsupported section type: ${section.type}`)
|
||||
@@ -1137,4 +1156,3 @@ export default function DynamicPageRenderer({ sections, categories, lang: langPr
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user