Add Hamkar UI reset-to-default snapshot and restore button.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,28 @@ export type HamkarPricingBoxTheme = '1' | '2' | '3' | '4'
|
||||
export type HamkarOrderUi = 'new' | 'old'
|
||||
export type HamkarPriceFontSize = 'کوچک' | 'متوسط' | 'بزرگ' | 'بزرگتر'
|
||||
|
||||
export type HamkarBrandColors = {
|
||||
/** 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
|
||||
}
|
||||
|
||||
export type HamkarUiSettings = {
|
||||
pricing_box_theme: HamkarPricingBoxTheme
|
||||
price_font_size: HamkarPriceFontSize
|
||||
orderUI: HamkarOrderUi
|
||||
}
|
||||
|
||||
/** Immutable factory look — set once, never overwritten on normal save */
|
||||
export type HamkarAppearanceSnapshot = {
|
||||
registryMode: string
|
||||
brand: HamkarBrandColors
|
||||
settings: HamkarUiSettings
|
||||
}
|
||||
|
||||
export type HamkarUiConfig = {
|
||||
kind: 'hamkar'
|
||||
/**
|
||||
@@ -11,19 +33,10 @@ export type HamkarUiConfig = {
|
||||
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
|
||||
}
|
||||
brand: HamkarBrandColors
|
||||
settings: HamkarUiSettings
|
||||
/** Original appearance before SuperAdmin color edits (reset target) */
|
||||
defaultSnapshot?: HamkarAppearanceSnapshot
|
||||
}
|
||||
|
||||
export const HAMKAR_PRICING_BOX_THEME_OPTIONS: {
|
||||
@@ -58,6 +71,34 @@ function asColor(value: unknown): string {
|
||||
return s
|
||||
}
|
||||
|
||||
function normalizeSettings(raw: unknown): HamkarUiSettings {
|
||||
const base = createDefaultHamkarUiConfig().settings
|
||||
const settings = raw && typeof raw === 'object' ? (raw as Record<string, any>) : {}
|
||||
const theme = String(settings.pricing_box_theme ?? base.pricing_box_theme)
|
||||
const orderUI = String(settings.orderUI ?? base.orderUI)
|
||||
const font = String(settings.price_font_size ?? base.price_font_size)
|
||||
return {
|
||||
pricing_box_theme: (['1', '2', '3', '4'].includes(theme)
|
||||
? theme
|
||||
: base.pricing_box_theme) as HamkarPricingBoxTheme,
|
||||
orderUI: (orderUI === 'old' || orderUI === 'new'
|
||||
? orderUI
|
||||
: base.orderUI) as HamkarOrderUi,
|
||||
price_font_size: (HAMKAR_PRICE_FONT_SIZE_OPTIONS.some((o) => o.value === font)
|
||||
? font
|
||||
: base.price_font_size) as HamkarPriceFontSize,
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeBrand(raw: unknown): HamkarBrandColors {
|
||||
const brand = raw && typeof raw === 'object' ? (raw as Record<string, any>) : {}
|
||||
return {
|
||||
primaryColor: asColor(brand.primaryColor),
|
||||
secondaryColor: asColor(brand.secondaryColor),
|
||||
backgroundColor: asColor(brand.backgroundColor),
|
||||
}
|
||||
}
|
||||
|
||||
export function createDefaultHamkarUiConfig(): HamkarUiConfig {
|
||||
return {
|
||||
kind: 'hamkar',
|
||||
@@ -76,55 +117,74 @@ export function createDefaultHamkarUiConfig(): HamkarUiConfig {
|
||||
}
|
||||
}
|
||||
|
||||
export function cloneHamkarSnapshot(config: {
|
||||
registryMode: string
|
||||
brand: HamkarBrandColors
|
||||
settings: HamkarUiSettings
|
||||
}): HamkarAppearanceSnapshot {
|
||||
return {
|
||||
registryMode: String(config.registryMode || 'dynamicMode').trim() || 'dynamicMode',
|
||||
brand: { ...config.brand },
|
||||
settings: { ...config.settings },
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
const settings = normalizeSettings(input.settings)
|
||||
const brand = normalizeBrand(input.brand)
|
||||
|
||||
let defaultSnapshot: HamkarAppearanceSnapshot | undefined
|
||||
if (input.defaultSnapshot && typeof input.defaultSnapshot === 'object') {
|
||||
const snap = input.defaultSnapshot as Record<string, any>
|
||||
defaultSnapshot = {
|
||||
registryMode: String(snap.registryMode || registryMode).trim() || 'dynamicMode',
|
||||
brand: normalizeBrand(snap.brand),
|
||||
settings: normalizeSettings(snap.settings ?? settings),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
brand,
|
||||
settings,
|
||||
defaultSnapshot,
|
||||
}
|
||||
}
|
||||
|
||||
/** Payload for API: empty color strings → null */
|
||||
/** Payload for API: empty color strings → null; always keep defaultSnapshot if present */
|
||||
export function hamkarUiToApiPayload(config: HamkarUiConfig) {
|
||||
const normalized = normalizeHamkarUiConfig(config)
|
||||
const emptyToNull = (v: string) => (v.trim() ? v.trim() : null)
|
||||
const snap = normalized.defaultSnapshot
|
||||
? {
|
||||
registryMode: normalized.defaultSnapshot.registryMode,
|
||||
brand: {
|
||||
primaryColor: emptyToNull(normalized.defaultSnapshot.brand.primaryColor),
|
||||
secondaryColor: emptyToNull(normalized.defaultSnapshot.brand.secondaryColor),
|
||||
backgroundColor: emptyToNull(normalized.defaultSnapshot.brand.backgroundColor),
|
||||
},
|
||||
settings: { ...normalized.defaultSnapshot.settings },
|
||||
}
|
||||
: undefined
|
||||
|
||||
return {
|
||||
...normalized,
|
||||
kind: 'hamkar' as const,
|
||||
settingsManaged: normalized.settingsManaged,
|
||||
registryMode: normalized.registryMode,
|
||||
brand: {
|
||||
primaryColor: emptyToNull(normalized.brand.primaryColor),
|
||||
secondaryColor: emptyToNull(normalized.brand.secondaryColor),
|
||||
backgroundColor: emptyToNull(normalized.brand.backgroundColor),
|
||||
},
|
||||
settings: { ...normalized.settings },
|
||||
...(snap ? { defaultSnapshot: snap } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,3 +197,13 @@ export function hamkarUiToSettingsList(config: HamkarUiConfig) {
|
||||
{ key: 'orderUI', value: config.settings.orderUI },
|
||||
]
|
||||
}
|
||||
|
||||
/** Resolve original mode from tenant notes (`registryMode:X` or `mode:X`). */
|
||||
export function registryModeFromNotes(notes?: string | null): string | null {
|
||||
if (!notes) return null
|
||||
return (
|
||||
notes.match(/registryMode:([^\s|]+)/)?.[1] ||
|
||||
notes.match(/(?:^|\|)\s*mode:([^\s|]+)/)?.[1] ||
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user