From 27cc56abaf57633d33d558367df04c663d63429a Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 3 Oct 2026 08:15:21 +0700 Subject: [PATCH] x --- .../category/CategoryHybridCmsEditor.tsx | 517 ++++++++++++++++++ apps/admin/src/pages/CategoryEditPage.tsx | 118 +++- .../migration.sql | 4 + apps/backend/prisma/schema.prisma | 3 + apps/backend/src/routes/categories.ts | 15 + apps/website/next.config.ts | 12 + .../src/app/[lang]/category/[id]/page.tsx | 91 ++- apps/website/src/components/ProductCard.tsx | 2 + .../category/CategoryCatalogContainer.tsx | 350 ++++++++++++ .../category/CategoryFeaturedSeries.tsx | 129 +++++ .../category/CategoryFilterSidebar.tsx | 345 ++++++++++++ .../components/category/CategoryToolbar.tsx | 106 ++++ .../components/category/CategoryUspBar.tsx | 66 +++ packages/types/index.ts | 34 +- 14 files changed, 1757 insertions(+), 35 deletions(-) create mode 100644 apps/admin/src/components/category/CategoryHybridCmsEditor.tsx create mode 100644 apps/backend/prisma/migrations/20261002233517_add_category_hybrid_cms_fields/migration.sql create mode 100644 apps/website/src/components/category/CategoryCatalogContainer.tsx create mode 100644 apps/website/src/components/category/CategoryFeaturedSeries.tsx create mode 100644 apps/website/src/components/category/CategoryFilterSidebar.tsx create mode 100644 apps/website/src/components/category/CategoryToolbar.tsx create mode 100644 apps/website/src/components/category/CategoryUspBar.tsx diff --git a/apps/admin/src/components/category/CategoryHybridCmsEditor.tsx b/apps/admin/src/components/category/CategoryHybridCmsEditor.tsx new file mode 100644 index 0000000..988b63c --- /dev/null +++ b/apps/admin/src/components/category/CategoryHybridCmsEditor.tsx @@ -0,0 +1,517 @@ +import { useState } from 'react' +import { + Sparkles, + Shield, + Gauge, + Wrench, + Zap, + Award, + Flame, + Plus, + Trash2, + Image as ImageIcon, + CheckCircle2, + Tag +} from 'lucide-react' +import type { CategoryFeaturedSeries, CategoryUspBenefit, CategoryFilterConfig } from '@homaroma/types' + +interface CategoryHybridCmsEditorProps { + featuredSeries: CategoryFeaturedSeries + onChangeFeaturedSeries: (val: CategoryFeaturedSeries) => void + uspBenefits: CategoryUspBenefit[] + onChangeUspBenefits: (val: CategoryUspBenefit[]) => void + filterConfig: CategoryFilterConfig + onChangeFilterConfig: (val: CategoryFilterConfig) => void + onOpenMediaModal: (target: 'featured_image' | 'featured_action') => void +} + +const AVAILABLE_ICONS = [ + { id: 'shield', label: 'Shield (Protection)', icon: Shield }, + { id: 'gauge', label: 'Gauge (Performance)', icon: Gauge }, + { id: 'wrench', label: 'Wrench (Durability)', icon: Wrench }, + { id: 'zap', label: 'Zap (Power/Speed)', icon: Zap }, + { id: 'award', label: 'Award (Quality)', icon: Award }, + { id: 'flame', label: 'Flame (Racing)', icon: Flame }, +] + +export default function CategoryHybridCmsEditor({ + featuredSeries, + onChangeFeaturedSeries, + uspBenefits, + onChangeUspBenefits, + filterConfig, + onChangeFilterConfig, + onOpenMediaModal, +}: CategoryHybridCmsEditorProps) { + const [newFeatureTag, setNewFeatureTag] = useState('') + const [activeTab, setActiveTab] = useState<'featured' | 'benefits' | 'filters'>('featured') + + // Feature Tags handlers + const handleAddFeatureTag = () => { + if (!newFeatureTag.trim()) return + const current = featuredSeries.features || [] + onChangeFeaturedSeries({ + ...featuredSeries, + features: [...current, newFeatureTag.trim()] + }) + setNewFeatureTag('') + } + + const handleRemoveFeatureTag = (idx: number) => { + const current = featuredSeries.features || [] + onChangeFeaturedSeries({ + ...featuredSeries, + features: current.filter((_, i) => i !== idx) + }) + } + + // USP Benefits handlers + const handleAddBenefit = () => { + if (uspBenefits.length >= 6) return + onChangeUspBenefits([ + ...uspBenefits, + { + icon: 'shield', + title: 'New Benefit', + description: 'Describe key performance or quality benefit' + } + ]) + } + + const handleUpdateBenefit = (idx: number, patch: Partial) => { + const updated = uspBenefits.map((b, i) => i === idx ? { ...b, ...patch } : b) + onChangeUspBenefits(updated) + } + + const handleRemoveBenefit = (idx: number) => { + onChangeUspBenefits(uspBenefits.filter((_, i) => i !== idx)) + } + + return ( +
+ {/* Header & Tabs */} +
+
+
+ +
+
+

+ Category Presentation & CMS Blocks + + Hybrid CMS + +

+

Configure Featured Series Banner, Benefits USP, and Filter System

+
+
+ + {/* Tab Buttons */} +
+ + + +
+
+ + {/* ── TAB 1: Featured Series Highlight ────────────────────────────── */} + {activeTab === 'featured' && ( +
+ {/* Toggle Enable */} +
onChangeFeaturedSeries({ ...featuredSeries, enabled: !featuredSeries.enabled })} + className={`flex items-center justify-between p-4 rounded-xl border transition-all cursor-pointer select-none ${ + featuredSeries.enabled + ? 'bg-orange-500/10 border-orange-500/40 shadow-sm' + : 'bg-white/[0.02] border-white/10 hover:border-white/20' + }`} + > +
+
+ Enable Featured Series Banner + {featuredSeries.enabled ? ( + + Active + + ) : ( + + Disabled + + )} +
+

+ Displays a prominent showcase banner (Title, Specs, Cutout Product & Lifestyle Action Shot) +

+
+ + +
+ + {featuredSeries.enabled && ( +
+
+ {/* Badge Kicker */} +
+ + onChangeFeaturedSeries({ ...featuredSeries, badge: e.target.value })} + placeholder="e.g. FEATURED SERIES /" + className="admin-input w-full text-sm py-2.5" + /> +
+ + {/* Series Title */} +
+ + onChangeFeaturedSeries({ ...featuredSeries, title: e.target.value })} + placeholder="e.g. ABSORBER PD SERIES" + className="admin-input w-full text-sm py-2.5 font-bold" + /> +
+
+ + {/* Subtitle */} +
+ + onChangeFeaturedSeries({ ...featuredSeries, subtitle: e.target.value })} + placeholder="e.g. RACE PROVEN SUSPENSION SYSTEM" + className="admin-input w-full text-sm py-2.5 font-medium" + /> +
+ + {/* Description */} +
+ +