9ba2996022
Adds a "Save offline" button on the recipe page that force-refetches the current page so the service worker's cache picks up a fresh copy right now, plus a small IndexedDB-backed list (lib/offline-db.ts) of what's been saved. The /offline fallback page now reads that list and renders it instead of being a dead end with just a "go back" link. Also fixes the service worker's network-first fetch handler, which never wrote successful responses into its cache -- meaning the existing offline page's claim that "recently visited recipes are available" was never actually true. It populates the cache on every successful GET now. Background sync: marking a batch-cook dish as cooked while offline (the only existing mark-cooked call site in the app) now queues the request in IndexedDB instead of just failing, and registers a Background Sync (public/sw.js's "sync" listener replays the queue) for Chromium; lib/offline-queue.ts's online-event fallback covers Safari/Firefox, which never fire that event at all. Both replay paths read/write the same IndexedDB store so either one drains it. Also removes apps/web/public/manifest.json, a stale static manifest that layout.tsx used to link to before the previous commit pointed it at the real generated route (app/manifest.ts) -- it had gone unnoticed and unused since. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
38 lines
1.3 KiB
TypeScript
38 lines
1.3 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import { toast } from "sonner";
|
|
import { flushOfflineQueue } from "@/lib/offline-queue";
|
|
|
|
/** Drains any offline-queued actions (lib/offline-queue.ts) once the browser
|
|
* regains connectivity. Background Sync (public/sw.js) already covers this
|
|
* on Chromium, but Safari/Firefox never fire that API at all, so this is
|
|
* the only path that works there — safe to run alongside it since both
|
|
* read/delete from the same IndexedDB queue and a queue drained by one has
|
|
* nothing left for the other to redo. */
|
|
export function OfflineSyncManager() {
|
|
useEffect(() => {
|
|
async function flush() {
|
|
const synced = await flushOfflineQueue();
|
|
if (synced > 0) toast.success(`Synced ${synced} offline action${synced === 1 ? "" : "s"}`);
|
|
}
|
|
|
|
if (navigator.onLine) void flush();
|
|
window.addEventListener("online", flush);
|
|
|
|
function onSwMessage(e: MessageEvent) {
|
|
if (e.data?.type === "epicure:offline-synced" && e.data.count > 0) {
|
|
toast.success(`Synced ${e.data.count} offline action${e.data.count === 1 ? "" : "s"}`);
|
|
}
|
|
}
|
|
navigator.serviceWorker?.addEventListener("message", onSwMessage);
|
|
|
|
return () => {
|
|
window.removeEventListener("online", flush);
|
|
navigator.serviceWorker?.removeEventListener("message", onSwMessage);
|
|
};
|
|
}, []);
|
|
|
|
return null;
|
|
}
|