This commit is contained in:
Your Name committed 2026-10-01 06:59:38 +07:00
1 parent 0ceee3dbff
commit 2d39edbd57
18 files changed
+804 -153

No files matched your search

@@ -1,6 +1,6 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { getCategoryImage, type Category, type CategoriesSectionPayload } from '@homaroma/types';
import { getCategoryImage, resolveCategoryMediaUrl, type Category, type CategoriesSectionPayload } from '@homaroma/types';
import { useTranslation } from 'react-i18next';
import { getLocalizedField } from '../utils/localization';
@@ -53,7 +53,7 @@ export default function DynamicCategories({ payload, categories, loading }: Dyna
>
<div className="absolute inset-0 bg-gradient-to-t from-pop-bg/90 via-pop-bg/40 to-transparent z-10 opacity-80 group-hover:opacity-100 transition-opacity" />
<img
src={cat.imageUrl || getCategoryImage(getLocalizedField(cat.name, lang))}
src={resolveCategoryMediaUrl(cat, 'card_portrait', getCategoryImage(getLocalizedField(cat.name, lang)))}
className="absolute inset-0 w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
alt={getLocalizedField(cat.name, lang)}
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
+21 -8
View File
@@ -5,7 +5,7 @@ import Navbar from '../components/Navbar'
import Footer from '../components/Footer'
import ProductCard from '../components/ProductCard'
import DynamicPageRenderer from '../components/DynamicPageRenderer'
import { API_BASE, getCategoryImage, type Category, type Product, type PageSection } from '../types'
import { API_BASE, getCategoryImage, getCategoryMedia, resolveCategoryMediaUrl, type Category, type Product, type PageSection } from '../types'
import { getLocalizedField } from '../utils/localization'
import { Sparkles, Layers, Grid3X3, ArrowRight } from 'lucide-react'
@@ -101,7 +101,13 @@ export default function CategoryPage() {
const bottomSections = cmsSections.filter(s => s.isActive && s.type.toUpperCase() !== 'ANNOUNCEMENT_BAR' && s.type.toUpperCase() !== 'HERO' && s.type.toUpperCase() !== 'BANNER')
const categoryName = getLocalizedField(category?.name, lang)
const categoryImageUrl = category?.imageUrl || (categoryName ? getCategoryImage(categoryName) : '')
const fallbackImageUrl = categoryName ? getCategoryImage(categoryName) : ''
const categoryThumbnailUrl = resolveCategoryMediaUrl(category, 'thumbnail', fallbackImageUrl)
const heroDesktopUrl = resolveCategoryMediaUrl(category, 'hero_desktop', fallbackImageUrl)
const heroMobileUrl = resolveCategoryMediaUrl(category, 'hero_mobile', heroDesktopUrl)
const heroDesktopMedia = getCategoryMedia(category, 'hero_desktop')
const heroMobileMedia = getCategoryMedia(category, 'hero_mobile')
const hasArtDirectedHero = Boolean(category?.media?.some((item) => item.role === 'hero_desktop' || item.role === 'hero_mobile'))
const layoutType = category?.layoutType || 'split_showcase'
// Determine Product Grid Class based on layout archetype
@@ -130,7 +136,7 @@ export default function CategoryPage() {
<Navbar categories={categories} />
{/* Hero / Banner Area */}
{heroSections.length > 0 ? (
{heroSections.length > 0 && !(layoutType === 'editorial_hero' && hasArtDirectedHero) ? (
<div className="pt-20">
<DynamicPageRenderer
sections={heroSections}
@@ -142,12 +148,19 @@ export default function CategoryPage() {
/* ── Archetype 2: Editorial Hero Layout ── */
<section className="relative overflow-hidden min-h-[380px] md:min-h-[460px] flex items-center justify-center border-b border-white/10 bg-night-950">
{/* Immersive Background Image with Dark Gradient Overlays */}
{categoryImageUrl && (
{heroDesktopUrl && (
<div className="absolute inset-0 z-0">
<img
src={categoryImageUrl}
src={heroDesktopUrl}
alt={categoryName}
className="w-full h-full object-cover object-center filter brightness-[0.45] saturate-125 scale-105 transform transition-transform duration-1000"
className="hidden h-full w-full object-cover filter brightness-[0.45] saturate-125 scale-105 transform transition-transform duration-1000 md:block"
style={{ objectPosition: `${(heroDesktopMedia?.focalX ?? 0.5) * 100}% ${(heroDesktopMedia?.focalY ?? 0.5) * 100}%` }}
/>
<img
src={heroMobileUrl}
alt={categoryName}
className="h-full w-full object-cover filter brightness-[0.45] saturate-125 scale-105 transform transition-transform duration-1000 md:hidden"
style={{ objectPosition: `${(heroMobileMedia?.focalX ?? 0.5) * 100}% ${(heroMobileMedia?.focalY ?? 0.5) * 100}%` }}
/>
<div className="absolute inset-0 bg-gradient-to-t from-night-950 via-night-950/70 to-night-950/40" />
<div className="absolute inset-0 bg-radial-gradient from-transparent via-night-950/40 to-night-950/90" />
@@ -257,10 +270,10 @@ export default function CategoryPage() {
)}
</div>
{categoryImageUrl && (
{categoryThumbnailUrl && (
<div className="relative w-48 h-48 md:w-64 md:h-64 shrink-0 rounded-3xl overflow-hidden shadow-2xl border border-white/10 bg-night-950 group">
<img
src={categoryImageUrl}
src={categoryThumbnailUrl}
alt={categoryName}
className="absolute inset-0 w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}