From 6a9b71bb20e19715e1b772271bfc7184977392dc Mon Sep 17 00:00:00 2001 From: luna Date: Sat, 4 Jan 2025 16:12:37 +1030 Subject: [PATCH] feat: offline banner --- src/components/FeedSelector.tsx | 5 +++- src/components/ui/alert.tsx | 38 ++++++++++++++++++++++++++++ src/components/ui/offline-banner.tsx | 19 ++++++++++++++ src/hooks/useOfflineStatus.ts | 20 +++++++++++++++ src/routes/__root.tsx | 2 ++ 5 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/offline-banner.tsx create mode 100644 src/hooks/useOfflineStatus.ts diff --git a/src/components/FeedSelector.tsx b/src/components/FeedSelector.tsx index e32fdc3..61cfa88 100644 --- a/src/components/FeedSelector.tsx +++ b/src/components/FeedSelector.tsx @@ -8,8 +8,11 @@ import { Loading } from './ui/loading'; import { TabList } from './ui/tab-list'; import { Tab } from './ui/tab'; import { useQueryClient } from '@tanstack/react-query'; +import { useOfflineStatus } from '@/hooks/useOfflineStatus'; +import { cn } from '@/lib/utils'; export const FeedSelector = ({ columnNumber = 1 }: { columnNumber: number }) => { + const isOffline = useOfflineStatus(); const queryClient = useQueryClient(); const { setSettings, columns } = useSettings(); const { isAuthenticated } = useAuth(); @@ -66,7 +69,7 @@ export const FeedSelector = ({ columnNumber = 1 }: { columnNumber: number }) => {/* if there are less than 2 feeds, don't show the selector */} {feeds.length >= 2 && ( <> - + {data?.map((feed) => { return ( svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7', + { + variants: { + variant: { + default: 'bg-background text-foreground', + destructive: 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +); + +export const Alert = forwardRef & VariantProps>( + ({ className, variant, ...props }, ref) => ( +
+ ), +); +Alert.displayName = 'Alert'; + +export const AlertTitle = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +AlertTitle.displayName = 'AlertTitle'; + +export const AlertDescription = forwardRef>( + ({ className, ...props }, ref) =>
, +); +AlertDescription.displayName = 'AlertDescription'; diff --git a/src/components/ui/offline-banner.tsx b/src/components/ui/offline-banner.tsx new file mode 100644 index 0000000..cb1788c --- /dev/null +++ b/src/components/ui/offline-banner.tsx @@ -0,0 +1,19 @@ +import { WifiOff } from 'lucide-react'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { useOfflineStatus } from '@/hooks/useOfflineStatus'; + +export const OfflineBanner = () => { + const isOffline = useOfflineStatus(); + + if (!isOffline) return null; + + return ( + + + {"You're currently offline. Some features may be unavailable."} + + ); +}; diff --git a/src/hooks/useOfflineStatus.ts b/src/hooks/useOfflineStatus.ts new file mode 100644 index 0000000..cfea777 --- /dev/null +++ b/src/hooks/useOfflineStatus.ts @@ -0,0 +1,20 @@ +import { useState, useEffect } from 'react'; + +export const useOfflineStatus = () => { + const [isOffline, setIsOffline] = useState(!navigator.onLine); + + useEffect(() => { + const handleOnline = () => setIsOffline(false); + const handleOffline = () => setIsOffline(true); + + window.addEventListener('online', handleOnline); + window.addEventListener('offline', handleOffline); + + return () => { + window.removeEventListener('online', handleOnline); + window.removeEventListener('offline', handleOffline); + }; + }, []); + + return isOffline; +}; diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index d755918..2bd397f 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -12,6 +12,7 @@ import { Toaster } from '@/components/ui/sonner'; import { Helmet } from 'react-helmet'; import { appName } from '@/config'; import { useUnreadCount } from '@/lib/bluesky/hooks/useUnreadCount'; +import { OfflineBanner } from '@/components/ui/offline-banner'; export const Route = createRootRoute({ component: Root, @@ -50,6 +51,7 @@ function Root() { return ( <> +