Skip to content

feat(fe-027): Add fallback behavior for offline mode when API base URL unreachable#1829

Open
rindicomfort wants to merge 2 commits into
EarnQuestOne:mainfrom
rindicomfort:feat/fe-027-offline-fallback-api-unreachable
Open

feat(fe-027): Add fallback behavior for offline mode when API base URL unreachable#1829
rindicomfort wants to merge 2 commits into
EarnQuestOne:mainfrom
rindicomfort:feat/fe-027-offline-fallback-api-unreachable

Conversation

@rindicomfort

Copy link
Copy Markdown

Summary

Closes #816

This PR implements [FE-027] — proper fallback behaviour when the application is offline or the API base URL is unreachable, replacing the previous no-op state with a fully wired, store-backed solution.


Problem

Previously the app had no cohesive handling for the two distinct connectivity failure modes:

  1. Device offlinenavigator.onLine === false
  2. Device online but API unreachable — network request fails with ERR_NETWORK / timeout

The existing useNetworkStatus hook tracked these states locally but never synced them to the global store, so no UI could react to them. The OfflineFallback component existed but was never mounted anywhere.


Changes

lib/store/slices/uiSlice.ts

  • Added isApiReachable: boolean field and setApiReachable() action to the Zustand store so any component tree can subscribe to API reachability without prop drilling.

lib/hooks/useNetworkStatus.ts (rewritten)

  • Syncs both isOnline and isApiReachable into the global store.
  • Listens to the network-unreachable custom event already dispatched by the Axios client interceptor.
  • Runs a periodic /health probe (every 30 s) when online; fires immediately on reconnect.
  • Skips probing entirely when offline to avoid noise.
  • Exposes recheckApi() for on-demand probing (used by retry buttons).

components/error/ApiUnreachableFallback.tsx (new)

  • Full-page fallback shown when the device has internet but the API is unreachable.
  • Distinct from OfflineFallback — different icon, copy, and user guidance.
  • Accessible: role="status", aria-live="polite", spinner state on retry button.

components/error/OfflineFallback.tsx

  • Added role="status" and aria-live="polite" for screen-reader compatibility.
  • Added aria-hidden on decorative icons.

components/providers/OfflineModeProvider.tsx (new)

  • Mounts useNetworkStatus once at the provider level.
  • Gates children: renders OfflineFallback when offline, ApiUnreachableFallback when online-but-unreachable, children otherwise.
  • Accepts an enabled prop (defaults true) to opt out in tests/Storybook.

app/providers/RootProviders.tsx

  • Wraps <A11yAnnouncerProvider> (and therefore all page content) with <OfflineModeProvider>.

components/error/index.ts

  • Exports OfflineFallback and ApiUnreachableFallback.

Tests

  • lib/hooks/useNetworkStatus.test.ts — covers initial sync, online/offline events, API probe success/failure, network-unreachable event, skipping probe when offline, and recheckApi().
  • components/error/ApiUnreachableFallback.test.tsx — rendering, retry callback, checking state, accessibility attributes.
  • components/providers/OfflineModeProvider.test.tsx — all three render paths (children / offline fallback / API fallback), retry wiring, enabled=false bypass.

Acceptance criteria

Criterion Status
Offline device shows OfflineFallback
Online but API unreachable shows ApiUnreachableFallback
Both reachable renders app normally
Retry button triggers re-probe
isApiReachable persisted in global store
Axios network-unreachable event consumed
Accessibility (roles, aria-live, aria-hidden)
Unit tests added
No regression to existing functionality
Code follows project conventions (Zustand slices, hooks, providers)

- Add isApiReachable field to uiSlice (Zustand store)
- Rewrite useNetworkStatus to sync both isOnline and isApiReachable
  into the global store, listen to network-unreachable custom events
  from Axios, and expose recheckApi() for on-demand probing
- Add ApiUnreachableFallback component shown when device is online
  but the backend API cannot be reached (distinct from OfflineFallback)
- Add OfflineModeProvider that wraps children and gates them behind
  connectivity checks, rendering the correct fallback automatically
- Wire OfflineModeProvider into RootProviders
- Update OfflineFallback with aria-live and aria-hidden attributes
- Export ApiUnreachableFallback from components/error/index.ts
- Add unit tests for useNetworkStatus, ApiUnreachableFallback,
  and OfflineModeProvider

Closes EarnQuestOne#816
@drips-wave

drips-wave Bot commented Jun 28, 2026

Copy link
Copy Markdown

@rindicomfort Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@RUKAYAT-CODER

Copy link
Copy Markdown
Contributor

Great job so far

There’s just one blocker — the workflow is failing. Could you take a look and fix it so all checks pass?
You could pull from the main to get the changes before pushing.

@rindicomfort

Copy link
Copy Markdown
Author

okay @RUKAYAT-CODER

- Remove self-referential ReturnType in buildStoreMock (caused TS2456
  'overrides is referenced in its own type annotation')
- Use explicit StoreMockState interface instead
- Use stable mock state object in tests to avoid infinite re-renders
  caused by new function references on every useStore selector call
- Make probeApi useCallback stable (empty deps) via setApiReachableRef
  to break the setInterval -> setApiReachable -> re-render -> effect
  infinite loop reported in CI (10000 timer limit hit)
@rindicomfort

Copy link
Copy Markdown
Author

done @RUKAYAT-CODER
kindly review and merge

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.

[FE-027] Add fallback behavior for offline mode when API base URL unreachable

2 participants