import React, { useState, useEffect, useRef } from 'react'; import { Palette, RotateCcw, Check, Upload } from 'lucide-react'; import { Button, Card, Input } from '../common'; import { GALLERY_THEME_PRESETS, type ThemeConfig } from '../../contexts/ThemeContext'; import { settingsService } from '../../services/settings.service'; import { toast } from 'react-toastify'; import { buildResourceUrl } from '../../utils/url'; interface ThemeCustomizerProps { value: ThemeConfig; onChange: (theme: ThemeConfig) => void; presetName?: string; onPresetChange?: (presetName: string) => void; } export const ThemeCustomizer: React.FC = ({ value, onChange, presetName = 'default', onPresetChange }) => { const [localTheme, setLocalTheme] = useState(value); const [selectedPreset, setSelectedPreset] = useState(presetName); const [customCss, setCustomCss] = useState(value.customCss || ''); const logoInputRef = useRef(null); useEffect(() => { setLocalTheme(value); setCustomCss(value.customCss || ''); }, [value]); useEffect(() => { setSelectedPreset(presetName); }, [presetName]); const handleChange = (key: keyof ThemeConfig, newValue: any) => { const updated = { ...localTheme, [key]: newValue }; setLocalTheme(updated); // Always propagate changes to parent, not just in preview mode onChange(updated); }; const handlePresetSelect = (presetKey: string) => { const preset = GALLERY_THEME_PRESETS[presetKey]; if (preset) { setSelectedPreset(presetKey); setLocalTheme(preset.config); if (onPresetChange) { onPresetChange(presetKey); } // Always propagate preset changes onChange(preset.config); } }; const handleApply = () => { const themeWithCss = { ...localTheme, customCss }; setLocalTheme(themeWithCss); onChange(themeWithCss); }; const handleReset = () => { const defaultPreset = GALLERY_THEME_PRESETS['default']; if (defaultPreset) { setSelectedPreset('default'); setLocalTheme(defaultPreset.config); setCustomCss(''); onChange(defaultPreset.config); if (onPresetChange) { onPresetChange('default'); } } }; const handleLogoUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { try { // Upload to server const logoUrl = await settingsService.uploadLogo(file); // Update theme with the server URL handleChange('logoUrl', logoUrl); toast.success('Logo uploaded successfully'); } catch (error) { console.error('Failed to upload logo:', error); toast.error('Failed to upload logo'); } } }; return (
{/* Preset Themes */}

Preset Themes

{Object.entries(GALLERY_THEME_PRESETS).map(([key, theme]) => ( ))}
{/* Color Customization */}

Colors

handleChange('primaryColor', e.target.value)} className="h-10 w-20 rounded border border-neutral-300" /> handleChange('primaryColor', e.target.value)} placeholder="#5C8762" className="flex-1" />
handleChange('accentColor', e.target.value)} className="h-10 w-20 rounded border border-neutral-300" /> handleChange('accentColor', e.target.value)} placeholder="#22c55e" className="flex-1" />
handleChange('backgroundColor', e.target.value)} className="h-10 w-20 rounded border border-neutral-300" /> handleChange('backgroundColor', e.target.value)} placeholder="#fafafa" className="flex-1" />
handleChange('textColor', e.target.value)} className="h-10 w-20 rounded border border-neutral-300" /> handleChange('textColor', e.target.value)} placeholder="#171717" className="flex-1" />
{/* Typography & Style */}

Typography & Style

{(['none', 'sm', 'md', 'lg'] as const).map((radius) => ( ))}
{/* Logo Upload */}

Branding

{localTheme.logoUrl && ( Custom logo )} {localTheme.logoUrl && ( )}
{/* Custom CSS */}

Custom CSS