Files
api/prisma/lib/appearance-builders.ts
T
MahdiandCursor a589c7f0c2 fix: normalize Khanegi newTheme ui config for userTheme schema
Map sidebar+bottom→both, products→buyProduct, ensure header.variant. Repair script + get_user normalize so colors apply instead of Zod fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-10 12:34:07 +03:30

212 lines
6.7 KiB
TypeScript

/** 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
}
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),
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 || {})),
},
}
}