This commit is contained in:
Your Name committed 2026-07-24 20:08:21 -04:00
1 parent ac08f4490b
commit f8da2cceaf
8 files changed
+427 -2

No files matched your search

@@ -2,7 +2,7 @@
import Image from 'next/image'
import Link from '../LocalizedLink'
import { Clock, Mail, MapPin, MessageCircle, Send, ChevronLeft, ChevronRight, Leaf, Droplet, Sparkles, Gift, CheckCircle, ShieldCheck, Heart } from 'lucide-react'
import { Clock, Mail, MapPin, MessageCircle, Send, ChevronLeft, ChevronRight, Leaf, Droplet, Sparkles, Gift, CheckCircle, ShieldCheck, Heart, Download, FileText } from 'lucide-react'
import { useState, useRef } from 'react'
import type { Category, PageSection, Product } from '@homaroma/types'
import { getCategoryImage } from '@homaroma/types'
@@ -736,6 +736,128 @@ function ArticleReferencesSection({ payload, lang }: { payload: Record<string, u
)
}
function ArticleTableSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
const i18nLang = useLang()
const resolvedLang = lang === 'all' ? i18nLang : lang
const title = localizedFromRecord(payload, 'title', resolvedLang)
const note = localizedFromRecord(payload, 'note', resolvedLang)
const columns = Array.isArray(payload.columns) ? payload.columns : []
const rows = Array.isArray(payload.rows) ? payload.rows : []
if (columns.length < 2 || rows.length === 0) 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="overflow-x-auto rounded-lg border border-line-primary/20">
<table className="w-full min-w-[42rem] border-collapse text-left">
<thead className="bg-surface-card">
<tr>
<th className="px-5 py-4 text-sm font-semibold text-text-main" scope="col" />
{columns.map((column, index) => (
<th key={index} className="px-5 py-4 text-sm font-semibold text-text-main" scope="col">
{localizedValue(column, resolvedLang)}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, rowIndex) => {
const record = toRecord(row)
const cells = Array.isArray(record.cells) ? record.cells : []
return (
<tr key={rowIndex} className="border-t border-line-primary/20">
<th className="px-5 py-4 font-semibold text-text-main" scope="row">
{localizedFromRecord(record, 'label', resolvedLang)}
</th>
{cells.map((cell, cellIndex) => (
<td key={cellIndex} className="px-5 py-4 leading-7 text-muted-text">
{localizedValue(cell, resolvedLang)}
</td>
))}
</tr>
)
})}
</tbody>
</table>
</div>
{note && <p className="mt-4 text-sm leading-6 text-muted-text">{note}</p>}
</div>
</section>
)
}
function ArticleDownloadSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
const i18nLang = useLang()
const resolvedLang = lang === 'all' ? i18nLang : lang
const title = localizedFromRecord(payload, 'title', resolvedLang)
const description = localizedFromRecord(payload, 'description', resolvedLang)
const files = Array.isArray(payload.files) ? payload.files : []
if (files.length === 0) return null
return (
<section className="bg-surface-card ui-section-band">
<div className="mx-auto max-w-5xl">
{title && <h2 className="text-3xl font-semibold text-text-main md:text-4xl">{title}</h2>}
{description && <p className="mt-4 max-w-3xl leading-7 text-muted-text">{description}</p>}
<div className="mt-8 divide-y divide-line-primary/20 border-y border-line-primary/20">
{files.map((file, index) => {
const record = toRecord(file)
const fileTitle = localizedFromRecord(record, 'title', resolvedLang)
const url = typeof record.url === 'string' ? record.url : ''
const meta = [record.fileType, record.fileSize].filter((value) => typeof value === 'string' && value).join(' · ')
if (!url) return null
return (
<a key={index} href={url} target="_blank" rel="noreferrer" className="flex items-center gap-4 py-5 text-text-main transition-opacity hover:opacity-70">
<FileText className="h-6 w-6 shrink-0 text-brand-primary" aria-hidden="true" />
<span className="min-w-0 flex-1">
<span className="block font-semibold">{fileTitle}</span>
{meta && <span className="mt-1 block text-sm text-muted-text">{meta}</span>}
</span>
<Download className="h-5 w-5 shrink-0" aria-hidden="true" />
</a>
)
})}
</div>
</div>
</section>
)
}
function ArticleCtaSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
const i18nLang = useLang()
const resolvedLang = lang === 'all' ? i18nLang : lang
const title = localizedFromRecord(payload, 'title', resolvedLang)
const description = localizedFromRecord(payload, 'description', resolvedLang)
const primaryText = localizedFromRecord(payload, 'primaryText', resolvedLang)
const secondaryText = localizedFromRecord(payload, 'secondaryText', resolvedLang)
const primaryLink = typeof payload.primaryLink === 'string' ? payload.primaryLink : ''
const secondaryLink = typeof payload.secondaryLink === 'string' ? payload.secondaryLink : ''
const centered = payload.alignment !== 'left'
if (!title || !primaryText || !primaryLink) return null
const primaryClass = "inline-flex min-h-12 items-center justify-center rounded-lg bg-brand-primary px-6 py-3 font-semibold text-surface-card transition-opacity hover:opacity-85"
const secondaryClass = "inline-flex min-h-12 items-center justify-center rounded-lg border border-line-primary px-6 py-3 font-semibold text-text-main transition-colors hover:bg-surface-page"
return (
<section className="bg-surface-card ui-section-band">
<div className={`mx-auto max-w-5xl ${centered ? 'text-center' : 'text-left'}`}>
<h2 className="text-3xl font-semibold text-text-main md:text-4xl">{title}</h2>
{description && <p className={`mt-4 max-w-2xl leading-7 text-muted-text ${centered ? 'mx-auto' : ''}`}>{description}</p>}
<div className={`mt-8 flex flex-wrap gap-3 ${centered ? 'justify-center' : 'justify-start'}`}>
{primaryLink.startsWith('/') ? <Link href={primaryLink} className={primaryClass}>{primaryText}</Link> : <a href={primaryLink} className={primaryClass}>{primaryText}</a>}
{secondaryText && secondaryLink && (
secondaryLink.startsWith('/') ? <Link href={secondaryLink} className={secondaryClass}>{secondaryText}</Link> : <a href={secondaryLink} className={secondaryClass}>{secondaryText}</a>
)}
</div>
</div>
</section>
)
}
function NewsletterSection({ payload, lang }: { payload: Record<string, unknown>, lang: string }) {
const i18nLang = useLang()
const resolvedLang = lang === 'all' ? i18nLang : lang
@@ -1389,6 +1511,12 @@ export default function DynamicPageRenderer({ sections, categories, lang: langPr
return <ArticleVideoSection key={section.id} payload={payload} lang={lang} />
case 'ARTICLE_REFERENCES':
return <ArticleReferencesSection key={section.id} payload={payload} lang={lang} />
case 'ARTICLE_TABLE':
return <ArticleTableSection key={section.id} payload={payload} lang={lang} />
case 'ARTICLE_DOWNLOAD':
return <ArticleDownloadSection key={section.id} payload={payload} lang={lang} />
case 'ARTICLE_CTA':
return <ArticleCtaSection key={section.id} payload={payload} lang={lang} />
case 'SCENT_NOTES_BREAKDOWN':
return (
<ScrollReveal key={section.id}>