x
This commit is contained in:
1 parent
d325cd8763
commit
c6bf2f70ac
45 files changed
+5195
-338
No files matched your search
+35
-167
@@ -1,193 +1,61 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import Navbar from '../components/Navbar'
|
||||
import Footer from '../components/Footer'
|
||||
import ProductCard from '../components/ProductCard'
|
||||
import { API_BASE, getCategoryImage, type Category, type Product, type Hero } from '../types'
|
||||
import DynamicPageRenderer from '../components/DynamicPageRenderer'
|
||||
import { API_BASE, type Category, type PageSection } from '../types'
|
||||
|
||||
export default function HomePage() {
|
||||
const [categories, setCategories] = useState<Category[]>([])
|
||||
const [featuredProducts, setFeaturedProducts] = useState<Product[]>([])
|
||||
const [hero, setHero] = useState<Hero | null>(null)
|
||||
const [sections, setSections] = useState<PageSection[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [categoriesLoading, setCategoriesLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const [catRes, prodRes, heroRes] = await Promise.all([
|
||||
fetch(`${API_BASE}/categories`),
|
||||
fetch(`${API_BASE}/products?categoryId=1`),
|
||||
fetch(`${API_BASE}/hero`),
|
||||
])
|
||||
const cats: Category[] = await catRes.json()
|
||||
const prodsJson = await prodRes.json()
|
||||
const heroText = await heroRes.text()
|
||||
const heroData: Hero | null = heroText ? JSON.parse(heroText) : null
|
||||
|
||||
const prods: Product[] = prodsJson.data || []
|
||||
const res = await fetch(`${API_BASE}/categories`)
|
||||
const cats: Category[] = await res.json()
|
||||
setCategories(cats)
|
||||
setFeaturedProducts(prods.slice(0, 3))
|
||||
setHero(heroData)
|
||||
} catch (err) {
|
||||
console.error('API error:', err)
|
||||
console.error('Categories error:', err)
|
||||
} finally {
|
||||
setCategoriesLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchPageLayout = async () => {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/pages/home`)
|
||||
const pageData = await res.json()
|
||||
if (pageData && pageData.sections) {
|
||||
setSections(pageData.sections)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Page layout error:', err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
fetchData()
|
||||
|
||||
fetchCategories()
|
||||
fetchPageLayout()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-pop-bg">
|
||||
<Navbar categories={categories} />
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="relative w-full min-h-[90vh] md:min-h-0 md:aspect-[21/9] flex items-center justify-center overflow-hidden bg-night-900">
|
||||
<div className="absolute inset-0 z-0">
|
||||
<img
|
||||
src={hero?.imageUrl || "/images/hero.png"}
|
||||
className="w-full h-full object-cover scale-[1.02]"
|
||||
alt="Hero Background"
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).style.display = 'none'
|
||||
}}
|
||||
/>
|
||||
{/* Dark gradient overlay for premium vibe */}
|
||||
<div className="absolute inset-0 bg-night-900/40"></div>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-night-900/90 via-night-900/10 to-night-900/30"></div>
|
||||
{loading ? (
|
||||
<div className="min-h-[60vh] flex items-center justify-center">
|
||||
<div className="w-12 h-12 border-4 border-pop-pink border-t-transparent rounded-full animate-spin"></div>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 text-center space-y-6 px-8 animate-fade-in max-w-4xl w-full mx-auto mt-16 md:mt-0">
|
||||
<p className="text-spa-sage font-semibold tracking-[0.3em] text-xs md:text-sm uppercase drop-shadow-md">
|
||||
{hero?.tagline || "Vibrant Elegance Collection"}
|
||||
</p>
|
||||
<h1 className="text-5xl md:text-7xl lg:text-8xl font-light text-white leading-tight tracking-wide drop-shadow-md font-serif">
|
||||
{hero ? (
|
||||
hero.title.split('[').map((part, i) => {
|
||||
if (part.includes(']')) {
|
||||
const [accent, rest] = part.split(']')
|
||||
return (
|
||||
<span key={i}>
|
||||
<span className="text-spa-gold font-medium italic">
|
||||
{accent}
|
||||
</span>
|
||||
{rest}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
return <span key={i}>{part}</span>
|
||||
})
|
||||
) : (
|
||||
<>
|
||||
Inhale <span className="text-spa-gold font-medium italic">Serenity</span>
|
||||
</>
|
||||
)}
|
||||
</h1>
|
||||
<p className="text-spa-light/90 max-w-2xl mx-auto text-lg md:text-xl font-light pt-4 pb-8 drop-shadow-md">
|
||||
{hero?.description || "Crafting premium sensory experiences that transform your space into a sanctuary of peace."}
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-6 items-center justify-center">
|
||||
{/* White solid primary button */}
|
||||
<Link
|
||||
to={hero?.buttonLink || "/shop"}
|
||||
className="px-8 py-4 bg-white text-spa-forest rounded-full font-bold uppercase tracking-widest text-sm hover:bg-spa-sage hover:shadow-xl transition-all duration-300"
|
||||
>
|
||||
{hero?.buttonText || "Explore Collection"}
|
||||
</Link>
|
||||
{/* Outline secondary link */}
|
||||
<a
|
||||
href="#categories"
|
||||
className="px-8 py-4 text-white font-medium uppercase tracking-widest text-sm hover:text-spa-gold flex items-center gap-2 group transition-colors"
|
||||
>
|
||||
Learn More
|
||||
<span className="group-hover:translate-x-1 transition-transform">→</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Categories Showcase */}
|
||||
<section id="categories" className="py-24 px-6 relative overflow-hidden bg-white">
|
||||
<div className="max-w-7xl mx-auto space-y-16 relative z-10">
|
||||
<div className="text-center space-y-4">
|
||||
<h2 className="text-4xl md:text-5xl font-bold tracking-tight text-pop-black">
|
||||
Our Collections
|
||||
</h2>
|
||||
<p className="text-gray-500 max-w-2xl mx-auto">Discover the perfect scent for every moment</p>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-6">
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<div key={i} className="h-80 brutal-box bg-gray-50 animate-pulse" />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-6">
|
||||
{categories.map((cat) => (
|
||||
<Link
|
||||
key={cat.id}
|
||||
to={`/category/${cat.id}`}
|
||||
className="relative h-80 rounded-2xl group overflow-hidden shadow-lg hover:shadow-xl transition-all duration-300"
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent z-10 opacity-80 group-hover:opacity-100 transition-opacity" />
|
||||
<img
|
||||
src={getCategoryImage(cat.name)}
|
||||
className="absolute inset-0 w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
|
||||
alt={cat.name}
|
||||
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
|
||||
/>
|
||||
<div className="absolute bottom-6 left-6 right-6 z-20 text-white">
|
||||
<span className="font-semibold text-xl block mb-1">
|
||||
{cat.name}
|
||||
</span>
|
||||
{cat._count && (
|
||||
<span className="text-sm font-medium text-white/80">
|
||||
{cat._count.products} Products
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Featured Products */}
|
||||
<section className="bg-pop-bg py-24 px-6 relative">
|
||||
<div className="max-w-7xl mx-auto space-y-12 relative z-10">
|
||||
<div className="flex flex-col md:flex-row justify-between items-end gap-6 border-b border-gray-200 pb-6">
|
||||
<div>
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-pop-black">
|
||||
Featured Picks
|
||||
</h2>
|
||||
</div>
|
||||
<Link
|
||||
to="/shop"
|
||||
className="text-pop-pink font-medium hover:text-pop-red transition-colors flex items-center gap-2"
|
||||
>
|
||||
View All Collection →
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{Array.from({ length: 3 }).map((_, i) => (
|
||||
<div key={i} className="brutal-box bg-white animate-pulse h-96" />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{featuredProducts.map((p) => (
|
||||
<div key={p.id}>
|
||||
<ProductCard product={p} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<DynamicPageRenderer
|
||||
sections={sections}
|
||||
categories={categories}
|
||||
categoriesLoading={categoriesLoading}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Footer categories={categories} />
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user