From 1729b028d00aedbb1608cd50520ca5ce2fe91a50 Mon Sep 17 00:00:00 2001
From: shafi54 <108669266+shafi-aviz@users.noreply.github.com>
Date: Sat, 8 Aug 2026 23:45:19 +0530
Subject: [PATCH] enh
---
.../(tabs)/flash-delivery/_layout.tsx | 15 +--
.../app/(drawer)/(tabs)/home/index.tsx | 6 +-
apps/user-ui/components/WebViewWrapper.tsx | 15 +--
apps/user-ui/src/hooks/prominent-api-hooks.ts | 115 ++++++++++++++++--
4 files changed, 110 insertions(+), 41 deletions(-)
diff --git a/apps/user-ui/app/(drawer)/(tabs)/flash-delivery/_layout.tsx b/apps/user-ui/app/(drawer)/(tabs)/flash-delivery/_layout.tsx
index a9e39dc..edefde9 100644
--- a/apps/user-ui/app/(drawer)/(tabs)/flash-delivery/_layout.tsx
+++ b/apps/user-ui/app/(drawer)/(tabs)/flash-delivery/_layout.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { View, ActivityIndicator } from 'react-native';
+import { View } from 'react-native';
import { Slot } from 'expo-router';
import { trpc } from '@/src/trpc-client';
import { MyText, MyTouchableOpacity, tw, AppContainer } from 'common-ui';
@@ -9,18 +9,7 @@ import { useGetEssentialConsts } from '@/src/hooks/prominent-api-hooks';
export default function FlashDeliveryBaseLayout() {
const router = useRouter();
- const { data: essentialConsts, isLoading } = useGetEssentialConsts();
-
- if (isLoading) {
- return (
-
-
-
- Loading...
-
-
- );
- }
+ const { data: essentialConsts } = useGetEssentialConsts();
const isFlashDeliveryEnabled = essentialConsts?.isFlashDeliveryEnabled ?? true;
diff --git a/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx b/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx
index 65c7a83..41c0c7f 100755
--- a/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx
+++ b/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx
@@ -514,7 +514,7 @@ export default function Dashboard() {
error,
} = useAllProducts();
- const { data: essentialConsts, isLoading: isLoadingConsts, error: constsError, refetch: refetchConsts } = useGetEssentialConsts();
+ const { data: essentialConsts, error: constsError, refetch: refetchConsts } = useGetEssentialConsts();
const { data: storesData, refetch: refetchStores } = useStores();
const { data: slotsData } = useSlots();
@@ -685,11 +685,11 @@ export default function Dashboard() {
staticStyles.flatListContent
], []);
- if (isLoading || isLoadingConsts) {
+ if (isLoading) {
return (
- {isLoading ? 'Loading products...' : 'Loading app settings...'}
+ Loading products...
);
diff --git a/apps/user-ui/components/WebViewWrapper.tsx b/apps/user-ui/components/WebViewWrapper.tsx
index 4c33cd9..8293643 100644
--- a/apps/user-ui/components/WebViewWrapper.tsx
+++ b/apps/user-ui/components/WebViewWrapper.tsx
@@ -1,9 +1,9 @@
import React, { useState } from 'react';
-import { View, ActivityIndicator } from 'react-native';
+import { View } from 'react-native';
import { WebView } from 'react-native-webview';
import { trpc } from '@/src/trpc-client';
import { useGetEssentialConsts } from '@/src/hooks/prominent-api-hooks';
-import { theme, MyText, MyTouchableOpacity } from 'common-ui';
+import { MyTouchableOpacity } from 'common-ui';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
interface WebViewWrapperProps {
@@ -11,18 +11,9 @@ interface WebViewWrapperProps {
}
export default function WebViewWrapper({ children }: WebViewWrapperProps) {
- const { data: constsData, isLoading } = useGetEssentialConsts();
+ const { data: constsData } = useGetEssentialConsts();
const [isClosed, setIsClosed] = useState(false);
- if (isLoading) {
- return (
-
-
- Loading...
-
- );
- }
-
const webviewHtml = constsData?.webviewHtml;
const isWebviewClosable = constsData?.isWebviewClosable;
diff --git a/apps/user-ui/src/hooks/prominent-api-hooks.ts b/apps/user-ui/src/hooks/prominent-api-hooks.ts
index aada9f8..c0c9a38 100644
--- a/apps/user-ui/src/hooks/prominent-api-hooks.ts
+++ b/apps/user-ui/src/hooks/prominent-api-hooks.ts
@@ -1,9 +1,10 @@
-import React from 'react'
+import React, { useEffect, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import axios from 'axios'
import { trpc } from '@/src/trpc-client'
import { AllProductsApiType, StoresApiType, SlotsApiType, EssentialConstsApiType, BannersApiType, StoreWithProductsApiType, AvailabilityApiType } from "@backend/trpc/router";
import { CACHE_FILENAMES } from "@packages/shared";
+import { StorageServiceCasual } from 'common-ui';
// Local useGetEssentialConsts hook
export const useGetEssentialConsts = () => {
@@ -13,6 +14,70 @@ export const useGetEssentialConsts = () => {
return { ...query, refetch: query.refetch }
}
+// ---------------------------------------------------------------------------
+// Persisted cache helpers — store fetched JSON keyed by its versioned URL.
+// Only refetch + repersist when the version (URL) changes.
+// ---------------------------------------------------------------------------
+
+interface PersistedCache {
+ version: string
+ data: T
+}
+
+const CACHE_STORAGE_KEYS = {
+ products: 'cache:products',
+ stores: 'cache:stores',
+ slots: 'cache:slots',
+ banners: 'cache:banners',
+ availability: 'cache:availability',
+ storeProducts: (storeId: number) => `cache:store:${storeId}`,
+} as const
+
+async function readPersistedCache(key: string): Promise | null> {
+ const raw = await StorageServiceCasual.getItem(key)
+ if (!raw) return null
+ try {
+ return JSON.parse(raw) as PersistedCache
+ } catch {
+ return null
+ }
+}
+
+async function writePersistedCache(key: string, version: string, data: T): Promise {
+ await StorageServiceCasual.setItem(key, JSON.stringify({ version, data }))
+}
+
+/**
+ * Loads the persisted cache for `storageKey` and reports whether its version
+ * matches the current `version`. Returns { initialData, isReady }.
+ * - version matches → serve persisted data, no network refetch needed.
+ * - version differs → signal to refetch (caller's query fetches + repersists).
+ */
+function usePersistedCache(storageKey: string, version: string | null) {
+ const [initialData, setInitialData] = useState(undefined)
+ const [isReady, setIsReady] = useState(false)
+
+ useEffect(() => {
+ let cancelled = false
+ setIsReady(false)
+ setInitialData(undefined)
+ if (!version) {
+ setIsReady(true)
+ return
+ }
+ readPersistedCache(storageKey).then((persisted) => {
+ if (cancelled) return
+ if (persisted && persisted.version === version) {
+ setInitialData(persisted.data)
+ }
+ setIsReady(true)
+ })
+ return () => { cancelled = true }
+ }, [storageKey, version])
+
+ return { initialData, isReady }
+}
+
type ProductsResponse = AllProductsApiType;
type StoresResponse = StoresApiType;
type SlotsResponse = SlotsApiType;
@@ -76,18 +141,22 @@ function useSlotsCacheUrl(): string | null {
export function useAllProducts() {
const cacheUrl = useCacheUrl(CACHE_FILENAMES.products)
const { data: availabilityData } = useAvailability()
+ const version = cacheUrl ?? ''
+ const { initialData, isReady } = usePersistedCache(CACHE_STORAGE_KEYS.products, version)
const productsQuery = useQuery({
- queryKey: ['all-products', cacheUrl],
+ queryKey: ['all-products', version],
queryFn: async () => {
if (!cacheUrl) {
throw new Error('Cache URL not available')
}
const response = await axios.get(cacheUrl)
+ await writePersistedCache(CACHE_STORAGE_KEYS.products, version, response.data)
return response.data
},
staleTime: 60000, // 1 minute
- enabled: !!cacheUrl,
+ enabled: !!cacheUrl && isReady,
+ placeholderData: initialData,
})
const mergedProducts = React.useMemo(() => {
@@ -127,71 +196,87 @@ export function useAllProducts() {
export function useAvailability() {
const cacheUrl = useAvailabilityCacheUrl()
+ const version = cacheUrl ?? ''
+ const { initialData, isReady } = usePersistedCache(CACHE_STORAGE_KEYS.availability, version)
return useQuery({
- queryKey: ['availability', cacheUrl],
+ queryKey: ['availability', version],
queryFn: async () => {
if (!cacheUrl) {
throw new Error('Cache URL not available')
}
const response = await axios.get(cacheUrl)
+ await writePersistedCache(CACHE_STORAGE_KEYS.availability, version, response.data)
return response.data
},
staleTime: 60000, // 1 minute
- enabled: !!cacheUrl,
+ enabled: !!cacheUrl && isReady,
+ placeholderData: initialData,
})
}
export function useStores() {
const cacheUrl = useCacheUrl(CACHE_FILENAMES.stores)
+ const version = cacheUrl ?? ''
+ const { initialData, isReady } = usePersistedCache(CACHE_STORAGE_KEYS.stores, version)
return useQuery({
- queryKey: ['stores', cacheUrl],
+ queryKey: ['stores', version],
queryFn: async () => {
if (!cacheUrl) {
throw new Error('Cache URL not available')
}
const response = await axios.get(cacheUrl)
+ await writePersistedCache(CACHE_STORAGE_KEYS.stores, version, response.data)
return response.data
},
staleTime: 60000, // 1 minute
- enabled: !!cacheUrl,
+ enabled: !!cacheUrl && isReady,
+ placeholderData: initialData,
})
}
export function useSlots() {
const cacheUrl = useSlotsCacheUrl()
+ const version = cacheUrl ?? ''
+ const { initialData, isReady } = usePersistedCache(CACHE_STORAGE_KEYS.slots, version)
return useQuery({
- queryKey: ['slots', cacheUrl],
+ queryKey: ['slots', version],
queryFn: async () => {
if (!cacheUrl) {
throw new Error('Cache URL not available')
}
const response = await axios.get(cacheUrl+'?v=123')
+ await writePersistedCache(CACHE_STORAGE_KEYS.slots, version, response.data)
return response.data
},
staleTime: 60000, // 1 minute
- enabled: !!cacheUrl,
+ enabled: !!cacheUrl && isReady,
+ placeholderData: initialData,
})
}
export function useBanners() {
const cacheUrl = useCacheUrl(CACHE_FILENAMES.banners)
+ const version = cacheUrl ?? ''
+ const { initialData, isReady } = usePersistedCache(CACHE_STORAGE_KEYS.banners, version)
return useQuery({
- queryKey: ['banners', cacheUrl],
+ queryKey: ['banners', version],
queryFn: async () => {
if (!cacheUrl) {
throw new Error('Cache URL not available')
}
const response = await axios.get(cacheUrl)
+ await writePersistedCache(CACHE_STORAGE_KEYS.banners, version, response.data)
return response.data
},
staleTime: 60000, // 1 minute
- enabled: !!cacheUrl,
+ enabled: !!cacheUrl && isReady,
+ placeholderData: initialData,
})
}
@@ -205,17 +290,21 @@ export function useStoreWithProducts(storeId: number) {
const cacheUrl = assetsDomain && apiCacheKey
? `${assetsDomain}${apiCacheKey}/v-${cacheVersion}/stores/${storeId}.json`
: null
+ const version = cacheUrl ?? ''
+ const { initialData, isReady } = usePersistedCache(CACHE_STORAGE_KEYS.storeProducts(storeId), version)
return useQuery({
- queryKey: ['store-with-products', storeId, cacheUrl],
+ queryKey: ['store-with-products', storeId, version],
queryFn: async () => {
if (!cacheUrl) {
throw new Error('Cache URL not available')
}
const response = await axios.get(cacheUrl)
+ await writePersistedCache(CACHE_STORAGE_KEYS.storeProducts(storeId), version, response.data)
return response.data
},
staleTime: 60000, // 1 minute
- enabled: !!cacheUrl,
+ enabled: !!cacheUrl && isReady,
+ placeholderData: initialData,
})
}