feat: align piggy, support chat and numeric input with modular app
Persian keyboards send ٫ ، and / as decimal or thousand separators, so amount and weight fields now normalize input before parsing. Support chat reopens an existing thread instead of starting a new one, and axios surfaces the server message on failure. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,6 +13,49 @@ export function persianToEnglishDigits(value: string | number | null | undefined
|
||||
.replace(/٫/g, '.')
|
||||
}
|
||||
|
||||
/**
|
||||
* اعداد فارسی/عربی + جداکنندههای اعشار رایج ماشینحساب فارسی.
|
||||
* - `commaAsThousand` true (مبلغ): `,` / `،` حذف میشوند
|
||||
* - `commaAsThousand` false (وزن/اعشار): `,` / `،` هم → `.`
|
||||
*/
|
||||
export function normalizeDecimalInput(
|
||||
val: string | number | null | undefined,
|
||||
options?: { commaAsThousand?: boolean }
|
||||
): string {
|
||||
if (val == null || val === '') return ''
|
||||
|
||||
const commaAsThousand = options?.commaAsThousand ?? true
|
||||
let normalized = persianToEnglishDigits(String(val))
|
||||
|
||||
if (commaAsThousand) {
|
||||
normalized = normalized.replace(/[,،]/g, '')
|
||||
} else {
|
||||
normalized = normalized.replace(/[,،]/g, '.')
|
||||
}
|
||||
|
||||
normalized = normalized
|
||||
.replace(/\//g, '.')
|
||||
.replace(/[^\d.]/g, '')
|
||||
|
||||
const firstDot = normalized.indexOf('.')
|
||||
if (firstDot !== -1) {
|
||||
normalized =
|
||||
normalized.slice(0, firstDot + 1) +
|
||||
normalized.slice(firstDot + 1).replace(/\./g, '')
|
||||
}
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
export function parseDecimalInput(
|
||||
val: string | number | null | undefined,
|
||||
options?: { commaAsThousand?: boolean }
|
||||
): number {
|
||||
const normalized = normalizeDecimalInput(val, options)
|
||||
if (!normalized) return NaN
|
||||
return Number(normalized)
|
||||
}
|
||||
|
||||
export function shouldNormalizeInputDigits(options: {
|
||||
isPassword?: boolean
|
||||
type?: string
|
||||
@@ -27,3 +70,16 @@ export function shouldNormalizeInputDigits(options: {
|
||||
const mode = String(options.inputmode ?? '').trim().toLowerCase()
|
||||
return mode === 'numeric' || mode === 'decimal' || mode === 'number'
|
||||
}
|
||||
|
||||
/** فیلدهای وزن/مبلغ/عدد اعشاری که باید `/` را ممیز بگیرند */
|
||||
export function shouldNormalizeDecimalSeparators(options: {
|
||||
isPassword?: boolean
|
||||
type?: string
|
||||
inputmode?: string
|
||||
}): boolean {
|
||||
if (options.isPassword) return false
|
||||
const type = String(options.type ?? '').trim().toLowerCase()
|
||||
if (type === 'price' || type === 'number') return true
|
||||
const mode = String(options.inputmode ?? '').trim().toLowerCase()
|
||||
return mode === 'numeric' || mode === 'decimal' || mode === 'number'
|
||||
}
|
||||
|
||||
@@ -87,8 +87,11 @@ export function parseSupportMessengersJson(raw?: string | null): SupportMessenge
|
||||
export function resolveSupportMessengersFromSettings(
|
||||
settings: Array<{ key?: string; value?: unknown }>,
|
||||
): SupportMessengerEntry[] {
|
||||
const fromJson = parseSupportMessengersJson(getSupportSettingValue(settings, 'support_messengers'))
|
||||
if (fromJson.length > 0) return fromJson
|
||||
// اگر کلید جدید در تنظیمات هست (حتی خالی/null)، فقط همان لیست معتبر است — legacy بله را برنگردان
|
||||
const hasMessengersKey = settings.some((row) => row?.key === 'support_messengers')
|
||||
if (hasMessengersKey) {
|
||||
return parseSupportMessengersJson(getSupportSettingValue(settings, 'support_messengers'))
|
||||
}
|
||||
|
||||
const legacyMessenger = getSupportSettingValue(settings, 'support_messenger').toLowerCase()
|
||||
const legacyId = getSupportSettingValue(settings, 'support_messenger_id')
|
||||
@@ -107,9 +110,8 @@ export function resolveSupportMessengerEntries(
|
||||
): ResolvedSupportMessengerEntry[] {
|
||||
const resolved = settings ?? supportMessengerSettings.value
|
||||
const text = (messageText ?? '').trim()
|
||||
const source = resolved.messengers?.length
|
||||
? resolved.messengers
|
||||
: resolveLegacySingleMessenger(resolved)
|
||||
// فقط لیست resolveشده؛ قبل از load خالی است تا legacy دوباره دکمه نسازد
|
||||
const source = Array.isArray(resolved.messengers) ? resolved.messengers : []
|
||||
|
||||
return source
|
||||
.map((entry) => {
|
||||
@@ -125,14 +127,6 @@ export function resolveSupportMessengerEntries(
|
||||
.filter((entry): entry is ResolvedSupportMessengerEntry => entry !== null)
|
||||
}
|
||||
|
||||
function resolveLegacySingleMessenger(settings: SupportMessengerSettings): SupportMessengerEntry[] {
|
||||
const messenger = (settings.messenger ?? '').trim().toLowerCase()
|
||||
const id = (settings.messengerId ?? '').trim()
|
||||
if (!messenger || !id) return []
|
||||
if (!VALID_SUPPORT_MESSENGERS.has(messenger)) return []
|
||||
return [{ messenger, id }]
|
||||
}
|
||||
|
||||
/** @deprecated از depositGuideText استفاده کنید */
|
||||
export const DEFAULT_SUPPORT_MESSENGER_TEXT = DEFAULT_DEPOSIT_GUIDE_TEXT
|
||||
|
||||
|
||||
+21
-4
@@ -71,29 +71,46 @@ _axios.interceptors.response.use(
|
||||
}
|
||||
|
||||
const apiMessage = extractApiErrorMessage(response?.data)
|
||||
const status = Number(response?.status ?? 0)
|
||||
|
||||
if (response?.status === 403) {
|
||||
showApiError(apiMessage || 'شما دسترسی لازم برای انجام این عملیات را ندارید', config)
|
||||
showApiError(
|
||||
withHttpStatus(apiMessage || 'شما دسترسی لازم برای انجام این عملیات را ندارید', 403),
|
||||
config,
|
||||
)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
if (response?.status === 422) {
|
||||
if (apiMessage) {
|
||||
showApiError(apiMessage, config)
|
||||
showApiError(withHttpStatus(apiMessage, 422), config)
|
||||
} else {
|
||||
showApiError(withHttpStatus('خطا در اعتبارسنجی', 422), config)
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
if (response) {
|
||||
showApiError(apiMessage || DEFAULT_LOAD_ERROR, config)
|
||||
if (status >= 500) {
|
||||
showApiError(withHttpStatus(SERVER_ERROR, status), config)
|
||||
} else {
|
||||
showApiError(withHttpStatus(apiMessage || DEFAULT_LOAD_ERROR, status), config)
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
showApiError(DEFAULT_LOAD_ERROR, config)
|
||||
// بدون status (نه 4xx نه 5xx): قطع شبکه، timeout، DNS، …
|
||||
showApiError(NETWORK_ERROR, config)
|
||||
return Promise.reject(error)
|
||||
})
|
||||
|
||||
const DEFAULT_LOAD_ERROR = 'خطا در بارگذاری اطلاعات'
|
||||
const SERVER_ERROR = 'خطا در برقراری ارتباط با سرور'
|
||||
const NETWORK_ERROR = 'لطفا اینترنت خود را بررسی کنید و مجددا تلاش کنید'
|
||||
|
||||
function withHttpStatus(message: string, status: number): string {
|
||||
return `${message} (${status})`
|
||||
}
|
||||
|
||||
function extractApiErrorMessage(data: unknown): string {
|
||||
if (!data || typeof data !== 'object') {
|
||||
|
||||
Reference in New Issue
Block a user