fix(home): show quote skeleton while loading and harden auth/images

Prefer server quote status, avoid false no-quote before ready, toast on 401 re-login, and keep product/banner image URLs absolute.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-26 16:59:29 +03:30
co-authored by Cursor
parent ff3a1cb00b
commit 4675a5b5e3
10 changed files with 128 additions and 17 deletions
+1 -5
View File
@@ -15,11 +15,7 @@ function goHome() {
}
function goBack() {
if (window.history.length > 1) {
router.back()
return
}
goHome()
router.back()
}
</script>
@@ -21,6 +21,14 @@ export function resolveAboutSoftwareBody(
return { kind: 'text', text: getDefaultAboutSoftwareText(projectName) }
}
if (looksLikeHtml(value)) {
const stripped = value
.replace(/<[^>]+>/g, ' ')
.replace(/&nbsp;/gi, ' ')
.replace(/\s+/g, ' ')
.trim()
if (!stripped) {
return { kind: 'text', text: getDefaultAboutSoftwareText(projectName) }
}
return { kind: 'html', html: value }
}
return { kind: 'text', text: value }
@@ -1,6 +1,7 @@
import { ref } from 'vue'
import { getConstByKey } from '@/api/const'
import { useSettingsStore } from '@/store/settingsStore'
import { normalizePublicFileUrl } from '@/utils/productImage'
function parseJsonArray(raw: unknown): unknown[] {
if (raw == null || raw === '') {
@@ -69,7 +70,14 @@ async function fetchBanners(force = false) {
}
}
banners.value = list.filter((banner) => (banner as { show?: boolean }).show === true)
banners.value = list
.filter((banner) => (banner as { show?: boolean }).show === true)
.map((banner) => {
if (!banner || typeof banner !== 'object') return banner
const row = banner as { icon?: unknown }
if (typeof row.icon !== 'string') return banner
return { ...row, icon: normalizePublicFileUrl(row.icon) ?? '' }
})
bannersLoaded = true
} catch (error) {
console.error('Error loading banners:', error)
@@ -6,7 +6,18 @@
'dhome-hero-price--boursecu-closed': isBoursecu && displayState !== 'open',
}"
>
<template v-if="displayState !== 'open'">
<div
v-if="quotePending"
class="flex flex-col items-center gap-2 py-4"
aria-busy="true"
aria-label="در حال بارگذاری قیمت"
>
<div class="h-3 w-24 rounded bg-gray-200 animate-pulse" />
<div class="h-4 w-32 rounded bg-gray-200 animate-pulse" />
<div class="h-8 w-40 rounded bg-gray-200 animate-pulse" />
</div>
<template v-else-if="displayState !== 'open'">
<div class="dhome-hero-price__closed" :class="{ 'dhome-hero-price__closed--default': !isBoursecu }">
<div class="dhome-hero-price__closed-icon">
<BasicIcon
@@ -61,6 +72,7 @@ import {
NO_QUOTE_MESSAGE,
getPriceDisplayState,
getStoreClosedMessage,
isHomeQuotePending,
} from '@/utils/storeHours'
import { formatSignedPercentChange } from '@/utils/usePrice'
@@ -69,6 +81,7 @@ const userStore = useUserStore()
const isBoursecu = isBoursecuProject()
const displayState = computed(() => getPriceDisplayState())
const quotePending = computed(() => isHomeQuotePending())
const statusTitle = computed(() =>
displayState.value === 'closed' ? 'فروشگاه بسته است' : NO_QUOTE_MESSAGE