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, }) }