Expand SuperAdmin web for SMS, servers, plugins, admins, and UI config.
Align SMS tenant panel with Hamkar, add Jalali dates, and support hamkar/khanegi appearance editing. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
export type HamkarPricingBoxTheme = '1' | '2' | '3' | '4'
|
||||
export type HamkarOrderUi = 'new' | 'old'
|
||||
export type HamkarPriceFontSize = 'کوچک' | 'متوسط' | 'بزرگ' | 'بزرگتر'
|
||||
|
||||
export type HamkarUiConfig = {
|
||||
kind: 'hamkar'
|
||||
/**
|
||||
* When false, GET /get_user will NOT emit ui_settings —
|
||||
* Laravel settings stay source of truth (prevents visual jumps).
|
||||
*/
|
||||
settingsManaged: boolean
|
||||
/** Named CSS mode from live registry (Montpar, limangold, …) or dynamicMode */
|
||||
registryMode: string
|
||||
brand: {
|
||||
/** Registry `color` → CSS `--bg2` (only when mode=dynamicMode) */
|
||||
primaryColor: string
|
||||
/** Registry `secondary_color` → CSS `--colorPrimary` (dynamicMode) */
|
||||
secondaryColor: string
|
||||
/** Registry `background_color` → CSS `--bg` (dynamicMode) */
|
||||
backgroundColor: string
|
||||
}
|
||||
settings: {
|
||||
pricing_box_theme: HamkarPricingBoxTheme
|
||||
price_font_size: HamkarPriceFontSize
|
||||
orderUI: HamkarOrderUi
|
||||
}
|
||||
}
|
||||
|
||||
export const HAMKAR_PRICING_BOX_THEME_OPTIONS: {
|
||||
value: HamkarPricingBoxTheme
|
||||
label: string
|
||||
}[] = [
|
||||
{ value: '1', label: 'تم ۱ (پیشفرض)' },
|
||||
{ value: '2', label: 'تم ۲' },
|
||||
{ value: '3', label: 'تم ۳' },
|
||||
{ value: '4', label: 'تم ۴' },
|
||||
]
|
||||
|
||||
export const HAMKAR_ORDER_UI_OPTIONS: { value: HamkarOrderUi; label: string }[] = [
|
||||
{ value: 'new', label: 'جدید (کارتی)' },
|
||||
{ value: 'old', label: 'قدیمی (جدولی)' },
|
||||
]
|
||||
|
||||
export const HAMKAR_PRICE_FONT_SIZE_OPTIONS: {
|
||||
value: HamkarPriceFontSize
|
||||
label: string
|
||||
}[] = [
|
||||
{ value: 'کوچک', label: 'کوچک' },
|
||||
{ value: 'متوسط', label: 'متوسط' },
|
||||
{ value: 'بزرگ', label: 'بزرگ' },
|
||||
{ value: 'بزرگتر', label: 'بزرگتر' },
|
||||
]
|
||||
|
||||
function asColor(value: unknown): string {
|
||||
if (value == null) return ''
|
||||
const s = String(value).trim()
|
||||
if (!s || s === 'null' || s === 'undefined') return ''
|
||||
return s
|
||||
}
|
||||
|
||||
export function createDefaultHamkarUiConfig(): HamkarUiConfig {
|
||||
return {
|
||||
kind: 'hamkar',
|
||||
settingsManaged: false,
|
||||
registryMode: 'dynamicMode',
|
||||
brand: {
|
||||
primaryColor: '',
|
||||
secondaryColor: '',
|
||||
backgroundColor: '',
|
||||
},
|
||||
settings: {
|
||||
pricing_box_theme: '1',
|
||||
price_font_size: 'بزرگ',
|
||||
orderUI: 'old',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeHamkarUiConfig(raw: unknown): HamkarUiConfig {
|
||||
const base = createDefaultHamkarUiConfig()
|
||||
if (!raw || typeof raw !== 'object') return base
|
||||
|
||||
const input = raw as Record<string, any>
|
||||
const brand = input.brand && typeof input.brand === 'object' ? input.brand : {}
|
||||
const settings =
|
||||
input.settings && typeof input.settings === 'object' ? input.settings : {}
|
||||
|
||||
const theme = String(settings.pricing_box_theme ?? base.settings.pricing_box_theme)
|
||||
const orderUI = String(settings.orderUI ?? base.settings.orderUI)
|
||||
const font = String(settings.price_font_size ?? base.settings.price_font_size)
|
||||
const registryMode = String(input.registryMode || base.registryMode).trim() || 'dynamicMode'
|
||||
|
||||
return {
|
||||
kind: 'hamkar',
|
||||
settingsManaged: Boolean(input.settingsManaged),
|
||||
registryMode,
|
||||
brand: {
|
||||
// Do not invent colors — empty means null in registry/get_user
|
||||
primaryColor: asColor(brand.primaryColor),
|
||||
secondaryColor: asColor(brand.secondaryColor),
|
||||
backgroundColor: asColor(brand.backgroundColor),
|
||||
},
|
||||
settings: {
|
||||
pricing_box_theme: (['1', '2', '3', '4'].includes(theme)
|
||||
? theme
|
||||
: base.settings.pricing_box_theme) as HamkarPricingBoxTheme,
|
||||
orderUI: (orderUI === 'old' || orderUI === 'new'
|
||||
? orderUI
|
||||
: base.settings.orderUI) as HamkarOrderUi,
|
||||
price_font_size: (HAMKAR_PRICE_FONT_SIZE_OPTIONS.some((o) => o.value === font)
|
||||
? font
|
||||
: base.settings.price_font_size) as HamkarPriceFontSize,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** Payload for API: empty color strings → null */
|
||||
export function hamkarUiToApiPayload(config: HamkarUiConfig) {
|
||||
const normalized = normalizeHamkarUiConfig(config)
|
||||
const emptyToNull = (v: string) => (v.trim() ? v.trim() : null)
|
||||
return {
|
||||
...normalized,
|
||||
brand: {
|
||||
primaryColor: emptyToNull(normalized.brand.primaryColor),
|
||||
secondaryColor: emptyToNull(normalized.brand.secondaryColor),
|
||||
backgroundColor: emptyToNull(normalized.brand.backgroundColor),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** Build Laravel-compatible settings overlay for Hamkar user app. */
|
||||
export function hamkarUiToSettingsList(config: HamkarUiConfig) {
|
||||
if (!config.settingsManaged) return []
|
||||
return [
|
||||
{ key: 'pricing_box_theme', value: config.settings.pricing_box_theme },
|
||||
{ key: 'price_font_size', value: config.settings.price_font_size },
|
||||
{ key: 'orderUI', value: config.settings.orderUI },
|
||||
]
|
||||
}
|
||||
@@ -1,3 +1,63 @@
|
||||
export type ThemeColors = {
|
||||
primary: string
|
||||
secondary: string
|
||||
tertiary: string
|
||||
background: string
|
||||
surface: string
|
||||
surfaceHover: string
|
||||
textPrimary: string
|
||||
textSecondary: string
|
||||
textSubtle: string
|
||||
border: string
|
||||
borderSubtle: string
|
||||
pageBg: string
|
||||
pageBgMobile: string
|
||||
onAccent: string
|
||||
success: string
|
||||
warning: string
|
||||
danger: string
|
||||
}
|
||||
|
||||
const navyGoldDark: ThemeColors = {
|
||||
primary: '#0A1B37',
|
||||
secondary: '#e3b056',
|
||||
tertiary: '#ce9d43',
|
||||
background: '#ffffff',
|
||||
surface: '#222228',
|
||||
surfaceHover: '#2c2c34',
|
||||
textPrimary: '#ffffff',
|
||||
textSecondary: 'rgba(255, 255, 255, 0.45)',
|
||||
textSubtle: 'rgba(255, 255, 255, 0.35)',
|
||||
border: 'rgba(255, 255, 255, 0.16)',
|
||||
borderSubtle: 'rgba(255, 255, 255, 0.12)',
|
||||
pageBg: '#121216',
|
||||
pageBgMobile: '#0e0e12',
|
||||
onAccent: '#0a0a0a',
|
||||
success: '#16A34A',
|
||||
warning: '#F59E0B',
|
||||
danger: '#DC2626',
|
||||
}
|
||||
|
||||
const navyGoldLight: ThemeColors = {
|
||||
primary: '#0A1B37',
|
||||
secondary: '#c9973a',
|
||||
tertiary: '#b98633',
|
||||
background: '#faf8f5',
|
||||
surface: '#ffffff',
|
||||
surfaceHover: '#fffefb',
|
||||
textPrimary: '#1c1916',
|
||||
textSecondary: '#5c564d',
|
||||
textSubtle: '#8a8278',
|
||||
border: 'rgba(184, 153, 88, 0.22)',
|
||||
borderSubtle: 'rgba(0, 0, 0, 0.08)',
|
||||
pageBg: '#f5f2ed',
|
||||
pageBgMobile: '#faf8f5',
|
||||
onAccent: '#ffffff',
|
||||
success: '#16A34A',
|
||||
warning: '#F59E0B',
|
||||
danger: '#DC2626',
|
||||
}
|
||||
|
||||
/** Default Khanegi app.theme + navigation shell for new UI configs */
|
||||
export function createDefaultKhanegiUiConfig() {
|
||||
return {
|
||||
@@ -10,25 +70,12 @@ export function createDefaultKhanegiUiConfig() {
|
||||
locale: 'fa-IR',
|
||||
startRoute: 'home',
|
||||
theme: {
|
||||
mode: 'dark',
|
||||
colors: {
|
||||
primary: '#0A1B37',
|
||||
secondary: '#e3b056',
|
||||
tertiary: '#ce9d43',
|
||||
background: '#ffffff',
|
||||
surface: '#222228',
|
||||
surfaceHover: '#2c2c34',
|
||||
textPrimary: '#ffffff',
|
||||
textSecondary: 'rgba(255, 255, 255, 0.45)',
|
||||
textSubtle: 'rgba(255, 255, 255, 0.35)',
|
||||
border: 'rgba(255, 255, 255, 0.16)',
|
||||
borderSubtle: 'rgba(255, 255, 255, 0.12)',
|
||||
pageBg: '#121216',
|
||||
pageBgMobile: '#0e0e12',
|
||||
onAccent: '#0a0a0a',
|
||||
success: '#16A34A',
|
||||
warning: '#F59E0B',
|
||||
danger: '#DC2626',
|
||||
presetId: 'navy-gold' as string,
|
||||
mode: 'dark' as 'dark' | 'light' | 'system',
|
||||
colors: { ...navyGoldDark },
|
||||
schemes: {
|
||||
dark: { ...navyGoldDark },
|
||||
light: { ...navyGoldLight },
|
||||
},
|
||||
borderRadius: { sm: 8, md: 12, lg: 16 },
|
||||
},
|
||||
@@ -65,7 +112,7 @@ export function createDefaultKhanegiUiConfig() {
|
||||
brandSubtitle: 'پنل کاربری',
|
||||
},
|
||||
},
|
||||
pages: {},
|
||||
pages: {} as Record<string, unknown>,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Mirrors Hamkar user `apply-theme.ts` + default CSS in `main.less` / `maindynamic.less`.
|
||||
* Named modes (Montpar, limangold, …) do NOT paint registry color fields onto the chrome.
|
||||
*/
|
||||
|
||||
export type HamkarEffectiveTheme = {
|
||||
mode: string
|
||||
isDynamic: boolean
|
||||
/** What paints the top header / mobile menu bar */
|
||||
headerBg: string
|
||||
/** Selected menu / text_Secondary / icon accent the user actually sees */
|
||||
accent: string
|
||||
/** Ant Design colorPrimary token (buttons) */
|
||||
antPrimary: string
|
||||
/** Page / body background */
|
||||
pageBg: string
|
||||
/** CSS vars apply-theme writes on :root */
|
||||
cssVars: {
|
||||
bg: string
|
||||
bg2: string
|
||||
colorPrimary: string
|
||||
}
|
||||
note: string
|
||||
}
|
||||
|
||||
const NAMED_DEFAULT = {
|
||||
headerBg: '#ffffff',
|
||||
accent: '#2563EB',
|
||||
antPrimary: '#1d4ed8',
|
||||
pageBg: '#fafafa',
|
||||
cssVars: {
|
||||
bg: '',
|
||||
bg2: '#ffffff',
|
||||
colorPrimary: '#1d4ed8',
|
||||
},
|
||||
} as const
|
||||
|
||||
function hexOr(value: string | null | undefined, fallback: string) {
|
||||
const s = (value || '').trim()
|
||||
if (!s || s === 'null' || s === 'undefined') return fallback
|
||||
return s
|
||||
}
|
||||
|
||||
export function resolveHamkarEffectiveTheme(input: {
|
||||
registryMode?: string | null
|
||||
primaryColor?: string | null
|
||||
secondaryColor?: string | null
|
||||
backgroundColor?: string | null
|
||||
}): HamkarEffectiveTheme {
|
||||
const mode = String(input.registryMode || 'dynamicMode').trim() || 'dynamicMode'
|
||||
const isDynamic = mode === 'dynamicMode'
|
||||
|
||||
if (!isDynamic) {
|
||||
return {
|
||||
mode,
|
||||
isDynamic: false,
|
||||
...NAMED_DEFAULT,
|
||||
note: `حالت «${mode}»: هدر و منو از CSS پیشفرض همکار میآیند؛ فیلدهای color / secondary / bg رجیستری روی ظاهر اعمال نمیشوند.`,
|
||||
}
|
||||
}
|
||||
|
||||
const panel = hexOr(input.primaryColor, '#ffffff')
|
||||
const accent = hexOr(input.secondaryColor, '#1d4ed8')
|
||||
const pageBg = hexOr(input.backgroundColor, '#ffffff')
|
||||
|
||||
return {
|
||||
mode,
|
||||
isDynamic: true,
|
||||
headerBg: panel,
|
||||
accent,
|
||||
antPrimary: accent,
|
||||
pageBg,
|
||||
cssVars: {
|
||||
bg: pageBg,
|
||||
bg2: panel,
|
||||
colorPrimary: accent,
|
||||
},
|
||||
note: 'حالت dynamicMode: color → هدر (--bg2)، secondary_color → اکسنت (--colorPrimary)، background_color → پسزمینه (--bg).',
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
import { createDefaultKhanegiUiConfig, type ThemeColors, type UiConfig } from './defaultKhanegiUiConfig'
|
||||
|
||||
export type KhanegiPresetId = 'navy-gold' | 'emerald' | 'warm-bronze' | 'custom'
|
||||
|
||||
export type ThemeSchemes = {
|
||||
dark: ThemeColors
|
||||
light: ThemeColors
|
||||
}
|
||||
|
||||
export type KhanegiThemePreset = {
|
||||
id: Exclude<KhanegiPresetId, 'custom'>
|
||||
nameFa: string
|
||||
description: string
|
||||
borderRadius: { sm: number; md: number; lg: number }
|
||||
schemes: ThemeSchemes
|
||||
/** رنگهای پیشنمایش کارت (حالت dark) */
|
||||
preview: ThemeColors
|
||||
}
|
||||
|
||||
const navyGoldDark: ThemeColors = {
|
||||
primary: '#0A1B37',
|
||||
secondary: '#e3b056',
|
||||
tertiary: '#ce9d43',
|
||||
background: '#ffffff',
|
||||
surface: '#222228',
|
||||
surfaceHover: '#2c2c34',
|
||||
textPrimary: '#ffffff',
|
||||
textSecondary: 'rgba(255, 255, 255, 0.45)',
|
||||
textSubtle: 'rgba(255, 255, 255, 0.35)',
|
||||
border: 'rgba(255, 255, 255, 0.16)',
|
||||
borderSubtle: 'rgba(255, 255, 255, 0.12)',
|
||||
pageBg: '#121216',
|
||||
pageBgMobile: '#0e0e12',
|
||||
onAccent: '#0a0a0a',
|
||||
success: '#16A34A',
|
||||
warning: '#F59E0B',
|
||||
danger: '#DC2626',
|
||||
}
|
||||
|
||||
const navyGoldLight: ThemeColors = {
|
||||
primary: '#0A1B37',
|
||||
secondary: '#c9973a',
|
||||
tertiary: '#b98633',
|
||||
background: '#faf8f5',
|
||||
surface: '#ffffff',
|
||||
surfaceHover: '#fffefb',
|
||||
textPrimary: '#1c1916',
|
||||
textSecondary: '#5c564d',
|
||||
textSubtle: '#8a8278',
|
||||
border: 'rgba(184, 153, 88, 0.22)',
|
||||
borderSubtle: 'rgba(0, 0, 0, 0.08)',
|
||||
pageBg: '#f5f2ed',
|
||||
pageBgMobile: '#faf8f5',
|
||||
onAccent: '#ffffff',
|
||||
success: '#16A34A',
|
||||
warning: '#F59E0B',
|
||||
danger: '#DC2626',
|
||||
}
|
||||
|
||||
const emeraldDark: ThemeColors = {
|
||||
primary: '#064e3b',
|
||||
secondary: '#fbbf24',
|
||||
tertiary: '#d97706',
|
||||
background: '#ffffff',
|
||||
surface: '#14201c',
|
||||
surfaceHover: '#1c2c26',
|
||||
textPrimary: '#ffffff',
|
||||
textSecondary: 'rgba(255, 255, 255, 0.5)',
|
||||
textSubtle: 'rgba(255, 255, 255, 0.35)',
|
||||
border: 'rgba(255, 255, 255, 0.14)',
|
||||
borderSubtle: 'rgba(255, 255, 255, 0.1)',
|
||||
pageBg: '#0b1411',
|
||||
pageBgMobile: '#08100d',
|
||||
onAccent: '#052e24',
|
||||
success: '#22c55e',
|
||||
warning: '#f59e0b',
|
||||
danger: '#ef4444',
|
||||
}
|
||||
|
||||
const emeraldLight: ThemeColors = {
|
||||
primary: '#065f46',
|
||||
secondary: '#059669',
|
||||
tertiary: '#047857',
|
||||
background: '#ecfdf5',
|
||||
surface: '#ffffff',
|
||||
surfaceHover: '#d1fae5',
|
||||
textPrimary: '#064e3b',
|
||||
textSecondary: '#047857',
|
||||
textSubtle: '#6b8578',
|
||||
border: 'rgba(5, 150, 105, 0.28)',
|
||||
borderSubtle: 'rgba(6, 95, 70, 0.12)',
|
||||
pageBg: '#d1fae5',
|
||||
pageBgMobile: '#ecfdf5',
|
||||
onAccent: '#ffffff',
|
||||
success: '#16a34a',
|
||||
warning: '#f59e0b',
|
||||
danger: '#dc2626',
|
||||
}
|
||||
|
||||
const warmBronzeDark: ThemeColors = {
|
||||
primary: '#3f2a1a',
|
||||
secondary: '#d4a373',
|
||||
tertiary: '#b08968',
|
||||
background: '#fffaf5',
|
||||
surface: '#2a211c',
|
||||
surfaceHover: '#352a23',
|
||||
textPrimary: '#ffffff',
|
||||
textSecondary: 'rgba(255, 255, 255, 0.5)',
|
||||
textSubtle: 'rgba(255, 255, 255, 0.35)',
|
||||
border: 'rgba(255, 255, 255, 0.14)',
|
||||
borderSubtle: 'rgba(255, 255, 255, 0.1)',
|
||||
pageBg: '#16110e',
|
||||
pageBgMobile: '#120e0b',
|
||||
onAccent: '#1c1410',
|
||||
success: '#16a34a',
|
||||
warning: '#f59e0b',
|
||||
danger: '#dc2626',
|
||||
}
|
||||
|
||||
const warmBronzeLight: ThemeColors = {
|
||||
primary: '#7c2d12',
|
||||
secondary: '#c2410c',
|
||||
tertiary: '#9a3412',
|
||||
background: '#fff7ed',
|
||||
surface: '#ffffff',
|
||||
surfaceHover: '#ffedd5',
|
||||
textPrimary: '#7c2d12',
|
||||
textSecondary: '#9a3412',
|
||||
textSubtle: '#c2410c',
|
||||
border: 'rgba(194, 65, 12, 0.28)',
|
||||
borderSubtle: 'rgba(124, 45, 18, 0.12)',
|
||||
pageBg: '#ffedd5',
|
||||
pageBgMobile: '#fff7ed',
|
||||
onAccent: '#ffffff',
|
||||
success: '#16a34a',
|
||||
warning: '#f59e0b',
|
||||
danger: '#dc2626',
|
||||
}
|
||||
|
||||
export const khanegiThemePresets: KhanegiThemePreset[] = [
|
||||
{
|
||||
id: 'navy-gold',
|
||||
nameFa: 'سرمهای طلایی',
|
||||
description: 'تم اصلی خانگی — دارک و لایت',
|
||||
borderRadius: { sm: 8, md: 12, lg: 16 },
|
||||
schemes: { dark: navyGoldDark, light: navyGoldLight },
|
||||
preview: navyGoldDark,
|
||||
},
|
||||
{
|
||||
id: 'emerald',
|
||||
nameFa: 'زمردی طلایی',
|
||||
description: 'تم سبز تیره با اکسنت طلایی',
|
||||
borderRadius: { sm: 8, md: 12, lg: 16 },
|
||||
schemes: { dark: emeraldDark, light: emeraldLight },
|
||||
preview: emeraldDark,
|
||||
},
|
||||
{
|
||||
id: 'warm-bronze',
|
||||
nameFa: 'برنز گرم',
|
||||
description: 'تم برنز/کرم با گوشههای گردتر',
|
||||
borderRadius: { sm: 12, md: 16, lg: 24 },
|
||||
schemes: { dark: warmBronzeDark, light: warmBronzeLight },
|
||||
preview: warmBronzeDark,
|
||||
},
|
||||
]
|
||||
|
||||
export function getKhanegiPreset(id: string): KhanegiThemePreset | undefined {
|
||||
return khanegiThemePresets.find((p) => p.id === id)
|
||||
}
|
||||
|
||||
/** اعمال preset روی config فعلی؛ pages موجود حفظ میشوند */
|
||||
export function applyKhanegiPreset(config: UiConfig, presetId: Exclude<KhanegiPresetId, 'custom'>): UiConfig {
|
||||
const preset = getKhanegiPreset(presetId)
|
||||
if (!preset) return config
|
||||
|
||||
const mode = config.app.theme.mode === 'light' ? 'light' : 'dark'
|
||||
const activeColors = { ...preset.schemes[mode] }
|
||||
|
||||
config.app.theme.presetId = presetId
|
||||
config.app.theme.mode = mode
|
||||
config.app.theme.colors = activeColors
|
||||
config.app.theme.schemes = {
|
||||
dark: { ...preset.schemes.dark },
|
||||
light: { ...preset.schemes.light },
|
||||
}
|
||||
config.app.theme.borderRadius = { ...preset.borderRadius }
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
/** ساخت config تازه از preset (برای نسخه جدید) */
|
||||
export function createConfigFromPreset(presetId: Exclude<KhanegiPresetId, 'custom'> = 'navy-gold'): UiConfig {
|
||||
const config = createDefaultKhanegiUiConfig()
|
||||
return applyKhanegiPreset(config, presetId)
|
||||
}
|
||||
|
||||
export function resolvePresetId(config: UiConfig): KhanegiPresetId {
|
||||
const id = config.app?.theme?.presetId
|
||||
if (id === 'navy-gold' || id === 'emerald' || id === 'warm-bronze' || id === 'custom') {
|
||||
return id
|
||||
}
|
||||
return 'custom'
|
||||
}
|
||||
|
||||
export function syncActiveColorsFromMode(config: UiConfig) {
|
||||
const schemes = config.app.theme.schemes
|
||||
if (!schemes?.dark || !schemes?.light) return
|
||||
const mode = config.app.theme.mode === 'light' ? 'light' : 'dark'
|
||||
Object.assign(config.app.theme.colors, schemes[mode])
|
||||
}
|
||||
|
||||
export function markCustomAndSyncSchemes(config: UiConfig) {
|
||||
config.app.theme.presetId = 'custom'
|
||||
const mode = config.app.theme.mode === 'light' ? 'light' : 'dark'
|
||||
if (!config.app.theme.schemes) {
|
||||
config.app.theme.schemes = {
|
||||
dark: { ...config.app.theme.colors },
|
||||
light: { ...config.app.theme.colors },
|
||||
}
|
||||
}
|
||||
Object.assign(config.app.theme.schemes[mode], config.app.theme.colors)
|
||||
}
|
||||
Reference in New Issue
Block a user