fix(offline): IndexedDB fallback when API returns empty on /recipes & /season/[month]
These two pages only fell back to IndexedDB when navigator.onLine was false. On mobile the device often reports online while the origin is flaky (502 / slow cellular / cached shell with stale connectivity), so the API call returned nothing and the pages rendered 0 recipes. Now both also fall back to IndexedDB when the API attempt yields an empty list, matching the pattern already used by [name]/+page.ts and icon/[icon]/+page.ts.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homepage",
|
||||
"version": "1.67.4",
|
||||
"version": "1.67.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -16,26 +16,31 @@ function addFavoriteStatus<T extends { _id: unknown }>(
|
||||
}));
|
||||
}
|
||||
|
||||
async function loadFromIndexedDB() {
|
||||
if (!await isOfflineDataAvailable()) return null;
|
||||
const [allBrief, seasonRecipes] = await Promise.all([
|
||||
getAllBriefRecipes(),
|
||||
getBriefRecipesInSeasonOn(new Date())
|
||||
]);
|
||||
return {
|
||||
all_brief: rand_array(allBrief),
|
||||
season: rand_array(seasonRecipes),
|
||||
heroIndex: Math.random(),
|
||||
isOffline: true as const
|
||||
};
|
||||
}
|
||||
|
||||
export const load: PageLoad = async ({ params, fetch, parent }) => {
|
||||
const parentData = await parent();
|
||||
const apiBase = `/api/${params.recipeLang}`;
|
||||
const useOfflineData =
|
||||
browser && (isOffline() || parentData.isOffline) && canUseOfflineData();
|
||||
const canUseOffline = browser && canUseOfflineData();
|
||||
const knownOffline = browser && (isOffline() || parentData.isOffline);
|
||||
|
||||
if (useOfflineData) {
|
||||
// Skip the network entirely when device is known offline.
|
||||
if (canUseOffline && knownOffline) {
|
||||
try {
|
||||
if (await isOfflineDataAvailable()) {
|
||||
const [allBrief, seasonRecipes] = await Promise.all([
|
||||
getAllBriefRecipes(),
|
||||
getBriefRecipesInSeasonOn(new Date())
|
||||
]);
|
||||
return {
|
||||
all_brief: rand_array(allBrief),
|
||||
season: rand_array(seasonRecipes),
|
||||
heroIndex: Math.random(),
|
||||
isOffline: true
|
||||
};
|
||||
}
|
||||
const offline = await loadFromIndexedDB();
|
||||
if (offline) return offline;
|
||||
} catch (e) {
|
||||
console.error('Failed to load offline data:', e);
|
||||
}
|
||||
@@ -54,7 +59,18 @@ export const load: PageLoad = async ({ params, fetch, parent }) => {
|
||||
favorites = body.favorites ?? [];
|
||||
}
|
||||
} catch {
|
||||
// Network unreachable — empty data; +page.svelte renders fallback layout.
|
||||
// Network unreachable — IndexedDB fallback below picks up the slack.
|
||||
}
|
||||
|
||||
// API failed or returned empty (502, slow cellular, server hiccup) — fall
|
||||
// back to IndexedDB so the cached PWA shell stays useful.
|
||||
if (canUseOffline && !all_brief.length) {
|
||||
try {
|
||||
const offline = await loadFromIndexedDB();
|
||||
if (offline) return offline;
|
||||
} catch (e) {
|
||||
console.error('Failed to load offline data:', e);
|
||||
}
|
||||
}
|
||||
|
||||
const marked = addFavoriteStatus(all_brief, favorites);
|
||||
|
||||
@@ -15,23 +15,28 @@ function addFavoriteStatus<T extends { _id: unknown }>(
|
||||
}));
|
||||
}
|
||||
|
||||
async function loadFromIndexedDB(month: number) {
|
||||
if (!await isOfflineDataAvailable()) return null;
|
||||
const recipes = await getBriefRecipesOverlappingMonth(month);
|
||||
return {
|
||||
month,
|
||||
season: rand_array(recipes),
|
||||
isOffline: true as const
|
||||
};
|
||||
}
|
||||
|
||||
export const load: PageLoad = async ({ params, fetch, parent }) => {
|
||||
const parentData = await parent();
|
||||
const month = parseInt(params.month, 10);
|
||||
const apiBase = `/api/${params.recipeLang}`;
|
||||
const useOfflineData =
|
||||
browser && (isOffline() || parentData.isOffline) && canUseOfflineData();
|
||||
const canUseOffline = browser && canUseOfflineData();
|
||||
const knownOffline = browser && (isOffline() || parentData.isOffline);
|
||||
|
||||
if (useOfflineData) {
|
||||
// Skip the network entirely when device is known offline.
|
||||
if (canUseOffline && knownOffline) {
|
||||
try {
|
||||
if (await isOfflineDataAvailable()) {
|
||||
const recipes = await getBriefRecipesOverlappingMonth(month);
|
||||
return {
|
||||
month,
|
||||
season: rand_array(recipes),
|
||||
isOffline: true
|
||||
};
|
||||
}
|
||||
const offline = await loadFromIndexedDB(month);
|
||||
if (offline) return offline;
|
||||
} catch (e) {
|
||||
console.error('Failed to load offline season data:', e);
|
||||
}
|
||||
@@ -50,7 +55,18 @@ export const load: PageLoad = async ({ params, fetch, parent }) => {
|
||||
favorites = body.favorites ?? [];
|
||||
}
|
||||
} catch {
|
||||
// Empty arrays — page will render with no recipes
|
||||
// Network unreachable — IndexedDB fallback below picks up the slack.
|
||||
}
|
||||
|
||||
// API failed or returned empty (502, slow cellular, server hiccup) — fall
|
||||
// back to IndexedDB so the cached PWA shell stays useful.
|
||||
if (canUseOffline && !item_season.length) {
|
||||
try {
|
||||
const offline = await loadFromIndexedDB(month);
|
||||
if (offline) return offline;
|
||||
} catch (e) {
|
||||
console.error('Failed to load offline season data:', e);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user