From 6075d22a848c44597eacef55e553539595b62ef8 Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 17 Jun 2026 14:19:32 -0600 Subject: [PATCH] feat(admin): add OfflineBanner with online status + pending sync count --- src/components/admin/OfflineBanner.tsx | 52 ++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/components/admin/OfflineBanner.tsx diff --git a/src/components/admin/OfflineBanner.tsx b/src/components/admin/OfflineBanner.tsx new file mode 100644 index 0000000..7a229de --- /dev/null +++ b/src/components/admin/OfflineBanner.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { getQueuedActions } from "@/lib/offline/queue"; + +export function OfflineBanner() { + const [online, setOnline] = useState(true); + const [pendingCount, setPendingCount] = useState(0); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + setOnline(navigator.onLine); + const onOnline = () => setOnline(true); + const onOffline = () => setOnline(false); + window.addEventListener("online", onOnline); + window.addEventListener("offline", onOffline); + + const interval = setInterval(async () => { + const actions = await getQueuedActions(); + setPendingCount(actions.filter((a) => a.status === "pending" || a.status === "in-flight").length); + }, 1000); + + return () => { + window.removeEventListener("online", onOnline); + window.removeEventListener("offline", onOffline); + clearInterval(interval); + }; + }, []); + + if (!mounted || (online && pendingCount === 0)) return null; + + return ( +
+ {online ? ( + <>Syncing {pendingCount} pending action{pendingCount === 1 ? "" : "s"}… + ) : ( + <>You're offline. Changes will sync when you reconnect. + )} +
+ ); +} + +export default OfflineBanner;