Split desktop and mobile layouts with shared page data so APIs fetch once per context.

Add layout shells, desktop views, module-level page caches, and UI fixes for auth, home, wallet, transactions, address, and related pages.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-14 10:08:46 +03:30
co-authored by Cursor
parent 4404b4cb92
commit a5e458ed25
234 changed files with 18425 additions and 5503 deletions
+44
View File
@@ -0,0 +1,44 @@
/**
* Deduplicate async work across remounts (e.g. accidental v-if layout switches).
* Prefer fixing layout with v-show + parent-level fetch; use this in stores/shared loaders.
*/
const cache = new Map<string, unknown>()
const inflight = new Map<string, Promise<unknown>>()
export async function runOnceAsync<T>(key: string, fn: () => Promise<T>, force = false): Promise<T> {
if (!force && cache.has(key)) {
return cache.get(key) as T
}
if (!force && inflight.has(key)) {
return inflight.get(key) as Promise<T>
}
const promise = fn()
.then((result) => {
cache.set(key, result)
return result
})
.finally(() => {
if (inflight.get(key) === promise) {
inflight.delete(key)
}
})
inflight.set(key, promise)
return promise
}
export function resetOnceAsync(key: string) {
cache.delete(key)
inflight.delete(key)
}
export function resetOnceAsyncByPrefix(prefix: string) {
for (const key of [...cache.keys(), ...inflight.keys()]) {
if (key.startsWith(prefix)) {
resetOnceAsync(key)
}
}
}
+32
View File
@@ -0,0 +1,32 @@
import { getProducts } from '@/api/products'
import type { Product } from '@/models/products'
/** Shared product catalog — dedupes getProducts() across wallet assets and chart offset. */
let catalogCache: Product[] | null = null
let catalogPromise: Promise<Product[]> | null = null
export function getProductsCatalog(force = false): Promise<Product[]> {
if (!force && catalogCache) {
return Promise.resolve(catalogCache)
}
if (!force && catalogPromise) {
return catalogPromise
}
catalogPromise = getProducts()
.then((products) => {
catalogCache = products ?? []
return catalogCache
})
.finally(() => {
catalogPromise = null
})
return catalogPromise
}
export function clearProductsCatalog() {
catalogCache = null
catalogPromise = null
}
+3 -3
View File
@@ -1,6 +1,6 @@
import { ref } from 'vue'
import { getWalletAssets } from '@/api/wallet'
import { getProducts } from '@/api/products'
import { getProductsCatalog } from '@/composables/useProductsCatalog'
import type { WalletAssetsSummary } from '@/models/walletAssets'
import { enrichInventoryCardsWithCatalog } from '@/utils/productAssets'
@@ -17,8 +17,8 @@ export function useWalletAssets() {
return assetsPromise
}
// سرور مرجع دارایی‌هاست (main_wallet_products هم سمت سرور هندل می‌شود)؛ کلاینت فقط نمایش می‌دهد
assetsPromise = Promise.all([getWalletAssets(), getProducts()])
// Reuses cached getProductsCatalog() — same catalog as chart offset logic
assetsPromise = Promise.all([getWalletAssets(), getProductsCatalog(force)])
.then(([data, catalog]) => {
assetsSummary.value = {
...data,