feat/fix: checkout email tracing, UI tweaks for add-to-cart, cart page overflow fix, implement hide admin bar setting

This commit is contained in:
Dwindi Ramadhana
2026-02-27 23:15:10 +07:00
parent 687a2318b0
commit a62037d993
22 changed files with 2711 additions and 294 deletions

View File

@@ -81,15 +81,18 @@ export function CanvasSection({
>
{/* Section content with Styles */}
<div
className={cn("relative overflow-hidden rounded-lg", !section.styles?.backgroundColor && "bg-white/50")}
className={cn("relative overflow-hidden rounded-lg", !section.styles?.backgroundColor && !section.styles?.backgroundType && "bg-white/50")}
style={{
backgroundColor: section.styles?.backgroundColor,
...(section.styles?.backgroundType === 'gradient'
? { background: `linear-gradient(${section.styles?.gradientAngle ?? 135}deg, ${section.styles?.gradientFrom || '#9333ea'}, ${section.styles?.gradientTo || '#3b82f6'})` }
: { backgroundColor: section.styles?.backgroundColor }
),
paddingTop: section.styles?.paddingTop,
paddingBottom: section.styles?.paddingBottom,
}}
>
{/* Background Image & Overlay */}
{section.styles?.backgroundImage && (
{section.styles?.backgroundType === 'image' && section.styles?.backgroundImage && (
<>
<div
className="absolute inset-0 z-0 bg-cover bg-center bg-no-repeat"
@@ -101,6 +104,19 @@ export function CanvasSection({
/>
</>
)}
{/* Legacy: show bg image even without backgroundType set */}
{!section.styles?.backgroundType && section.styles?.backgroundImage && (
<>
<div
className="absolute inset-0 z-0 bg-cover bg-center bg-no-repeat"
style={{ backgroundImage: `url(${section.styles.backgroundImage})` }}
/>
<div
className="absolute inset-0 z-0 bg-black"
style={{ opacity: (section.styles?.backgroundOverlay || 0) / 100 }}
/>
</>
)}
{/* Content Wrapper */}
<div className={cn(

View File

@@ -455,90 +455,170 @@ export function InspectorPanel({
{/* Background */}
<div className="space-y-4">
<h4 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">{__('Background')}</h4>
{/* Background Type Selector */}
<div className="space-y-2">
<Label className="text-xs">{__('Background Color')}</Label>
<div className="flex gap-2">
<div className="relative w-8 h-8 rounded border shadow-sm shrink-0 overflow-hidden">
<div className="absolute inset-0" style={{ backgroundColor: selectedSection.styles?.backgroundColor || 'transparent' }} />
<Label className="text-xs">{__('Type')}</Label>
<div className="flex gap-1">
{(['solid', 'gradient', 'image'] as const).map((t) => (
<button
key={t}
onClick={() => onSectionStylesChange({ backgroundType: t })}
className={cn(
'flex-1 text-xs py-1.5 px-2 rounded-md border transition-colors capitalize',
(selectedSection.styles?.backgroundType || 'solid') === t
? 'bg-blue-50 border-blue-300 text-blue-700 font-medium'
: 'bg-white border-gray-200 text-gray-600 hover:bg-gray-50'
)}
>
{t}
</button>
))}
</div>
</div>
{/* Solid Color */}
{(!selectedSection.styles?.backgroundType || selectedSection.styles?.backgroundType === 'solid') && (
<div className="space-y-2">
<Label className="text-xs">{__('Background Color')}</Label>
<div className="flex gap-2">
<div className="relative w-8 h-8 rounded border shadow-sm shrink-0 overflow-hidden">
<div className="absolute inset-0" style={{ backgroundColor: selectedSection.styles?.backgroundColor || 'transparent' }} />
<input
type="color"
className="absolute inset-0 opacity-0 cursor-pointer w-full h-full p-0 border-0"
value={selectedSection.styles?.backgroundColor || '#ffffff'}
onChange={(e) => onSectionStylesChange({ backgroundColor: e.target.value })}
/>
</div>
<input
type="color"
className="absolute inset-0 opacity-0 cursor-pointer w-full h-full p-0 border-0"
value={selectedSection.styles?.backgroundColor || '#ffffff'}
type="text"
placeholder="#FFFFFF"
className="flex-1 h-8 rounded-md border border-input bg-background px-3 py-1 text-sm"
value={selectedSection.styles?.backgroundColor || ''}
onChange={(e) => onSectionStylesChange({ backgroundColor: e.target.value })}
/>
</div>
<input
type="text"
placeholder="#FFFFFF"
className="flex-1 h-8 rounded-md border border-input bg-background px-3 py-1 text-sm"
value={selectedSection.styles?.backgroundColor || ''}
onChange={(e) => onSectionStylesChange({ backgroundColor: e.target.value })}
</div>
)}
{/* Gradient Controls */}
{selectedSection.styles?.backgroundType === 'gradient' && (
<div className="space-y-3">
{/* Live Preview Swatch */}
<div
className="w-full h-12 rounded-lg border shadow-inner"
style={{
background: `linear-gradient(${selectedSection.styles?.gradientAngle ?? 135}deg, ${selectedSection.styles?.gradientFrom || '#9333ea'}, ${selectedSection.styles?.gradientTo || '#3b82f6'})`
}}
/>
</div>
</div>
<div className="space-y-2">
<Label className="text-xs">{__('Background Image')}</Label>
<MediaUploader type="image" onSelect={(url) => onSectionStylesChange({ backgroundImage: url })}>
{selectedSection.styles?.backgroundImage ? (
<div className="relative group cursor-pointer border rounded overflow-hidden h-24 bg-gray-50">
<img src={selectedSection.styles.backgroundImage} alt="Background" className="w-full h-full object-cover" />
<div className="absolute inset-0 bg-black/40 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<span className="text-white text-xs font-medium">{__('Change')}</span>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-xs">{__('From')}</Label>
<div className="flex gap-1.5">
<div className="relative w-8 h-8 rounded border shadow-sm shrink-0 overflow-hidden">
<div className="absolute inset-0" style={{ backgroundColor: selectedSection.styles?.gradientFrom || '#9333ea' }} />
<input
type="color"
className="absolute inset-0 opacity-0 cursor-pointer w-full h-full p-0 border-0"
value={selectedSection.styles?.gradientFrom || '#9333ea'}
onChange={(e) => onSectionStylesChange({ gradientFrom: e.target.value })}
/>
</div>
<input
type="text"
className="flex-1 h-8 rounded-md border border-input bg-background px-2 py-1 text-xs"
value={selectedSection.styles?.gradientFrom || '#9333ea'}
onChange={(e) => onSectionStylesChange({ gradientFrom: e.target.value })}
/>
</div>
<button
onClick={(e) => { e.stopPropagation(); onSectionStylesChange({ backgroundImage: '' }); }}
className="absolute top-1 right-1 bg-white/90 p-1 rounded-full text-gray-600 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"
>
<Trash2 className="w-3 h-3" />
</button>
</div>
) : (
<Button variant="outline" className="w-full h-24 border-dashed flex flex-row gap-2 text-gray-400 font-normal">
<Palette className="w-6 h-6" />
{__('Select Image')}
</Button>
)}
</MediaUploader>
</div>
<div className="space-y-3 pt-2">
<div className="flex items-center justify-between">
<Label className="text-xs">{__('Overlay Opacity')}</Label>
<span className="text-xs text-gray-500">{selectedSection.styles?.backgroundOverlay ?? 0}%</span>
<div className="space-y-1">
<Label className="text-xs">{__('To')}</Label>
<div className="flex gap-1.5">
<div className="relative w-8 h-8 rounded border shadow-sm shrink-0 overflow-hidden">
<div className="absolute inset-0" style={{ backgroundColor: selectedSection.styles?.gradientTo || '#3b82f6' }} />
<input
type="color"
className="absolute inset-0 opacity-0 cursor-pointer w-full h-full p-0 border-0"
value={selectedSection.styles?.gradientTo || '#3b82f6'}
onChange={(e) => onSectionStylesChange({ gradientTo: e.target.value })}
/>
</div>
<input
type="text"
className="flex-1 h-8 rounded-md border border-input bg-background px-2 py-1 text-xs"
value={selectedSection.styles?.gradientTo || '#3b82f6'}
onChange={(e) => onSectionStylesChange({ gradientTo: e.target.value })}
/>
</div>
</div>
</div>
<div className="space-y-1">
<div className="flex items-center justify-between">
<Label className="text-xs">{__('Angle')}</Label>
<span className="text-xs text-gray-500">{selectedSection.styles?.gradientAngle ?? 135}°</span>
</div>
<Slider
value={[selectedSection.styles?.gradientAngle ?? 135]}
min={0}
max={360}
step={15}
onValueChange={(vals) => onSectionStylesChange({ gradientAngle: vals[0] })}
/>
</div>
</div>
<Slider
value={[selectedSection.styles?.backgroundOverlay ?? 0]}
max={100}
step={5}
onValueChange={(vals) => onSectionStylesChange({ backgroundOverlay: vals[0] })}
/>
</div>
)}
{/* Image Background */}
{selectedSection.styles?.backgroundType === 'image' && (
<>
<div className="space-y-2">
<Label className="text-xs">{__('Background Image')}</Label>
<MediaUploader type="image" onSelect={(url) => onSectionStylesChange({ backgroundImage: url })}>
{selectedSection.styles?.backgroundImage ? (
<div className="relative group cursor-pointer border rounded overflow-hidden h-24 bg-gray-50">
<img src={selectedSection.styles.backgroundImage} alt="Background" className="w-full h-full object-cover" />
<div className="absolute inset-0 bg-black/40 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<span className="text-white text-xs font-medium">{__('Change')}</span>
</div>
<button
onClick={(e) => { e.stopPropagation(); onSectionStylesChange({ backgroundImage: '' }); }}
className="absolute top-1 right-1 bg-white/90 p-1 rounded-full text-gray-600 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"
>
<Trash2 className="w-3 h-3" />
</button>
</div>
) : (
<Button variant="outline" className="w-full h-24 border-dashed flex flex-row gap-2 text-gray-400 font-normal">
<Palette className="w-6 h-6" />
{__('Select Image')}
</Button>
)}
</MediaUploader>
</div>
<div className="space-y-3 pt-2">
<div className="flex items-center justify-between">
<Label className="text-xs">{__('Overlay Opacity')}</Label>
<span className="text-xs text-gray-500">{selectedSection.styles?.backgroundOverlay ?? 0}%</span>
</div>
<Slider
value={[selectedSection.styles?.backgroundOverlay ?? 0]}
max={100}
step={5}
onValueChange={(vals) => onSectionStylesChange({ backgroundOverlay: vals[0] })}
/>
</div>
</>
)}
<div className="space-y-2 pt-2">
<Label className="text-xs">{__('Section Height')}</Label>
<Select
value={selectedSection.styles?.heightPreset || 'default'}
onValueChange={(val) => {
// Map presets to padding values
const paddingMap: Record<string, string> = {
'default': '0',
'small': '0',
'medium': '0',
'large': '0',
'screen': '0',
};
const padding = paddingMap[val] || '4rem';
// If screen, we might need a specific flag, but for now lets reuse paddingTop/Bottom or add a new prop.
// To avoid breaking schema, let's use paddingTop as the "preset carrier" or add a new styles prop if possible.
// Since styles key is SectionStyles, let's stick to modifying paddingTop/Bottom for now as a simple preset.
onSectionStylesChange({
paddingTop: padding,
paddingBottom: padding,
heightPreset: val // We'll add this to interface
} as any);
onSectionStylesChange({ heightPreset: val });
}}
>
<SelectTrigger><SelectValue placeholder="Height" /></SelectTrigger>

View File

@@ -11,6 +11,10 @@ export interface SectionProp {
export interface SectionStyles {
backgroundColor?: string;
backgroundType?: 'solid' | 'gradient' | 'image';
gradientFrom?: string;
gradientTo?: string;
gradientAngle?: number; // 0-360
backgroundImage?: string;
backgroundOverlay?: number; // 0-100 opacity
paddingTop?: string;