enh
This commit is contained in:
parent
1139bf0f46
commit
360289d3c6
2 changed files with 43 additions and 93 deletions
|
|
@ -12,7 +12,7 @@ export default function HomeLayout() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="product-detail/[id]"
|
name="product-detail"
|
||||||
options={{
|
options={{
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
title: "Product Details",
|
title: "Product Details",
|
||||||
|
|
|
||||||
|
|
@ -6,82 +6,52 @@ import { AllProductsApiType, StoresApiType, SlotsApiType, EssentialConstsApiType
|
||||||
import { CACHE_FILENAMES } from "@packages/shared";
|
import { CACHE_FILENAMES } from "@packages/shared";
|
||||||
import { StorageServiceCasual } from 'common-ui';
|
import { StorageServiceCasual } from 'common-ui';
|
||||||
|
|
||||||
// Local useGetEssentialConsts hook
|
// The only persisted payload: the small essential-consts config object
|
||||||
|
// (assetsDomain, cache versions, etc.). The big data payloads (products,
|
||||||
|
// stores, slots, availability, banners) are fetched fresh each session and
|
||||||
|
// kept in memory by react-query — they are NOT written to storage.
|
||||||
|
const ESSENTIAL_CONSTS_KEY = 'essential_consts'
|
||||||
|
|
||||||
export const useGetEssentialConsts = () => {
|
export const useGetEssentialConsts = () => {
|
||||||
const query = trpc.common.essentialConsts.useQuery(undefined, {
|
const [initialData, setInitialData] = useState<EssentialConstsApiType | undefined>(undefined)
|
||||||
refetchInterval: 60000,
|
|
||||||
})
|
|
||||||
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<T> {
|
|
||||||
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<T>(key: string): Promise<PersistedCache<T> | null> {
|
|
||||||
const raw = await StorageServiceCasual.getItem(key)
|
|
||||||
if (!raw) return null
|
|
||||||
try {
|
|
||||||
return JSON.parse(raw) as PersistedCache<T>
|
|
||||||
} catch {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function writePersistedCache<T>(key: string, version: string, data: T): Promise<void> {
|
|
||||||
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<T>(storageKey: string, version: string | null) {
|
|
||||||
const [initialData, setInitialData] = useState<T | undefined>(undefined)
|
|
||||||
const [isReady, setIsReady] = useState(false)
|
const [isReady, setIsReady] = useState(false)
|
||||||
|
|
||||||
|
// Load the last persisted response so config is available immediately.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
setIsReady(false)
|
StorageServiceCasual.getItem(ESSENTIAL_CONSTS_KEY).then((raw) => {
|
||||||
setInitialData(undefined)
|
|
||||||
if (!version) {
|
|
||||||
setIsReady(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
readPersistedCache<T>(storageKey).then((persisted) => {
|
|
||||||
if (cancelled) return
|
if (cancelled) return
|
||||||
if (persisted && persisted.version === version) {
|
if (raw) {
|
||||||
setInitialData(persisted.data)
|
try {
|
||||||
|
setInitialData(JSON.parse(raw))
|
||||||
|
} catch {
|
||||||
|
// ignore corrupt cache
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setIsReady(true)
|
setIsReady(true)
|
||||||
})
|
})
|
||||||
return () => { cancelled = true }
|
return () => { cancelled = true }
|
||||||
}, [storageKey, version])
|
}, [])
|
||||||
|
|
||||||
return { initialData, isReady }
|
const query = trpc.common.essentialConsts.useQuery(undefined, {
|
||||||
|
refetchInterval: 60000,
|
||||||
|
enabled: isReady,
|
||||||
|
placeholderData: initialData,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Persist the latest response.
|
||||||
|
useEffect(() => {
|
||||||
|
if (query.data) {
|
||||||
|
StorageServiceCasual.setItem(ESSENTIAL_CONSTS_KEY, JSON.stringify(query.data))
|
||||||
|
}
|
||||||
|
}, [query.data])
|
||||||
|
|
||||||
|
return { ...query, refetch: query.refetch }
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductsResponse = AllProductsApiType;
|
type ProductsResponse = AllProductsApiType;
|
||||||
type StoresResponse = StoresApiType;
|
type StoresResponse = StoresApiType;
|
||||||
type SlotsResponse = SlotsApiType;
|
type SlotsResponse = SlotsApiType;
|
||||||
type EssentialConstsResponse = EssentialConstsApiType;
|
|
||||||
type BannersResponse = BannersApiType;
|
type BannersResponse = BannersApiType;
|
||||||
type StoreWithProductsResponse = StoreWithProductsApiType;
|
type StoreWithProductsResponse = StoreWithProductsApiType;
|
||||||
type AvailabilityResponse = AvailabilityApiType;
|
type AvailabilityResponse = AvailabilityApiType;
|
||||||
|
|
@ -142,7 +112,6 @@ export function useAllProducts() {
|
||||||
const cacheUrl = useCacheUrl(CACHE_FILENAMES.products)
|
const cacheUrl = useCacheUrl(CACHE_FILENAMES.products)
|
||||||
const { data: availabilityData } = useAvailability()
|
const { data: availabilityData } = useAvailability()
|
||||||
const version = cacheUrl ?? ''
|
const version = cacheUrl ?? ''
|
||||||
const { initialData, isReady } = usePersistedCache<ProductsResponse>(CACHE_STORAGE_KEYS.products, version)
|
|
||||||
|
|
||||||
const productsQuery = useQuery<ProductsResponse>({
|
const productsQuery = useQuery<ProductsResponse>({
|
||||||
queryKey: ['all-products', version],
|
queryKey: ['all-products', version],
|
||||||
|
|
@ -151,12 +120,10 @@ export function useAllProducts() {
|
||||||
throw new Error('Cache URL not available')
|
throw new Error('Cache URL not available')
|
||||||
}
|
}
|
||||||
const response = await axios.get<ProductsResponse>(cacheUrl)
|
const response = await axios.get<ProductsResponse>(cacheUrl)
|
||||||
await writePersistedCache(CACHE_STORAGE_KEYS.products, version, response.data)
|
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
staleTime: 60000, // 1 minute
|
staleTime: 60000, // 1 minute
|
||||||
enabled: !!cacheUrl && isReady,
|
enabled: !!cacheUrl,
|
||||||
placeholderData: initialData,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const mergedProducts = React.useMemo(() => {
|
const mergedProducts = React.useMemo(() => {
|
||||||
|
|
@ -197,7 +164,6 @@ export function useAllProducts() {
|
||||||
export function useAvailability() {
|
export function useAvailability() {
|
||||||
const cacheUrl = useAvailabilityCacheUrl()
|
const cacheUrl = useAvailabilityCacheUrl()
|
||||||
const version = cacheUrl ?? ''
|
const version = cacheUrl ?? ''
|
||||||
const { initialData, isReady } = usePersistedCache<AvailabilityResponse>(CACHE_STORAGE_KEYS.availability, version)
|
|
||||||
|
|
||||||
return useQuery<AvailabilityResponse>({
|
return useQuery<AvailabilityResponse>({
|
||||||
queryKey: ['availability', version],
|
queryKey: ['availability', version],
|
||||||
|
|
@ -206,19 +172,16 @@ export function useAvailability() {
|
||||||
throw new Error('Cache URL not available')
|
throw new Error('Cache URL not available')
|
||||||
}
|
}
|
||||||
const response = await axios.get<AvailabilityResponse>(cacheUrl)
|
const response = await axios.get<AvailabilityResponse>(cacheUrl)
|
||||||
await writePersistedCache(CACHE_STORAGE_KEYS.availability, version, response.data)
|
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
staleTime: 60000, // 1 minute
|
staleTime: 60000, // 1 minute
|
||||||
enabled: !!cacheUrl && isReady,
|
enabled: !!cacheUrl,
|
||||||
placeholderData: initialData,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useStores() {
|
export function useStores() {
|
||||||
const cacheUrl = useCacheUrl(CACHE_FILENAMES.stores)
|
const cacheUrl = useCacheUrl(CACHE_FILENAMES.stores)
|
||||||
const version = cacheUrl ?? ''
|
const version = cacheUrl ?? ''
|
||||||
const { initialData, isReady } = usePersistedCache<StoresResponse>(CACHE_STORAGE_KEYS.stores, version)
|
|
||||||
|
|
||||||
return useQuery<StoresResponse>({
|
return useQuery<StoresResponse>({
|
||||||
queryKey: ['stores', version],
|
queryKey: ['stores', version],
|
||||||
|
|
@ -227,19 +190,16 @@ export function useStores() {
|
||||||
throw new Error('Cache URL not available')
|
throw new Error('Cache URL not available')
|
||||||
}
|
}
|
||||||
const response = await axios.get<StoresResponse>(cacheUrl)
|
const response = await axios.get<StoresResponse>(cacheUrl)
|
||||||
await writePersistedCache(CACHE_STORAGE_KEYS.stores, version, response.data)
|
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
staleTime: 60000, // 1 minute
|
staleTime: 60000, // 1 minute
|
||||||
enabled: !!cacheUrl && isReady,
|
enabled: !!cacheUrl,
|
||||||
placeholderData: initialData,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSlots() {
|
export function useSlots() {
|
||||||
const cacheUrl = useSlotsCacheUrl()
|
const cacheUrl = useSlotsCacheUrl()
|
||||||
const version = cacheUrl ?? ''
|
const version = cacheUrl ?? ''
|
||||||
const { initialData, isReady } = usePersistedCache<SlotsResponse>(CACHE_STORAGE_KEYS.slots, version)
|
|
||||||
|
|
||||||
return useQuery<SlotsResponse>({
|
return useQuery<SlotsResponse>({
|
||||||
queryKey: ['slots', version],
|
queryKey: ['slots', version],
|
||||||
|
|
@ -247,36 +207,29 @@ export function useSlots() {
|
||||||
if (!cacheUrl) {
|
if (!cacheUrl) {
|
||||||
throw new Error('Cache URL not available')
|
throw new Error('Cache URL not available')
|
||||||
}
|
}
|
||||||
const response = await axios.get<SlotsResponse>(cacheUrl+'?v=123')
|
const response = await axios.get<SlotsResponse>(cacheUrl + '?v=123')
|
||||||
await writePersistedCache(CACHE_STORAGE_KEYS.slots, version, response.data)
|
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
staleTime: 60000, // 1 minute
|
staleTime: 60000, // 1 minute
|
||||||
enabled: !!cacheUrl && isReady,
|
enabled: !!cacheUrl,
|
||||||
placeholderData: initialData,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useBanners() {
|
export function useBanners() {
|
||||||
const cacheUrl = useCacheUrl(CACHE_FILENAMES.banners)
|
const cacheUrl = useCacheUrl(CACHE_FILENAMES.banners)
|
||||||
const version = cacheUrl ?? ''
|
const version = cacheUrl ?? ''
|
||||||
const { initialData, isReady } = usePersistedCache<BannersResponse>(CACHE_STORAGE_KEYS.banners, version)
|
|
||||||
|
|
||||||
return useQuery<BannersResponse>({
|
return useQuery<BannersResponse>({
|
||||||
queryKey: ['banners', version],
|
queryKey: ['banners', version],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
|
|
||||||
if (!cacheUrl) {
|
if (!cacheUrl) {
|
||||||
throw new Error('Cache URL not available')
|
throw new Error('Cache URL not available')
|
||||||
}
|
}
|
||||||
const response = await axios.get<BannersResponse>(cacheUrl)
|
const response = await axios.get<BannersResponse>(cacheUrl)
|
||||||
await writePersistedCache(CACHE_STORAGE_KEYS.banners, version, response.data)
|
|
||||||
return response.data
|
return response.data
|
||||||
|
|
||||||
},
|
},
|
||||||
staleTime: 60000, // 1 minute
|
staleTime: 60000, // 1 minute
|
||||||
enabled: !!cacheUrl && isReady,
|
enabled: !!cacheUrl,
|
||||||
placeholderData: initialData,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,7 +244,6 @@ export function useStoreWithProducts(storeId: number) {
|
||||||
? `${assetsDomain}${apiCacheKey}/v-${cacheVersion}/stores/${storeId}.json`
|
? `${assetsDomain}${apiCacheKey}/v-${cacheVersion}/stores/${storeId}.json`
|
||||||
: null
|
: null
|
||||||
const version = cacheUrl ?? ''
|
const version = cacheUrl ?? ''
|
||||||
const { initialData, isReady } = usePersistedCache<StoreWithProductsResponse>(CACHE_STORAGE_KEYS.storeProducts(storeId), version)
|
|
||||||
|
|
||||||
return useQuery<StoreWithProductsResponse>({
|
return useQuery<StoreWithProductsResponse>({
|
||||||
queryKey: ['store-with-products', storeId, version],
|
queryKey: ['store-with-products', storeId, version],
|
||||||
|
|
@ -300,11 +252,9 @@ export function useStoreWithProducts(storeId: number) {
|
||||||
throw new Error('Cache URL not available')
|
throw new Error('Cache URL not available')
|
||||||
}
|
}
|
||||||
const response = await axios.get<StoreWithProductsResponse>(cacheUrl)
|
const response = await axios.get<StoreWithProductsResponse>(cacheUrl)
|
||||||
await writePersistedCache(CACHE_STORAGE_KEYS.storeProducts(storeId), version, response.data)
|
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
staleTime: 60000, // 1 minute
|
staleTime: 60000, // 1 minute
|
||||||
enabled: !!cacheUrl && isReady,
|
enabled: !!cacheUrl,
|
||||||
placeholderData: initialData,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue