diff --git a/components/wallet/ui/OfflineBanner.tsx b/components/wallet/ui/OfflineBanner.tsx index 60f3979..462e5ab 100644 --- a/components/wallet/ui/OfflineBanner.tsx +++ b/components/wallet/ui/OfflineBanner.tsx @@ -1,25 +1,35 @@ 'use client'; import useOffline from '@/hooks/useOffline'; -import { WifiOff } from 'lucide-react'; +import { WifiOff, Wifi } from 'lucide-react'; /** * OfflineBanner — Component to display network status fallback. */ const OfflineBanner = () => { - const { isOnline } = useOffline(); + const { isOnline, showBackOnline } = useOffline(); - if (isOnline) return null; + if (isOnline && !showBackOnline) return null; return (
-
- +
+ {showBackOnline ? ( + + ) : ( + + )}

- You are currently offline.{' '} - - Transactions are disabled until connection is restored. - + {showBackOnline ? ( + 'Back Online!' + ) : ( + <> + You are currently offline.{' '} + + Transactions are disabled until connection is restored. + + + )}

diff --git a/hooks/__tests__/useOffline.test.ts b/hooks/__tests__/useOffline.test.ts index c639163..e80018e 100644 --- a/hooks/__tests__/useOffline.test.ts +++ b/hooks/__tests__/useOffline.test.ts @@ -2,6 +2,8 @@ import { renderHook, act } from '@testing-library/react'; import useOffline from '@/hooks/useOffline'; import { networkService } from '@/services/networkService'; +jest.useFakeTimers(); + jest.mock('@/services/networkService', () => ({ networkService: { getIsOnline: jest.fn(), @@ -14,12 +16,17 @@ describe('useOffline', () => { beforeEach(() => { jest.clearAllMocks(); + jest.useFakeTimers(); (networkService.subscribe as jest.Mock).mockImplementation((cb) => { subscribeCallback = cb; return jest.fn(); // Unsubscribe mock }); }); + afterEach(() => { + jest.useRealTimers(); + }); + it('should initialize with the current network status', () => { (networkService.getIsOnline as jest.Mock).mockReturnValue(true); const { result } = renderHook(() => useOffline()); @@ -38,12 +45,21 @@ describe('useOffline', () => { }); expect(result.current.isOnline).toBe(false); + expect(result.current.showBackOnline).toBe(false); act(() => { subscribeCallback(true); }); + expect(result.current.isOnline).toBe(false); + expect(result.current.showBackOnline).toBe(true); + + act(() => { + jest.advanceTimersByTime(2000); + }); + expect(result.current.isOnline).toBe(true); + expect(result.current.showBackOnline).toBe(false); }); it('should cleanup subscription on unmount', () => { diff --git a/hooks/useOffline.tsx b/hooks/useOffline.tsx index 3a1e0b6..22638cc 100644 --- a/hooks/useOffline.tsx +++ b/hooks/useOffline.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { networkService } from '@/services/networkService'; /** @@ -8,20 +8,43 @@ const useOffline = () => { const [isOnline, setIsOnline] = useState( networkService.getIsOnline() ); + const [showBackOnline, setShowBackOnline] = useState(false); + const timeoutRef = useRef(null); useEffect(() => { - // eslint-disable-next-line react-hooks/set-state-in-effect setIsOnline(networkService.getIsOnline()); const unsubscribe = networkService.subscribe((status) => { - setIsOnline(status); + if (status) { + setShowBackOnline(true); + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + setShowBackOnline(false); + setIsOnline(true); + }, 2000); + } else { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + setShowBackOnline(false); + setIsOnline(false); + } }); - return () => unsubscribe(); + return () => { + unsubscribe(); + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; }, []); return { isOnline, + showBackOnline, }; }; diff --git a/jest.config.ts b/jest.config.ts index b33a0fa..d061a79 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -10,7 +10,11 @@ const config: Config = { '^.+\\.(ts|tsx)$': [ 'ts-jest', { - tsconfig: '/tsconfig.jest.json', + tsconfig: { + jsx: 'react-jsx', + moduleResolution: 'node', + ignoreDeprecations: '5.0', + }, }, ], },