x
This commit is contained in:
1 parent
64bcd6388a
commit
4f8fa1ae09
8 files changed
+400
-120
No files matched your search
@@ -9,7 +9,7 @@ interface HeroFormProps {
|
||||
getLocalizedValue: (payload: any, field: string) => string;
|
||||
updateLocalizedValue: (field: string, value: string) => void;
|
||||
updatePayloadValue: (field: string, value: any) => void;
|
||||
setUploadTarget: (target: { type: 'main' | 'slide'; index?: number } | null) => void;
|
||||
setUploadTarget: (target: { type: 'main' | 'slide' | 'gallery' | 'tile' | 'seoOgImage' | 'mobileImage'; index?: number } | null) => void;
|
||||
setIsMediaModalOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
@@ -31,12 +31,104 @@ export default function HeroForm({
|
||||
}: HeroFormProps) {
|
||||
const currentOverlayStyle = editingSection.payload?.overlayStyle || 'gradient';
|
||||
const currentOverlayOpacity = editingSection.payload?.overlayOpacity !== undefined ? editingSection.payload.overlayOpacity : 45;
|
||||
const payload = editingSection.payload || {};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Hero image uploading */}
|
||||
<div className="md:col-span-2 space-y-2">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">Background Image</label>
|
||||
{/* Global Dimension & Display Settings */}
|
||||
<div className="md:col-span-2 p-4 rounded-2xl bg-slate-900/60 border border-white/5 space-y-4">
|
||||
<div className="flex items-center gap-2 text-xs font-bold text-cyan-glow uppercase tracking-wider">
|
||||
<Sliders className="w-3.5 h-3.5" />
|
||||
<span>Carousel Dimension & Display Settings</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">
|
||||
Desktop Aspect Ratio (Wide & Short)
|
||||
</label>
|
||||
<select
|
||||
value={payload.aspectRatio || '21/7'}
|
||||
onChange={(e) => updatePayloadValue('aspectRatio', e.target.value)}
|
||||
className="admin-input w-full text-xs"
|
||||
>
|
||||
<option value="21/6">21:6 (Ultra Wide / Extra Short ~320px)</option>
|
||||
<option value="21/7">21:7 (Wide Compact - Beautrium Style ~380px)</option>
|
||||
<option value="21/9">21:9 (Standard Cinematic Wide ~450px)</option>
|
||||
<option value="16/5">16:5 (Wide Banner)</option>
|
||||
<option value="16/6">16:6 (Compact Banner)</option>
|
||||
<option value="4/1">4:1 (Panoramic Ultra Slim)</option>
|
||||
<option value="5/1">5:1 (Ultra Slim Ribbon)</option>
|
||||
<option value="full_screen">Full Screen / Original Hero (~90vh)</option>
|
||||
</select>
|
||||
<p className="text-[9px] text-slate-500">BEAUTRIUM style uses 21:7 or 21:6 for wide banners without eating vertical screen space.</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">
|
||||
Mobile Aspect Ratio
|
||||
</label>
|
||||
<select
|
||||
value={payload.mobileAspectRatio || '16/9'}
|
||||
onChange={(e) => updatePayloadValue('mobileAspectRatio', e.target.value)}
|
||||
className="admin-input w-full text-xs"
|
||||
>
|
||||
<option value="16/9">16:9 (Standard Mobile Widescreen)</option>
|
||||
<option value="4/3">4:3 (Taller Mobile View)</option>
|
||||
<option value="1/1">1:1 (Square Mobile Banner)</option>
|
||||
<option value="21/9">21:9 (Compact Mobile Strip)</option>
|
||||
<option value="full_screen">Full Screen Mobile (~85vh)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5 md:col-span-2">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">
|
||||
Header Spacing (ตำแหน่งเทียบกับ Header Menu เมื่ออยู่บล็อกแรก)
|
||||
</label>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => updatePayloadValue('headerSpacing', 'below_header')}
|
||||
className={`p-3 rounded-xl border text-left transition cursor-pointer flex flex-col gap-1 ${
|
||||
(payload.headerSpacing || 'below_header') === 'below_header'
|
||||
? 'border-cyan-glow bg-cyan-glow/10 text-white'
|
||||
: 'border-white/10 bg-white/5 text-slate-400 hover:border-white/20'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-bold text-white">ต่อจาก Header Menu (แนะนำ)</span>
|
||||
{(payload.headerSpacing || 'below_header') === 'below_header' && (
|
||||
<span className="text-[10px] font-bold text-cyan-glow uppercase">Selected</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-[10px] text-slate-400">เว้นระยะด้านบนอัตโนมัติ ไม่โดน Header Menu ลอยทับแถบแบนเนอร์</p>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => updatePayloadValue('headerSpacing', 'under_header')}
|
||||
className={`p-3 rounded-xl border text-left transition cursor-pointer flex flex-col gap-1 ${
|
||||
payload.headerSpacing === 'under_header'
|
||||
? 'border-cyan-glow bg-cyan-glow/10 text-white'
|
||||
: 'border-white/10 bg-white/5 text-slate-400 hover:border-white/20'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-bold text-white">อยู่ใต้ Header Menu (Overlay)</span>
|
||||
{payload.headerSpacing === 'under_header' && (
|
||||
<span className="text-[10px] font-bold text-cyan-glow uppercase">Selected</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-[10px] text-slate-400">Header เมนูจะโปร่งแสงและลอยทับรูปภาพแบนเนอร์ส่วนบนสุด</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hero Desktop Image */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">Background Image (Desktop)</label>
|
||||
<div
|
||||
className="relative h-40 w-full rounded-2xl overflow-hidden border-2 border-dashed border-white/10 bg-night-950/50 flex flex-col items-center justify-center group cursor-pointer hover:border-cyan-glow/30 transition-all"
|
||||
onClick={() => {
|
||||
@@ -60,6 +152,51 @@ export default function HeroForm({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hero Mobile Image (Optional) */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">
|
||||
Mobile Image (จอมือถือ - เว้นว่างได้)
|
||||
</label>
|
||||
{editingSection.payload?.mobileImageUrl && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
updatePayloadValue('mobileImageUrl', '');
|
||||
}}
|
||||
className="text-[10px] text-rose-400 hover:text-rose-300 transition cursor-pointer"
|
||||
>
|
||||
ลบรูปโมบาย
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="relative h-40 w-full rounded-2xl overflow-hidden border-2 border-dashed border-white/10 bg-night-950/50 flex flex-col items-center justify-center group cursor-pointer hover:border-cyan-glow/30 transition-all"
|
||||
onClick={() => {
|
||||
setUploadTarget({ type: 'mobileImage' });
|
||||
setIsMediaModalOpen(true);
|
||||
}}
|
||||
>
|
||||
{editingSection.payload?.mobileImageUrl ? (
|
||||
<>
|
||||
<img src={editingSection.payload.mobileImageUrl} className="w-full h-full object-cover" alt="Hero Mobile Background" />
|
||||
<div className="absolute inset-0 bg-night-950/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center backdrop-blur-sm">
|
||||
<UploadCloud className="w-10 h-10 text-white" />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-center space-y-2">
|
||||
<ImageIcon className="w-10 h-10 text-slate-700 mx-auto" />
|
||||
<p className="text-xs text-slate-500 font-bold uppercase tracking-widest">
|
||||
{editingSection.payload?.imageUrl ? 'ใช้รูปเดียวกับ Desktop' : 'Select Mobile Image'}
|
||||
</p>
|
||||
<p className="text-[9px] text-slate-500">แนะนำสำหรับสัดส่วน 1:1 หรือ 4:3 บนมือถือ</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{editingSection.payload?.imageUrl && (
|
||||
<div className="md:col-span-2">
|
||||
<FocalPointPicker
|
||||
|
||||
@@ -147,7 +147,7 @@ export default function PageBuilderPage() {
|
||||
const [brands, setBrands] = useState<Brand[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [editingSection, setEditingSection] = useState<Partial<PageSection> | null>(null);
|
||||
const [uploadTarget, setUploadTarget] = useState<{ type: 'main' | 'slide' | 'gallery' | 'tile' | 'seoOgImage'; index?: number } | null>(null);
|
||||
const [uploadTarget, setUploadTarget] = useState<{ type: 'main' | 'slide' | 'gallery' | 'tile' | 'seoOgImage' | 'mobileImage'; index?: number } | null>(null);
|
||||
const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');
|
||||
const [isSavingPageSettings, setIsSavingPageSettings] = useState(false);
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
@@ -160,6 +160,11 @@ export default function PageBuilderPage() {
|
||||
const handleMediaSelect = (url: string) => {
|
||||
if (uploadTarget?.type === 'seoOgImage') {
|
||||
setPage(prev => (prev ? { ...prev, ogImageUrl: url } : prev));
|
||||
} else if (uploadTarget?.type === 'mobileImage') {
|
||||
setEditingSection(prev => {
|
||||
if (!prev) return null;
|
||||
return { ...prev, payload: { ...(prev.payload || {}), mobileImageUrl: url } };
|
||||
});
|
||||
} else if (uploadTarget?.type === 'tile' && typeof uploadTarget.index === 'number') {
|
||||
const idx = uploadTarget.index;
|
||||
setEditingSection(prev => {
|
||||
@@ -733,7 +738,10 @@ export default function PageBuilderPage() {
|
||||
description: 'Transform your room into a peaceful escape.',
|
||||
imageUrl: '/images/hero.png',
|
||||
buttonText: 'Explore',
|
||||
buttonLink: '/shop'
|
||||
buttonLink: '/shop',
|
||||
aspectRatio: '21/7',
|
||||
mobileAspectRatio: '16/9',
|
||||
headerSpacing: 'below_header'
|
||||
};
|
||||
break;
|
||||
case 'HERO_CAROUSEL':
|
||||
|
||||
Reference in new issue
Block a user