Sync live quote handling, SSE product images, store hours, file URL normalization, PWA icon cache, TV monitor, about software, SuperAdmin menu gates, price alerts, and related UX from modular while preserving boursecu branding and config constraints. Co-authored-by: Cursor <cursoragent@cursor.com>
91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
import { resolveProjectBase } from '@/themeConfig'
|
|
import type { User } from '@/models/login'
|
|
|
|
export interface ProductFile {
|
|
id?: number
|
|
url?: string | null
|
|
src?: string
|
|
full_src?: string
|
|
}
|
|
|
|
function projectBase(): string {
|
|
return resolveProjectBase().replace(/\/$/, '')
|
|
}
|
|
|
|
function storageRelativePath(value: string): string | null {
|
|
const match = value.match(/(?:^|\/)(storage\/.+)$/i)
|
|
return match ? match[1].replace(/^\/+/, '') : null
|
|
}
|
|
|
|
function joinProjectUrl(relative: string): string {
|
|
const path = relative.replace(/^\//, '')
|
|
let base = projectBase()
|
|
if (!base && typeof window !== 'undefined') {
|
|
base = `${window.location.origin}/server`
|
|
}
|
|
if (!base) {
|
|
return `/server/${path}`
|
|
}
|
|
base = base.replace(/\/$/, '')
|
|
if (path.startsWith('storage/') && !/\/(server|api)(\/|$)/i.test(base)) {
|
|
base = `${base}/server`
|
|
}
|
|
let url = `${base}/${path}`
|
|
if (typeof window !== 'undefined' && window.location.protocol === 'https:') {
|
|
url = url.replace(/^http:\/\//i, 'https://')
|
|
}
|
|
return url
|
|
}
|
|
|
|
/**
|
|
* URL فایل روی API — مسیرهای `storage/` و آدرسهای localhost/http
|
|
* را به `node_server` میچسباند تا روی اندروید (PWA/TWA) لود شوند.
|
|
*/
|
|
export function normalizePublicFileUrl(raw?: string | null): string | null {
|
|
if (raw == null) return null
|
|
const trimmed = String(raw).trim()
|
|
if (!trimmed || trimmed.toLowerCase() === 'null' || trimmed.toLowerCase() === 'undefined') {
|
|
return null
|
|
}
|
|
if (/^(data:|blob:)/i.test(trimmed)) return trimmed
|
|
|
|
const storagePath = storageRelativePath(trimmed)
|
|
if (storagePath) {
|
|
return joinProjectUrl(storagePath)
|
|
}
|
|
|
|
if (/^https?:\/\//i.test(trimmed)) {
|
|
if (typeof window !== 'undefined' && window.location.protocol === 'https:') {
|
|
return trimmed.replace(/^http:\/\//i, 'https://')
|
|
}
|
|
return trimmed
|
|
}
|
|
|
|
return joinProjectUrl(trimmed)
|
|
}
|
|
|
|
export function getFileUrl(file?: ProductFile | null): string | null {
|
|
if (!file) return null
|
|
|
|
for (const value of [file.full_src, file.url, file.src]) {
|
|
if (!value) continue
|
|
const normalized = normalizePublicFileUrl(value)
|
|
if (normalized) return normalized
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
/** Resolve profile avatar URL from GET /customer/me payload */
|
|
export function getUserAvatarUrl(user?: User | null): string | null {
|
|
if (!user) return null
|
|
if (typeof user.avatar_url === 'string' && user.avatar_url) {
|
|
return normalizePublicFileUrl(user.avatar_url)
|
|
}
|
|
if (typeof user.full_src === 'string' && user.full_src) {
|
|
return normalizePublicFileUrl(user.full_src)
|
|
}
|
|
const nested = user.avatar || user.file
|
|
return getFileUrl(nested as ProductFile | undefined)
|
|
}
|