/** Shared appearance helpers for import/backfill scripts (no Vue deps). */ export type RegistryUser = { avatar?: string | null color?: string | null secondary_color?: string | null background_color?: string | null name?: string | null name_fa?: string | null theme?: string | null mode?: string | null node_server?: string | null app_ui_config?: unknown user?: RegistryUser } export function pickRegistryPayload(raw: any): RegistryUser { if (raw?.user && typeof raw.user === 'object') { return { ...raw, ...raw.user } } return raw || {} } export function resolveAvatarUrl(raw: string | null | undefined, registryBase: string) { if (!raw) return null if (/^https?:\/\//i.test(raw)) return raw if (/^data:/i.test(raw)) return raw const base = registryBase.replace(/\/get_user\/?$/i, '').replace(/\/+$/, '') return `${base}/${String(raw).replace(/^\/+/, '')}` } export function buildHamkarUiFromBrand(input: { primaryColor?: string | null secondaryColor?: string | null backgroundColor?: string | null settingsManaged?: boolean brandManaged?: boolean registryMode?: string | null settings?: { pricing_box_theme?: string price_font_size?: string orderUI?: string } defaultSnapshot?: { registryMode?: string | null brand?: { primaryColor?: string | null secondaryColor?: string | null backgroundColor?: string | null } settings?: { pricing_box_theme?: string price_font_size?: string orderUI?: string } } | null }) { const base = { kind: 'hamkar' as const, settingsManaged: Boolean(input.settingsManaged), brandManaged: Boolean(input.brandManaged), registryMode: input.registryMode || 'dynamicMode', brand: { // Keep nulls — do NOT invent #1d4ed8 (causes wrong accent vs live registry) primaryColor: input.primaryColor || null, secondaryColor: input.secondaryColor || null, backgroundColor: input.backgroundColor || null, }, settings: { pricing_box_theme: String(input.settings?.pricing_box_theme || '1'), price_font_size: String(input.settings?.price_font_size || 'بزرگ'), orderUI: String(input.settings?.orderUI || 'old'), }, } if (!input.defaultSnapshot) return base return { ...base, defaultSnapshot: { registryMode: input.defaultSnapshot.registryMode || base.registryMode, brand: { primaryColor: input.defaultSnapshot.brand?.primaryColor ?? null, secondaryColor: input.defaultSnapshot.brand?.secondaryColor ?? null, backgroundColor: input.defaultSnapshot.brand?.backgroundColor ?? null, }, settings: { pricing_box_theme: String( input.defaultSnapshot.settings?.pricing_box_theme || base.settings.pricing_box_theme, ), price_font_size: String( input.defaultSnapshot.settings?.price_font_size || base.settings.price_font_size, ), orderUI: String(input.defaultSnapshot.settings?.orderUI || base.settings.orderUI), }, }, } } export function buildKhanegiUiFromColors(input: { primary?: string | null secondary?: string | null background?: string | null existing?: any }) { if ( input.existing && typeof input.existing === 'object' && (input.existing.schemaId === 'ui-config' || input.existing.app) ) { return input.existing } const primary = input.primary || '#0A1B37' const secondary = input.secondary || '#e3b056' const pageBg = input.background || '#121216' const surface = '#222228' const dark = { primary, secondary, tertiary: secondary, background: '#ffffff', surface, 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, pageBgMobile: pageBg, onAccent: '#0a0a0a', success: '#16A34A', warning: '#F59E0B', danger: '#DC2626', } const light = { ...dark, surface: '#ffffff', surfaceHover: '#fffefb', textPrimary: '#1c1916', textSecondary: '#5c564d', textSubtle: '#8a8278', border: 'rgba(0, 0, 0, 0.08)', borderSubtle: 'rgba(0, 0, 0, 0.06)', pageBg: pageBg === '#121216' ? '#f5f2ed' : pageBg, pageBgMobile: pageBg === '#121216' ? '#faf8f5' : pageBg, onAccent: '#ffffff', } const base = { schemaId: 'ui-config', version: '1.0.0', app: { schemaId: 'app', version: '1.0.0', direction: 'rtl', locale: 'fa-IR', startRoute: 'home', theme: { presetId: 'custom', mode: 'dark', colors: { ...dark }, schemes: { dark: { ...dark }, light: { ...light } }, borderRadius: { sm: 8, md: 12, lg: 16 }, }, navigation: { // userTheme schema: bottom-navigation | sidebar | both type: 'both', sidebar: [ { id: 'home', label: 'داشبورد', icon: 'vuesax-linear:chart', route: 'home', order: 1, visible: true }, { id: 'products', label: 'معاملات', icon: 'vuesax-linear:shop', route: 'buyProduct', order: 2, visible: true }, { id: 'transactions', label: 'تراکنش‌ها', icon: 'vuesax-linear:receipt-item', route: 'transactions', order: 3, visible: true }, { id: 'wallet', label: 'کیف پول', icon: 'vuesax-linear:wallet-2', route: 'wallet', order: 4, visible: true }, { id: 'user', label: 'حساب کاربری', icon: 'vuesax-linear:user', route: 'user', order: 5, visible: true }, ], bottom: [ { id: 'home', label: 'خانه', icon: 'vuesax-linear:home-2', route: 'home', order: 1, visible: true }, { id: 'products', label: 'معامله', icon: 'vuesax-linear:shop', route: 'buyProduct', order: 2, visible: true }, { id: 'transactions', label: 'سوابق', icon: 'vuesax-linear:receipt-item', route: 'transactions', order: 3, visible: true }, { id: 'wallet', label: 'کیف پول', icon: 'vuesax-linear:wallet-2', route: 'wallet', order: 4, visible: true }, { id: 'user', label: 'پروفایل', icon: 'vuesax-linear:user', route: 'user', order: 5, visible: true }, ], sidebarReferral: { visible: true, title: 'دعوت از دوستان', subtitle: 'کسب درآمد', route: 'earnMoney', }, }, header: { visible: true, variant: 'default', showLogo: true, showNotifications: true, showProfile: true, brandSubtitle: 'پنل کاربری', }, }, pages: {}, _backfill: 'colors-from-live-registry', } return { ...base, defaultSnapshot: { app: JSON.parse(JSON.stringify(base.app)), pages: JSON.parse(JSON.stringify(base.pages || {})), }, } }