2026-06-16 19:47:28 +03:30
|
|
|
import { resolveProjectBase } from '@/themeConfig'
|
2026-06-03 11:57:40 +03:30
|
|
|
import type { User } from '@/models/login'
|
2026-05-18 10:13:50 +03:30
|
|
|
|
|
|
|
|
export interface ProductFile {
|
|
|
|
|
id?: number
|
|
|
|
|
url?: string | null
|
|
|
|
|
src?: string
|
2026-06-03 11:57:40 +03:30
|
|
|
full_src?: string
|
2026-05-18 10:13:50 +03:30
|
|
|
}
|
|
|
|
|
|
2026-09-02 08:26:52 -04:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 10:13:50 +03:30
|
|
|
export function getFileUrl(file?: ProductFile | null): string | null {
|
|
|
|
|
if (!file) return null
|
2026-06-09 21:04:22 +03:30
|
|
|
|
|
|
|
|
for (const value of [file.full_src, file.url, file.src]) {
|
|
|
|
|
if (!value) continue
|
2026-09-02 08:26:52 -04:00
|
|
|
const normalized = normalizePublicFileUrl(value)
|
|
|
|
|
if (normalized) return normalized
|
2026-06-09 21:04:22 +03:30
|
|
|
}
|
|
|
|
|
|
2026-09-02 08:26:52 -04:00
|
|
|
return null
|
2026-05-18 10:13:50 +03:30
|
|
|
}
|
2026-06-03 11:57:40 +03:30
|
|
|
|
|
|
|
|
/** Resolve profile avatar URL from GET /customer/me payload */
|
|
|
|
|
export function getUserAvatarUrl(user?: User | null): string | null {
|
|
|
|
|
if (!user) return null
|
2026-09-02 08:26:52 -04:00
|
|
|
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)
|
|
|
|
|
}
|
2026-06-03 11:57:40 +03:30
|
|
|
const nested = user.avatar || user.file
|
|
|
|
|
return getFileUrl(nested as ProductFile | undefined)
|
|
|
|
|
}
|