2026-07-30 18:19:53 +03:30
|
|
|
/** 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
|
|
|
|
|
registryMode?: string | null
|
|
|
|
|
settings?: {
|
|
|
|
|
pricing_box_theme?: string
|
|
|
|
|
price_font_size?: string
|
|
|
|
|
orderUI?: string
|
|
|
|
|
}
|
2026-08-03 20:51:33 +03:30
|
|
|
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
|
2026-07-30 18:19:53 +03:30
|
|
|
}) {
|
2026-08-03 20:51:33 +03:30
|
|
|
const base = {
|
2026-07-30 18:19:53 +03:30
|
|
|
kind: 'hamkar' as const,
|
|
|
|
|
settingsManaged: Boolean(input.settingsManaged),
|
|
|
|
|
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'),
|
|
|
|
|
},
|
|
|
|
|
}
|
2026-08-03 20:51:33 +03:30
|
|
|
|
|
|
|
|
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),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
2026-07-30 18:19:53 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
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: {
|
|
|
|
|
type: 'sidebar+bottom',
|
|
|
|
|
sidebar: [
|
|
|
|
|
{ id: 'home', label: 'داشبورد', icon: 'vuesax-linear:chart', route: 'home', order: 1, visible: true },
|
|
|
|
|
{ id: 'products', label: 'معاملات', icon: 'vuesax-linear:shop', route: 'products', 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: 'products', 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,
|
|
|
|
|
showLogo: true,
|
|
|
|
|
showNotifications: true,
|
|
|
|
|
showProfile: true,
|
|
|
|
|
brandSubtitle: 'پنل کاربری',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
pages: {},
|
|
|
|
|
_backfill: 'colors-from-live-registry',
|
|
|
|
|
}
|
|
|
|
|
}
|