mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
Add collapsible theme sections and Russian translations
- Translated Quick Presets to Russian (Быстрые пресеты) - Translated preset names: Стандарт, Океан, Лес, Закат, Фиолет, Роза, Полночь, Бирюза - Made theme color sections collapsible (Accent, Dark Theme, Light Theme, Status Colors) - Each section shows color preview when collapsed
This commit is contained in:
@@ -134,11 +134,11 @@ const META_CATEGORIES: Record<string, { label: string; emoji: string; categories
|
|||||||
// Theme presets - predefined color schemes
|
// Theme presets - predefined color schemes
|
||||||
const THEME_PRESETS: { name: string; colors: ThemeColors }[] = [
|
const THEME_PRESETS: { name: string; colors: ThemeColors }[] = [
|
||||||
{
|
{
|
||||||
name: 'Default Blue',
|
name: 'Стандарт',
|
||||||
colors: DEFAULT_THEME_COLORS,
|
colors: DEFAULT_THEME_COLORS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Ocean',
|
name: 'Океан',
|
||||||
colors: {
|
colors: {
|
||||||
accent: '#0ea5e9',
|
accent: '#0ea5e9',
|
||||||
darkBackground: '#0c1222',
|
darkBackground: '#0c1222',
|
||||||
@@ -155,7 +155,7 @@ const THEME_PRESETS: { name: string; colors: ThemeColors }[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Forest',
|
name: 'Лес',
|
||||||
colors: {
|
colors: {
|
||||||
accent: '#22c55e',
|
accent: '#22c55e',
|
||||||
darkBackground: '#0a1a0f',
|
darkBackground: '#0a1a0f',
|
||||||
@@ -172,7 +172,7 @@ const THEME_PRESETS: { name: string; colors: ThemeColors }[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Sunset',
|
name: 'Закат',
|
||||||
colors: {
|
colors: {
|
||||||
accent: '#f97316',
|
accent: '#f97316',
|
||||||
darkBackground: '#1c1009',
|
darkBackground: '#1c1009',
|
||||||
@@ -189,7 +189,7 @@ const THEME_PRESETS: { name: string; colors: ThemeColors }[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Purple',
|
name: 'Фиолет',
|
||||||
colors: {
|
colors: {
|
||||||
accent: '#a855f7',
|
accent: '#a855f7',
|
||||||
darkBackground: '#0f0a1a',
|
darkBackground: '#0f0a1a',
|
||||||
@@ -206,7 +206,7 @@ const THEME_PRESETS: { name: string; colors: ThemeColors }[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Rose',
|
name: 'Роза',
|
||||||
colors: {
|
colors: {
|
||||||
accent: '#f43f5e',
|
accent: '#f43f5e',
|
||||||
darkBackground: '#1a0a10',
|
darkBackground: '#1a0a10',
|
||||||
@@ -223,7 +223,7 @@ const THEME_PRESETS: { name: string; colors: ThemeColors }[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Midnight',
|
name: 'Полночь',
|
||||||
colors: {
|
colors: {
|
||||||
accent: '#6366f1',
|
accent: '#6366f1',
|
||||||
darkBackground: '#030712',
|
darkBackground: '#030712',
|
||||||
@@ -240,7 +240,7 @@ const THEME_PRESETS: { name: string; colors: ThemeColors }[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Teal',
|
name: 'Бирюза',
|
||||||
colors: {
|
colors: {
|
||||||
accent: '#14b8a6',
|
accent: '#14b8a6',
|
||||||
darkBackground: '#0a1614',
|
darkBackground: '#0a1614',
|
||||||
@@ -985,8 +985,21 @@ export default function AdminSettings() {
|
|||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const [editingName, setEditingName] = useState(false)
|
const [editingName, setEditingName] = useState(false)
|
||||||
const [newName, setNewName] = useState('')
|
const [newName, setNewName] = useState('')
|
||||||
|
const [expandedThemeSections, setExpandedThemeSections] = useState<Set<string>>(new Set())
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
const toggleThemeSection = (section: string) => {
|
||||||
|
setExpandedThemeSections(prev => {
|
||||||
|
const next = new Set(prev)
|
||||||
|
if (next.has(section)) {
|
||||||
|
next.delete(section)
|
||||||
|
} else {
|
||||||
|
next.add(section)
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Branding query and mutations
|
// Branding query and mutations
|
||||||
const { data: branding } = useQuery({
|
const { data: branding } = useQuery({
|
||||||
queryKey: ['branding'],
|
queryKey: ['branding'],
|
||||||
@@ -1485,7 +1498,7 @@ export default function AdminSettings() {
|
|||||||
|
|
||||||
{/* Quick Presets */}
|
{/* Quick Presets */}
|
||||||
<div className="pb-6 border-b border-dark-700/50">
|
<div className="pb-6 border-b border-dark-700/50">
|
||||||
<label className="label mb-3">Quick Presets</label>
|
<label className="label mb-3">Быстрые пресеты</label>
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||||
{THEME_PRESETS.map((preset) => (
|
{THEME_PRESETS.map((preset) => (
|
||||||
<button
|
<button
|
||||||
@@ -1537,100 +1550,156 @@ export default function AdminSettings() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Accent Color */}
|
{/* Accent Color - Collapsible */}
|
||||||
<ColorPicker
|
<div className="border-b border-dark-700/50 pb-4">
|
||||||
label={t('theme.accent')}
|
<button
|
||||||
description={t('theme.accentDescription')}
|
onClick={() => toggleThemeSection('accent')}
|
||||||
value={themeColors?.accent || DEFAULT_THEME_COLORS.accent}
|
className="w-full flex items-center justify-between py-2 text-left"
|
||||||
onChange={(color) => updateColorsMutation.mutate({ accent: color })}
|
>
|
||||||
disabled={updateColorsMutation.isPending}
|
<div className="flex items-center gap-3">
|
||||||
/>
|
<div
|
||||||
|
className="w-5 h-5 rounded-full border-2 border-dark-600"
|
||||||
{/* Dark Theme Section */}
|
style={{ backgroundColor: themeColors?.accent || DEFAULT_THEME_COLORS.accent }}
|
||||||
<div>
|
/>
|
||||||
<h3 className="text-sm font-medium text-dark-300 mb-3">{t('theme.darkTheme')}</h3>
|
<span className="text-sm font-medium text-dark-200">{t('theme.accent')}</span>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
</div>
|
||||||
<ColorPicker
|
<ChevronDownIcon />
|
||||||
label={t('theme.background')}
|
</button>
|
||||||
value={themeColors?.darkBackground || DEFAULT_THEME_COLORS.darkBackground}
|
{expandedThemeSections.has('accent') && (
|
||||||
onChange={(color) => updateColorsMutation.mutate({ darkBackground: color })}
|
<div className="mt-3">
|
||||||
disabled={updateColorsMutation.isPending}
|
<ColorPicker
|
||||||
/>
|
label={t('theme.accent')}
|
||||||
<ColorPicker
|
description={t('theme.accentDescription')}
|
||||||
label={t('theme.surface')}
|
value={themeColors?.accent || DEFAULT_THEME_COLORS.accent}
|
||||||
value={themeColors?.darkSurface || DEFAULT_THEME_COLORS.darkSurface}
|
onChange={(color) => updateColorsMutation.mutate({ accent: color })}
|
||||||
onChange={(color) => updateColorsMutation.mutate({ darkSurface: color })}
|
disabled={updateColorsMutation.isPending}
|
||||||
disabled={updateColorsMutation.isPending}
|
/>
|
||||||
/>
|
</div>
|
||||||
<ColorPicker
|
)}
|
||||||
label={t('theme.text')}
|
|
||||||
value={themeColors?.darkText || DEFAULT_THEME_COLORS.darkText}
|
|
||||||
onChange={(color) => updateColorsMutation.mutate({ darkText: color })}
|
|
||||||
disabled={updateColorsMutation.isPending}
|
|
||||||
/>
|
|
||||||
<ColorPicker
|
|
||||||
label={t('theme.textSecondary')}
|
|
||||||
value={themeColors?.darkTextSecondary || DEFAULT_THEME_COLORS.darkTextSecondary}
|
|
||||||
onChange={(color) => updateColorsMutation.mutate({ darkTextSecondary: color })}
|
|
||||||
disabled={updateColorsMutation.isPending}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Light Theme Section */}
|
{/* Dark Theme Section - Collapsible */}
|
||||||
<div>
|
<div className="border-b border-dark-700/50 pb-4">
|
||||||
<h3 className="text-sm font-medium text-dark-300 mb-3">{t('theme.lightTheme')}</h3>
|
<button
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
onClick={() => toggleThemeSection('dark')}
|
||||||
<ColorPicker
|
className="w-full flex items-center justify-between py-2 text-left"
|
||||||
label={t('theme.background')}
|
>
|
||||||
value={themeColors?.lightBackground || DEFAULT_THEME_COLORS.lightBackground}
|
<div className="flex items-center gap-3">
|
||||||
onChange={(color) => updateColorsMutation.mutate({ lightBackground: color })}
|
<MoonIcon />
|
||||||
disabled={updateColorsMutation.isPending}
|
<span className="text-sm font-medium text-dark-200">{t('theme.darkTheme')}</span>
|
||||||
/>
|
</div>
|
||||||
<ColorPicker
|
<ChevronDownIcon />
|
||||||
label={t('theme.surface')}
|
</button>
|
||||||
value={themeColors?.lightSurface || DEFAULT_THEME_COLORS.lightSurface}
|
{expandedThemeSections.has('dark') && (
|
||||||
onChange={(color) => updateColorsMutation.mutate({ lightSurface: color })}
|
<div className="mt-3 grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
disabled={updateColorsMutation.isPending}
|
<ColorPicker
|
||||||
/>
|
label={t('theme.background')}
|
||||||
<ColorPicker
|
value={themeColors?.darkBackground || DEFAULT_THEME_COLORS.darkBackground}
|
||||||
label={t('theme.text')}
|
onChange={(color) => updateColorsMutation.mutate({ darkBackground: color })}
|
||||||
value={themeColors?.lightText || DEFAULT_THEME_COLORS.lightText}
|
disabled={updateColorsMutation.isPending}
|
||||||
onChange={(color) => updateColorsMutation.mutate({ lightText: color })}
|
/>
|
||||||
disabled={updateColorsMutation.isPending}
|
<ColorPicker
|
||||||
/>
|
label={t('theme.surface')}
|
||||||
<ColorPicker
|
value={themeColors?.darkSurface || DEFAULT_THEME_COLORS.darkSurface}
|
||||||
label={t('theme.textSecondary')}
|
onChange={(color) => updateColorsMutation.mutate({ darkSurface: color })}
|
||||||
value={themeColors?.lightTextSecondary || DEFAULT_THEME_COLORS.lightTextSecondary}
|
disabled={updateColorsMutation.isPending}
|
||||||
onChange={(color) => updateColorsMutation.mutate({ lightTextSecondary: color })}
|
/>
|
||||||
disabled={updateColorsMutation.isPending}
|
<ColorPicker
|
||||||
/>
|
label={t('theme.text')}
|
||||||
</div>
|
value={themeColors?.darkText || DEFAULT_THEME_COLORS.darkText}
|
||||||
|
onChange={(color) => updateColorsMutation.mutate({ darkText: color })}
|
||||||
|
disabled={updateColorsMutation.isPending}
|
||||||
|
/>
|
||||||
|
<ColorPicker
|
||||||
|
label={t('theme.textSecondary')}
|
||||||
|
value={themeColors?.darkTextSecondary || DEFAULT_THEME_COLORS.darkTextSecondary}
|
||||||
|
onChange={(color) => updateColorsMutation.mutate({ darkTextSecondary: color })}
|
||||||
|
disabled={updateColorsMutation.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Status Colors */}
|
{/* Light Theme Section - Collapsible */}
|
||||||
<div>
|
<div className="border-b border-dark-700/50 pb-4">
|
||||||
<h3 className="text-sm font-medium text-dark-300 mb-3">{t('theme.statusColors')}</h3>
|
<button
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
onClick={() => toggleThemeSection('light')}
|
||||||
<ColorPicker
|
className="w-full flex items-center justify-between py-2 text-left"
|
||||||
label={t('theme.success')}
|
>
|
||||||
value={themeColors?.success || DEFAULT_THEME_COLORS.success}
|
<div className="flex items-center gap-3">
|
||||||
onChange={(color) => updateColorsMutation.mutate({ success: color })}
|
<SunIcon />
|
||||||
disabled={updateColorsMutation.isPending}
|
<span className="text-sm font-medium text-dark-200">{t('theme.lightTheme')}</span>
|
||||||
/>
|
</div>
|
||||||
<ColorPicker
|
<ChevronDownIcon />
|
||||||
label={t('theme.warning')}
|
</button>
|
||||||
value={themeColors?.warning || DEFAULT_THEME_COLORS.warning}
|
{expandedThemeSections.has('light') && (
|
||||||
onChange={(color) => updateColorsMutation.mutate({ warning: color })}
|
<div className="mt-3 grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
disabled={updateColorsMutation.isPending}
|
<ColorPicker
|
||||||
/>
|
label={t('theme.background')}
|
||||||
<ColorPicker
|
value={themeColors?.lightBackground || DEFAULT_THEME_COLORS.lightBackground}
|
||||||
label={t('theme.error')}
|
onChange={(color) => updateColorsMutation.mutate({ lightBackground: color })}
|
||||||
value={themeColors?.error || DEFAULT_THEME_COLORS.error}
|
disabled={updateColorsMutation.isPending}
|
||||||
onChange={(color) => updateColorsMutation.mutate({ error: color })}
|
/>
|
||||||
disabled={updateColorsMutation.isPending}
|
<ColorPicker
|
||||||
/>
|
label={t('theme.surface')}
|
||||||
</div>
|
value={themeColors?.lightSurface || DEFAULT_THEME_COLORS.lightSurface}
|
||||||
|
onChange={(color) => updateColorsMutation.mutate({ lightSurface: color })}
|
||||||
|
disabled={updateColorsMutation.isPending}
|
||||||
|
/>
|
||||||
|
<ColorPicker
|
||||||
|
label={t('theme.text')}
|
||||||
|
value={themeColors?.lightText || DEFAULT_THEME_COLORS.lightText}
|
||||||
|
onChange={(color) => updateColorsMutation.mutate({ lightText: color })}
|
||||||
|
disabled={updateColorsMutation.isPending}
|
||||||
|
/>
|
||||||
|
<ColorPicker
|
||||||
|
label={t('theme.textSecondary')}
|
||||||
|
value={themeColors?.lightTextSecondary || DEFAULT_THEME_COLORS.lightTextSecondary}
|
||||||
|
onChange={(color) => updateColorsMutation.mutate({ lightTextSecondary: color })}
|
||||||
|
disabled={updateColorsMutation.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status Colors - Collapsible */}
|
||||||
|
<div className="border-b border-dark-700/50 pb-4">
|
||||||
|
<button
|
||||||
|
onClick={() => toggleThemeSection('status')}
|
||||||
|
className="w-full flex items-center justify-between py-2 text-left"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<div className="w-3 h-3 rounded-full" style={{ backgroundColor: themeColors?.success || DEFAULT_THEME_COLORS.success }} />
|
||||||
|
<div className="w-3 h-3 rounded-full" style={{ backgroundColor: themeColors?.warning || DEFAULT_THEME_COLORS.warning }} />
|
||||||
|
<div className="w-3 h-3 rounded-full" style={{ backgroundColor: themeColors?.error || DEFAULT_THEME_COLORS.error }} />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium text-dark-200">{t('theme.statusColors')}</span>
|
||||||
|
</div>
|
||||||
|
<ChevronDownIcon />
|
||||||
|
</button>
|
||||||
|
{expandedThemeSections.has('status') && (
|
||||||
|
<div className="mt-3 grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
|
<ColorPicker
|
||||||
|
label={t('theme.success')}
|
||||||
|
value={themeColors?.success || DEFAULT_THEME_COLORS.success}
|
||||||
|
onChange={(color) => updateColorsMutation.mutate({ success: color })}
|
||||||
|
disabled={updateColorsMutation.isPending}
|
||||||
|
/>
|
||||||
|
<ColorPicker
|
||||||
|
label={t('theme.warning')}
|
||||||
|
value={themeColors?.warning || DEFAULT_THEME_COLORS.warning}
|
||||||
|
onChange={(color) => updateColorsMutation.mutate({ warning: color })}
|
||||||
|
disabled={updateColorsMutation.isPending}
|
||||||
|
/>
|
||||||
|
<ColorPicker
|
||||||
|
label={t('theme.error')}
|
||||||
|
value={themeColors?.error || DEFAULT_THEME_COLORS.error}
|
||||||
|
onChange={(color) => updateColorsMutation.mutate({ error: color })}
|
||||||
|
disabled={updateColorsMutation.isPending}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Preview */}
|
{/* Preview */}
|
||||||
|
|||||||
Reference in New Issue
Block a user