Skip to content

feat(offline): installable PWA dashboard — service worker, background sync & offline mutations - #1

Merged
micmusjnr20 merged 1 commit into
mainfrom
feat/pwa-offline-dashboard-verified
Aug 29, 2026
Merged

feat(offline): installable PWA dashboard — service worker, background sync & offline mutations#1
micmusjnr20 merged 1 commit into
mainfrom
feat/pwa-offline-dashboard-verified

Conversation

@micmusjnr20

@micmusjnr20 micmusjnr20 commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Closes Betta-Pay#467

Summary

The merchant dashboard was fully network-dependent: no offline shell, no cached assets, and every mutation failed the moment connectivity dropped. This PR turns the dashboard into a resilient, installable PWA:

  • Service worker (Workbox)scripts/sw-template.js is bundled by scripts/build-sw.mjs (rollup + workbox-build, wired into npm run build) into public/sw.js. It precaches the Next.js app shell (JS/CSS/fonts/icons), serves navigations network-first with a cached-shell fallback per route, and runtime-caches GET API list/detail responses stale-while-revalidate (same-origin /api/* and the NEXT_PUBLIC_API_URL origin), so list pages stay populated offline.
  • Offline banner — reuses offlineStore + useOnlineStatus: the banner reflects API reachability (healthz poll), not just navigator.onLine, and also shows how many offline changes are waiting to sync. /healthz is never cached, so reachability is always live.
  • Background sync — payment links created and webhook test events sent while offline are queued in IndexedDB (lib/offline/syncQueue.ts, shared with the SW) and replayed automatically when connectivity returns (native sync + SW online event + client-side reconnect trigger). The payments page shows a pending-sync badge and refetches on SYNC_COMPLETE; the webhook tester marks queued deliveries "Queued (offline)" and flips them to delivered/failed after replay.
  • Installablepublic/manifest.webmanifest + generated icons (scripts/generate-icons.mjs) + components/layout/InstallPrompt.tsx capture beforeinstallprompt so merchants can install the dashboard. The middleware matcher now excludes sw.js, the manifest, and icons so they're never redirected to login.

How it works (walkthrough)

  1. First visit — the SW installs, precaches the shell, and SWR-caches API lists as the merchant browses (dashboard, payments).
  2. Offline reload — the SW serves cached HTML/JS/CSS and answers API GETs from the stale-while-revalidate cache: the dashboard renders with data and the banner shows "You are offline / API server is unreachable".
  3. Offline mutation — creating a payment link while offline queues the POST (toast + "N offline links waiting to sync" badge) instead of failing.
  4. Reconnect — the queued POST is replayed by the SW, the stale list is evicted so the new link appears immediately, and the badge clears.

Verification

npm run verify:offline drives a production build in headless Chromium through the full scenario — 13/13 checks passed on this branch:

✅ Service worker installed and controlling the page
✅ Payment link rendered on first load
✅ App shell precached (451 entries)
✅ GET API list response cached stale-while-revalidate
✅ HTML shell cached per route (offline navigation works)
✅ Browser reports offline after reload
✅ Dashboard renders offline with cached payment link
✅ Offline banner shown (API reachability via healthz)
✅ Offline-created payment link queued with confirmation toast
✅ Pending-sync badge appears in the header
✅ Queued payment-link POST replayed on reconnect
✅ Background-synced link visible in the list
✅ Pending-sync badge cleared after sync
  • npm run build passes (451 precached assets, ~15.7 MiB shell).
  • npm run typecheck: no new errors — remaining errors are pre-existing implicit-any in unrelated test files (lib/status/__tests__/time.test.ts).
  • Jest: new lib/offline tests pass; the only failing suites are the same pre-existing ones that fail on main.
  • Playwright e2e (payments/auth/navigation): failure set is identical to main in this environment — no new regressions.

Notes for reviewers

  • The service worker is registered production-only (components/ui/service-worker-registration.tsx) so dev HMR is unaffected.
  • public/sw.js is build-generated and git-ignored — it embeds the build-time NEXT_PUBLIC_API_URL so the SW matches the same API origin the client uses.
  • GET/HEAD requests are no longer rejected when offline (lib/api/axios.ts); they pass through so the SW can answer from cache. Mutations still fail fast so callers can queue them for background sync.
  • React Query queries use networkMode: 'offlineFirst' so they reach the SW when the browser reports offline.
  • On logout the SW's API cache is cleared (CLEAR_API_CACHE) so a different account never sees stale session/payment data.
  • Also fixed three pre-existing type errors in lib/api/hooks.ts (missing isFetching) that blocked next build.

Test plan

  1. npm run build — passes.
  2. NEXT_PUBLIC_API_URL=http://localhost:3000 npm run build && npm run verify:offline — 13/13 checks.
  3. Manual: install the app (install prompt in the merchant layout), toggle DevTools offline, reload /dashboard — shell + data render with banner; create a payment link offline; reconnect — it syncs and appears in the list.

Supersedes

Same change, re-verified end-to-end on this branch.

…WA install

Adds a Workbox service worker that precaches the app shell and serves GET
API list endpoints stale-while-revalidate, an API-reachability-aware offline
banner, background sync for payment links and webhook tests created while
offline, and a web manifest with icons plus an install prompt for the
merchant dashboard.

Verified end-to-end with `npm run verify:offline` (13/13 checks: the shell
renders offline with cached data and the banner; an offline-created payment
link is replayed on reconnect and appears in the list). `npm run build`
passes; jest shows only pre-existing failures.

Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
@micmusjnr20
micmusjnr20 merged commit 5e57d20 into main Aug 29, 2026
2 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Password reset flow

1 participant