feat(cms): add Cinematic Story Scroll and Apple Showcase blocks to Page Builder

This commit is contained in:
Your Name committed 2026-10-17 09:06:44 +07:00
1 parent 25cbe3549e
commit 8d9f2fc587
10 files changed
+971 -8

No files matched your search

@@ -9,6 +9,9 @@ interface Props {
product: Product
heroImage?: string
lang?: string
endCtaTitle?: string
endCtaButtonText?: string
endCtaLink?: string
}
interface HighlightCard {
@@ -184,7 +187,14 @@ const DEEP_FEATURES: DeepFeature[] = [
},
]
export default function AppleWatchShowcase({ product, heroImage, lang = 'th' }: Props) {
export default function AppleWatchShowcase({
product,
heroImage,
lang = 'th',
endCtaTitle,
endCtaButtonText,
endCtaLink,
}: Props) {
const productName = resolveLocalized(product.name, lang)
const mainImage = heroImage || product.images?.[0]?.url || '/placeholder.jpg'
@@ -231,9 +241,15 @@ export default function AppleWatchShowcase({ product, heroImage, lang = 'th' }:
const currentHighlight = HIGHLIGHTS[activeHighlight]
const scrollToPurchase = () => {
if (endCtaLink) {
window.location.href = endCtaLink
return
}
const el = document.getElementById('product-purchase-top')
if (el) {
el.scrollIntoView({ behavior: 'smooth' })
} else {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
}
@@ -619,8 +635,7 @@ export default function AppleWatchShowcase({ product, heroImage, lang = 'th' }:
<div className="max-w-3xl mx-auto space-y-6">
<p className="text-xs font-bold uppercase tracking-widest text-slate-500">READY TO UPGRADE</p>
<h2 className="text-4xl sm:text-6xl font-black tracking-tight text-white">
อัปเกรดระบบเบรก<br />
สู่มาตรฐานระดับโลก
{endCtaTitle ? endCtaTitle : <>อัปเกรดระบบเบรก<br />สู่มาตรฐานระดับโลก</>}
</h2>
<p className="text-base sm:text-lg text-slate-400 leading-relaxed">
พร้อมรับประกันคุณภาพและความเข้ากันได้กับรถของคุณ สั่งซื้อวันนี้เพื่อสัมผัสความมั่นใจในทุกระยะหยุด
@@ -632,7 +647,7 @@ export default function AppleWatchShowcase({ product, heroImage, lang = 'th' }:
onClick={scrollToPurchase}
className="inline-flex items-center justify-center rounded-2xl bg-white px-10 py-4 text-base font-black text-slate-950 hover:bg-slate-200 active:scale-95 transition-all shadow-2xl shadow-white/20"
>
เลือกซื้อรุ่นของคุณ (Select Model) ↑
{endCtaButtonText || 'เลือกซื้อรุ่นของคุณ (Select Model) ↑'}
</button>
</div>
</div>
@@ -24,6 +24,7 @@ interface Props {
lang: string
endCtaTitle?: string
endCtaButtonText?: string
endCtaLink?: string
storyTheme?: 'dark' | 'light' | string
}
@@ -89,6 +90,7 @@ export default function CinematicStoryScroll({
lang,
endCtaTitle,
endCtaButtonText,
endCtaLink,
storyTheme = 'light',
}: Props) {
const containerRef = useRef<HTMLDivElement | null>(null)
@@ -212,6 +214,10 @@ export default function CinematicStoryScroll({
}, [activeIndex, activeScenes.length, scrollToScene])
const scrollToPurchase = () => {
if (endCtaLink) {
window.location.href = endCtaLink
return
}
const el = document.getElementById('product-purchase-top')
if (el) {
el.scrollIntoView({ behavior: 'smooth' })
@@ -16,6 +16,8 @@ import { Swiper, SwiperSlide } from 'swiper/react'
import { Autoplay, Pagination, Navigation } from 'swiper/modules'
import ScrollReveal from '../ScrollReveal'
import PageDivider from '../PageDivider'
import CinematicStoryScroll from '../CinematicStoryScroll'
import AppleWatchShowcase from '../AppleWatchShowcase'
import 'swiper/css'
import 'swiper/css/pagination'
import 'swiper/css/navigation'
@@ -2600,6 +2602,11 @@ export default function DynamicPageRenderer({ sections, categories, lang: langPr
'HERO_SPLIT_MINIMAL',
'HERO_VIDEO_MINIMAL',
'HERO_CAROUSEL',
'CINEMATIC_STORY_SCROLL',
'STORY_SCROLL',
'APPLE_SHOWCASE',
'APPLE_KEYNOTE_SHOWCASE',
'APPLE_WATCH_TOUR',
]), [])
const firstActiveSection = sections.find(s => s.isActive && s.type.toUpperCase() !== 'ANNOUNCEMENT_BAR' && s.type.toUpperCase() !== 'ANNOUNCEMENT_BAR_MINIMAL')
@@ -2831,6 +2838,96 @@ export default function DynamicPageRenderer({ sections, categories, lang: langPr
</ScrollReveal>
)
}
case 'CINEMATIC_STORY_SCROLL':
case 'STORY_SCROLL': {
const rawTheme = payload.storyTheme as string | undefined
const storyTheme = rawTheme === 'dark' ? 'dark' : 'light'
const storyHeroImage = (payload.storyHeroImage as string) || (payload.heroImage as string) || undefined
const storyScenes = Array.isArray(payload.storyScenes) ? (payload.storyScenes as any[]) : undefined
const endCtaTitle = (payload.endCtaTitle as string) || undefined
const endCtaButtonText = (payload.endCtaButtonText as string) || undefined
const endCtaLink = (payload.endCtaLink as string) || undefined
const targetProductId = Number(payload.productId) || 0
const foundProduct = targetProductId
? (categories.flatMap(c => (c as any).products || []) as Product[]).find(p => p.id === targetProductId)
: undefined
const mockProduct: Product = foundProduct || {
id: targetProductId || 1,
name: { en: 'Product Story', th: 'การนำเสนอผลิตภัณฑ์' },
description: { en: 'High Performance Story', th: 'การขับขี่สมรรถนะสูง' },
details: null,
price: 0,
brandId: 1,
categoryId: 1,
images: storyHeroImage ? [{ id: 1, url: storyHeroImage, order: 0 }] : [],
variants: [],
options: [
{
id: 1,
name: { en: 'Model', th: 'รุ่น' },
values: ['NVX155', 'LC135', 'Vario125', 'Vario150', 'PCX150', 'Beat110', 'Wave110-I', 'R15'] as any
}
] as any,
category: { id: 1, name: 'Braking' } as any,
brand: { id: 1, name: 'Brand Story' } as any,
}
return (
<div key={section.id} className="w-full">
<CinematicStoryScroll
product={mockProduct}
heroImage={storyHeroImage}
scenes={storyScenes}
lang={lang}
endCtaTitle={endCtaTitle}
endCtaButtonText={endCtaButtonText}
endCtaLink={endCtaLink}
storyTheme={storyTheme}
/>
</div>
)
}
case 'APPLE_SHOWCASE':
case 'APPLE_KEYNOTE_SHOWCASE':
case 'APPLE_WATCH_TOUR': {
const storyHeroImage = (payload.heroImage as string) || (payload.storyHeroImage as string) || undefined
const targetProductId = Number(payload.productId) || 0
const foundProduct = targetProductId
? (categories.flatMap(c => (c as any).products || []) as Product[]).find(p => p.id === targetProductId)
: undefined
const mockProduct: Product = foundProduct || {
id: targetProductId || 1,
name: { en: 'Keynote Showcase', th: 'การนำเสนอผลิตภัณฑ์' },
description: { en: 'Next-Gen Performance', th: 'นวัตกรรมมาตรฐานระดับโลก' },
details: null,
price: 0,
brandId: 1,
categoryId: 1,
images: storyHeroImage ? [{ id: 1, url: storyHeroImage, order: 0 }] : [],
variants: [],
category: { id: 1, name: 'Technology' } as any,
brand: { id: 1, name: 'Keynote' } as any,
}
return (
<div key={section.id} className="w-full">
<AppleWatchShowcase
product={mockProduct}
heroImage={storyHeroImage}
lang={lang}
endCtaTitle={payload.endCtaTitle as string | undefined}
endCtaButtonText={payload.endCtaButtonText as string | undefined}
endCtaLink={payload.endCtaLink as string | undefined}
/>
</div>
)
}
default:
if (process.env.NODE_ENV !== 'production') {
console.warn(`[DynamicPageRenderer] Unsupported section type: ${section.type}`)