x
This commit is contained in:
1 parent
c1c3a43d7d
commit
a5f6e3171b
11 files changed
+351
-13
No files matched your search
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
|
||||
interface DynamicHeroArticleProps {
|
||||
payload: any;
|
||||
}
|
||||
|
||||
export default function DynamicHeroArticle({ payload }: DynamicHeroArticleProps) {
|
||||
const { title, subtitle, imageUrl, author, date, category } = payload || {};
|
||||
|
||||
return (
|
||||
<section className="relative w-full min-h-[60vh] md:min-h-[80vh] flex items-center justify-center bg-spa-sage overflow-hidden">
|
||||
{imageUrl && (
|
||||
<div className="absolute inset-0 z-0">
|
||||
<img src={imageUrl} alt={title || 'Article Hero'} className="w-full h-full object-cover" />
|
||||
<div className="absolute inset-0 bg-black/40" />
|
||||
</div>
|
||||
)}
|
||||
<div className="relative z-10 text-center max-w-4xl px-6 text-white">
|
||||
{category && (
|
||||
<span className="text-sm uppercase tracking-widest font-bold mb-4 block text-spa-gold">
|
||||
{category}
|
||||
</span>
|
||||
)}
|
||||
<h1 className="text-4xl md:text-6xl font-serif mb-6 leading-tight">
|
||||
{title || 'Scent Guide Article'}
|
||||
</h1>
|
||||
{subtitle && (
|
||||
<p className="text-lg md:text-xl font-light opacity-90 max-w-2xl mx-auto mb-8">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
{(author || date) && (
|
||||
<div className="text-sm font-medium tracking-wide uppercase opacity-80 flex items-center justify-center gap-4">
|
||||
{author && <span>By {author}</span>}
|
||||
{author && date && <span>•</span>}
|
||||
{date && <span>{date}</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,10 @@ import DynamicAnnouncementBar from './DynamicAnnouncementBar';
|
||||
import DynamicNewsletter from './DynamicNewsletter';
|
||||
import DynamicEditorialTile from './DynamicEditorialTile';
|
||||
import DynamicEditorialCarousel from './DynamicEditorialCarousel';
|
||||
import DynamicHeroArticle from './DynamicHeroArticle';
|
||||
import DynamicScentNotesBreakdown from './DynamicScentNotesBreakdown';
|
||||
import DynamicQuoteCallout from './DynamicQuoteCallout';
|
||||
import DynamicProductCarousel from './DynamicProductCarousel';
|
||||
|
||||
interface DynamicPageRendererProps {
|
||||
sections: PageSection[];
|
||||
@@ -51,6 +55,14 @@ export default function DynamicPageRenderer({
|
||||
return <DynamicEditorialTile key={section.id} payload={section.payload} />;
|
||||
case 'EDITORIAL_CAROUSEL':
|
||||
return <DynamicEditorialCarousel key={section.id} payload={section.payload} />;
|
||||
case 'HERO_ARTICLE':
|
||||
return <DynamicHeroArticle key={section.id} payload={section.payload} />;
|
||||
case 'SCENT_NOTES_BREAKDOWN':
|
||||
return <DynamicScentNotesBreakdown key={section.id} payload={section.payload} />;
|
||||
case 'QUOTE_CALLOUT':
|
||||
return <DynamicQuoteCallout key={section.id} payload={section.payload} />;
|
||||
case 'PRODUCT_CAROUSEL':
|
||||
return <DynamicProductCarousel key={section.id} payload={section.payload} />;
|
||||
default:
|
||||
console.warn(`[DynamicPageRenderer] Unknown block type: ${section.type}`);
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Autoplay, Pagination, Navigation } from 'swiper/modules';
|
||||
import ProductCard from './ProductCard';
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/pagination';
|
||||
import 'swiper/css/navigation';
|
||||
|
||||
interface DynamicProductCarouselProps {
|
||||
payload: any;
|
||||
}
|
||||
|
||||
export default function DynamicProductCarousel({ payload }: DynamicProductCarouselProps) {
|
||||
const { title, description, products = [] } = payload || {};
|
||||
|
||||
return (
|
||||
<section className="py-20 bg-spa-sage/30 px-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{(title || description) && (
|
||||
<div className="text-center mb-12">
|
||||
{title && <h2 className="text-3xl md:text-5xl font-serif text-spa-forest mb-4">{title}</h2>}
|
||||
{description && <p className="text-lg text-spa-stone/70 max-w-2xl mx-auto">{description}</p>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{products.length === 0 ? (
|
||||
<div className="text-center py-12 text-spa-stone/50 italic">
|
||||
No products to display.
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative pb-12">
|
||||
<Swiper
|
||||
modules={[Autoplay, Pagination, Navigation]}
|
||||
spaceBetween={32}
|
||||
slidesPerView={1}
|
||||
breakpoints={{
|
||||
640: { slidesPerView: 2 },
|
||||
1024: { slidesPerView: 3 },
|
||||
1280: { slidesPerView: 4 }
|
||||
}}
|
||||
pagination={{ clickable: true, bulletActiveClass: 'swiper-pagination-bullet-active !bg-spa-forest' }}
|
||||
navigation
|
||||
autoplay={{ delay: 5000, disableOnInteraction: false }}
|
||||
className="w-full !px-4"
|
||||
>
|
||||
{products.map((p: any) => (
|
||||
<SwiperSlide key={p.id} className="h-auto">
|
||||
<ProductCard product={p} enableQuickShop={true} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
interface DynamicQuoteCalloutProps {
|
||||
payload: any;
|
||||
}
|
||||
|
||||
export default function DynamicQuoteCallout({ payload }: DynamicQuoteCalloutProps) {
|
||||
const { quote, author, role } = payload || {};
|
||||
|
||||
return (
|
||||
<section className="py-24 px-6 bg-white">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<svg className="w-12 h-12 mx-auto text-spa-gold/40 mb-8" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M14.017 21v-7.391c0-5.704 3.731-9.57 8.983-10.609l.995 2.151c-2.432.917-3.995 3.638-3.995 5.849h4v10h-9.983zm-14.017 0v-7.391c0-5.704 3.748-9.57 9-10.609l.996 2.151c-2.433.917-3.996 3.638-3.996 5.849h3.983v10h-9.983z" />
|
||||
</svg>
|
||||
<blockquote className="text-2xl md:text-4xl font-serif text-spa-forest italic leading-relaxed mb-8">
|
||||
"{quote || 'Fragrance is the invisible, unforgettable, ultimate accessory of fashion.'}"
|
||||
</blockquote>
|
||||
{(author || role) && (
|
||||
<div className="flex flex-col items-center">
|
||||
{author && <span className="font-bold uppercase tracking-widest text-spa-stone">{author}</span>}
|
||||
{role && <span className="text-sm text-spa-stone/60 mt-1 uppercase tracking-wide">{role}</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
|
||||
interface DynamicScentNotesBreakdownProps {
|
||||
payload: any;
|
||||
}
|
||||
|
||||
export default function DynamicScentNotesBreakdown({ payload }: DynamicScentNotesBreakdownProps) {
|
||||
const { title, description, topNotes, heartNotes, baseNotes } = payload || {};
|
||||
|
||||
return (
|
||||
<section className="py-20 px-6 bg-spa-light">
|
||||
<div className="max-w-5xl mx-auto text-center">
|
||||
<h2 className="text-3xl md:text-5xl font-serif text-spa-forest mb-6">
|
||||
{title || 'Scent Notes'}
|
||||
</h2>
|
||||
{description && (
|
||||
<p className="text-lg text-spa-stone/80 max-w-2xl mx-auto mb-16">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div className="p-8 bg-white rounded-2xl shadow-sm border border-spa-stone/10">
|
||||
<h3 className="text-xl font-bold uppercase tracking-widest text-spa-forest mb-4">Top Notes</h3>
|
||||
<p className="text-spa-stone font-light leading-relaxed">
|
||||
{topNotes || 'The initial, lighter smells of the fragrance.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-8 bg-white rounded-2xl shadow-sm border border-spa-stone/10">
|
||||
<h3 className="text-xl font-bold uppercase tracking-widest text-spa-forest mb-4">Heart Notes</h3>
|
||||
<p className="text-spa-stone font-light leading-relaxed">
|
||||
{heartNotes || 'The core of the fragrance, emerging just after the top notes.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-8 bg-white rounded-2xl shadow-sm border border-spa-stone/10">
|
||||
<h3 className="text-xl font-bold uppercase tracking-widest text-spa-forest mb-4">Base Notes</h3>
|
||||
<p className="text-spa-stone font-light leading-relaxed">
|
||||
{baseNotes || 'The deepest, richest scents that linger the longest.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user