x
This commit is contained in:
1 parent
e509e3f0a2
commit
0d4df55d93
7 files changed
+526
-3
No files matched your search
@@ -469,6 +469,42 @@ function TextSection({ payload, lang }: { payload: Record<string, unknown>, lang
|
||||
)
|
||||
}
|
||||
|
||||
function ArticleHeaderSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
const title = localizedFromRecord(payload, 'title', resolvedLang)
|
||||
const subtitle = localizedFromRecord(payload, 'subtitle', resolvedLang)
|
||||
const category = localizedFromRecord(payload, 'category', resolvedLang)
|
||||
const author = localizedFromRecord(payload, 'author', resolvedLang)
|
||||
const imageAlt = localizedFromRecord(payload, 'imageAlt', resolvedLang)
|
||||
const imageUrl = typeof payload.imageUrl === 'string' ? payload.imageUrl : ''
|
||||
const publishDate = typeof payload.publishDate === 'string' ? payload.publishDate : ''
|
||||
|
||||
if (!title) return null
|
||||
|
||||
return (
|
||||
<header className="bg-surface-page ui-section-band">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<div className="mx-auto max-w-4xl text-center">
|
||||
{category && <p className="mb-4 text-sm font-semibold uppercase text-brand-primary">{category}</p>}
|
||||
<h1 className="text-4xl font-semibold leading-tight text-text-main md:text-6xl">{title}</h1>
|
||||
{subtitle && <p className="mx-auto mt-6 max-w-3xl text-lg leading-8 text-muted-text md:text-xl">{subtitle}</p>}
|
||||
{(author || publishDate) && (
|
||||
<p className="mt-6 text-sm text-muted-text">
|
||||
{[author, publishDate].filter(Boolean).join(' · ')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{imageUrl && (
|
||||
<div className="relative mt-10 aspect-[16/9] w-full overflow-hidden rounded-lg bg-surface-card">
|
||||
<Image src={imageUrl} alt={imageAlt || title} fill unoptimized priority sizes="100vw" className="object-cover" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
function ArticleMediaSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
@@ -547,6 +583,159 @@ function ArticleMediaSection({ payload, lang }: { payload: Record<string, unknow
|
||||
)
|
||||
}
|
||||
|
||||
function ArticleGallerySection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
const title = localizedFromRecord(payload, 'title', resolvedLang)
|
||||
const images = Array.isArray(payload.images) ? payload.images : []
|
||||
const featured = payload.layout === 'featured'
|
||||
|
||||
if (images.length < 2) return null
|
||||
|
||||
return (
|
||||
<section className="bg-surface-page ui-section-band">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
{title && <h2 className="mb-8 text-3xl font-semibold text-text-main md:text-4xl">{title}</h2>}
|
||||
<div className={featured ? 'grid gap-4 md:grid-cols-2' : 'grid gap-4 sm:grid-cols-2 lg:grid-cols-3'}>
|
||||
{images.map((image, index) => {
|
||||
const item = toRecord(image)
|
||||
const imageUrl = typeof item.imageUrl === 'string' ? item.imageUrl : ''
|
||||
const alt = localizedFromRecord(item, 'alt', resolvedLang)
|
||||
const caption = localizedFromRecord(item, 'caption', resolvedLang)
|
||||
if (!imageUrl) return null
|
||||
return (
|
||||
<figure key={`${imageUrl}-${index}`} className={featured && index === 0 ? 'md:row-span-2' : undefined}>
|
||||
<div className={`relative w-full overflow-hidden rounded-lg bg-surface-card ${featured && index === 0 ? 'aspect-[4/5] md:h-full md:min-h-[32rem]' : 'aspect-[4/3]'}`}>
|
||||
<Image src={imageUrl} alt={alt} fill unoptimized sizes="(max-width: 768px) 100vw, 50vw" className="object-cover" />
|
||||
</div>
|
||||
{caption && <figcaption className="mt-3 text-sm leading-6 text-muted-text">{caption}</figcaption>}
|
||||
</figure>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function ArticleFactsSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
const title = localizedFromRecord(payload, 'title', resolvedLang)
|
||||
const items = Array.isArray(payload.items) ? payload.items : []
|
||||
|
||||
if (items.length === 0) return null
|
||||
|
||||
return (
|
||||
<section className="bg-surface-card ui-section-band">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
{title && <h2 className="mb-8 text-3xl font-semibold text-text-main md:text-4xl">{title}</h2>}
|
||||
<dl className="grid gap-x-10 gap-y-8 md:grid-cols-2">
|
||||
{items.map((item, index) => {
|
||||
const record = toRecord(item)
|
||||
const itemTitle = localizedFromRecord(record, 'title', resolvedLang)
|
||||
const description = localizedFromRecord(record, 'description', resolvedLang)
|
||||
return (
|
||||
<div key={index} className="border-t border-line-primary/20 pt-5">
|
||||
<dt className="text-lg font-semibold text-text-main">{itemTitle}</dt>
|
||||
{description && <dd className="mt-2 leading-7 text-muted-text">{description}</dd>}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function getVideoEmbedUrl(videoUrl: string) {
|
||||
try {
|
||||
const url = new URL(videoUrl)
|
||||
if (url.hostname === 'youtu.be') return `https://www.youtube.com/embed/${url.pathname.slice(1)}`
|
||||
if (url.hostname === 'youtube.com' || url.hostname.endsWith('.youtube.com')) {
|
||||
const id = url.searchParams.get('v')
|
||||
return id ? `https://www.youtube.com/embed/${id}` : ''
|
||||
}
|
||||
if (url.hostname === 'vimeo.com' || url.hostname.endsWith('.vimeo.com')) {
|
||||
const id = url.pathname.split('/').filter(Boolean).pop()
|
||||
return id ? `https://player.vimeo.com/video/${id}` : ''
|
||||
}
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function ArticleVideoSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
const title = localizedFromRecord(payload, 'title', resolvedLang)
|
||||
const caption = localizedFromRecord(payload, 'caption', resolvedLang)
|
||||
const videoUrl = typeof payload.videoUrl === 'string' ? payload.videoUrl : ''
|
||||
const posterUrl = typeof payload.posterUrl === 'string' ? payload.posterUrl : undefined
|
||||
const embedUrl = getVideoEmbedUrl(videoUrl)
|
||||
const aspectClass = payload.aspectRatio === '4:3' ? 'aspect-[4/3]' : payload.aspectRatio === '1:1' ? 'aspect-square' : 'aspect-video'
|
||||
|
||||
if (!videoUrl) return null
|
||||
|
||||
return (
|
||||
<section className="bg-surface-page ui-section-band">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
{title && <h2 className="mb-8 text-3xl font-semibold text-text-main md:text-4xl">{title}</h2>}
|
||||
<div className={`${aspectClass} overflow-hidden rounded-lg bg-text-main`}>
|
||||
{embedUrl ? (
|
||||
<iframe
|
||||
src={embedUrl}
|
||||
title={title || caption || 'Article video'}
|
||||
className="h-full w-full"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
) : (
|
||||
<video src={videoUrl} poster={posterUrl} controls preload="metadata" className="h-full w-full object-contain" />
|
||||
)}
|
||||
</div>
|
||||
{caption && <p className="mt-3 text-sm leading-6 text-muted-text">{caption}</p>}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function ArticleReferencesSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
const title = localizedFromRecord(payload, 'title', resolvedLang)
|
||||
const items = Array.isArray(payload.items) ? payload.items : []
|
||||
|
||||
if (items.length === 0) return null
|
||||
|
||||
return (
|
||||
<section className="bg-surface-page ui-section-band">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<h2 className="mb-6 text-2xl font-semibold text-text-main">{title || 'References'}</h2>
|
||||
<ol className="list-decimal space-y-4 pl-5 text-muted-text">
|
||||
{items.map((item, index) => {
|
||||
const record = toRecord(item)
|
||||
const itemTitle = localizedFromRecord(record, 'title', resolvedLang)
|
||||
const note = localizedFromRecord(record, 'note', resolvedLang)
|
||||
const url = typeof record.url === 'string' ? record.url : ''
|
||||
return (
|
||||
<li key={index} className="pl-2">
|
||||
{url ? (
|
||||
<a href={url} target="_blank" rel="noreferrer" className="font-medium text-text-main underline decoration-line-primary underline-offset-4">
|
||||
{itemTitle}
|
||||
</a>
|
||||
) : <span className="font-medium text-text-main">{itemTitle}</span>}
|
||||
{note && <p className="mt-1 text-sm leading-6">{note}</p>}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function NewsletterSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
|
||||
const i18nLang = useLang()
|
||||
const resolvedLang = lang === 'all' ? i18nLang : lang
|
||||
@@ -1184,12 +1373,22 @@ export default function DynamicPageRenderer({ sections, categories, lang: langPr
|
||||
<TextSection payload={payload} lang={lang} />
|
||||
</ScrollReveal>
|
||||
)
|
||||
case 'ARTICLE_HEADER':
|
||||
return <ArticleHeaderSection key={section.id} payload={payload} lang={lang} />
|
||||
case 'ARTICLE_MEDIA':
|
||||
return (
|
||||
<ScrollReveal key={section.id}>
|
||||
<ArticleMediaSection payload={payload} lang={lang} />
|
||||
</ScrollReveal>
|
||||
)
|
||||
case 'ARTICLE_GALLERY':
|
||||
return <ArticleGallerySection key={section.id} payload={payload} lang={lang} />
|
||||
case 'ARTICLE_FACTS':
|
||||
return <ArticleFactsSection key={section.id} payload={payload} lang={lang} />
|
||||
case 'ARTICLE_VIDEO':
|
||||
return <ArticleVideoSection key={section.id} payload={payload} lang={lang} />
|
||||
case 'ARTICLE_REFERENCES':
|
||||
return <ArticleReferencesSection key={section.id} payload={payload} lang={lang} />
|
||||
case 'SCENT_NOTES_BREAKDOWN':
|
||||
return (
|
||||
<ScrollReveal key={section.id}>
|
||||
|
||||
Reference in new issue
Block a user