diff options
| author | Fuwn <[email protected]> | 2026-02-09 23:13:08 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-09 23:13:08 -0800 |
| commit | 2cb58bde47a2475aa85184e34a1432a997312186 (patch) | |
| tree | ed5a7e4ef4d96da03028294842a5e0e03360242d /apps/web/app/reader | |
| parent | fix: add spacing between collapsible appearance settings sections (diff) | |
| download | asa.news-2cb58bde47a2475aa85184e34a1432a997312186.tar.xz asa.news-2cb58bde47a2475aa85184e34a1432a997312186.zip | |
feat: offline support tier 1 — IndexedDB query persistence and offline banner
Persist React Query cache to IndexedDB via idb-keyval so timeline,
entry details, subscriptions, and other read data survive page reloads
and brief offline periods. Add network status banner in reader layout.
Diffstat (limited to 'apps/web/app/reader')
| -rw-r--r-- | apps/web/app/reader/_components/offline-banner.tsx | 32 | ||||
| -rw-r--r-- | apps/web/app/reader/_components/reader-layout-shell.tsx | 6 |
2 files changed, 37 insertions, 1 deletions
diff --git a/apps/web/app/reader/_components/offline-banner.tsx b/apps/web/app/reader/_components/offline-banner.tsx new file mode 100644 index 0000000..81ebf8f --- /dev/null +++ b/apps/web/app/reader/_components/offline-banner.tsx @@ -0,0 +1,32 @@ +"use client" + +import { useSyncExternalStore } from "react" + +function subscribe(callback: () => void) { + window.addEventListener("online", callback) + window.addEventListener("offline", callback) + return () => { + window.removeEventListener("online", callback) + window.removeEventListener("offline", callback) + } +} + +function getSnapshot() { + return navigator.onLine +} + +function getServerSnapshot() { + return true +} + +export function OfflineBanner() { + const isOnline = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) + + if (isOnline) return null + + return ( + <div className="border-b border-border bg-background-tertiary px-3 py-1.5 text-center text-text-dim"> + you are offline — showing cached content + </div> + ) +} diff --git a/apps/web/app/reader/_components/reader-layout-shell.tsx b/apps/web/app/reader/_components/reader-layout-shell.tsx index 9d2eb24..fe158b5 100644 --- a/apps/web/app/reader/_components/reader-layout-shell.tsx +++ b/apps/web/app/reader/_components/reader-layout-shell.tsx @@ -12,6 +12,7 @@ import { AddFeedDialog } from "./add-feed-dialog" import { SearchOverlay } from "./search-overlay" import { KeyboardShortcutsDialog } from "./keyboard-shortcuts-dialog" import { MfaChallenge } from "./mfa-challenge" +import { OfflineBanner } from "./offline-banner" import { useKeyboardNavigation } from "@/lib/hooks/use-keyboard-navigation" import { createSupabaseBrowserClient } from "@/lib/supabase/client" import { useSubscriptions } from "@/lib/queries/use-subscriptions" @@ -284,7 +285,9 @@ export function ReaderLayoutShell({ } return ( - <div className="flex h-screen"> + <div className="flex h-screen flex-col"> + <OfflineBanner /> + <div className="flex min-h-0 flex-1"> {isMobile ? ( <> <div @@ -405,6 +408,7 @@ export function ReaderLayoutShell({ <SearchOverlay onClose={() => setSearchOpen(false)} /> )} <KeyboardShortcutsDialog /> + </div> </div> ) } |