diff --git a/src/App.tsx b/src/App.tsx index 3c382de..8d4e573 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { Header } from './components/Header'; import { Sidebar } from './components/Sidebar'; import { Chat } from './components/Chat'; import { LoginScreen } from './components/LoginScreen'; +import { ConnectionBanner } from './components/ConnectionBanner'; export default function App() { const { @@ -38,6 +39,7 @@ export default function App() { />
setSidebarOpen(!sidebarOpen)} activeSessionData={sessions.find(s => s.key === activeSession)} onLogout={logout} /> +
diff --git a/src/components/ConnectionBanner.tsx b/src/components/ConnectionBanner.tsx new file mode 100644 index 0000000..34f6a09 --- /dev/null +++ b/src/components/ConnectionBanner.tsx @@ -0,0 +1,72 @@ +import { useState, useEffect, useRef } from 'react'; +import { Wifi, Loader2 } from 'lucide-react'; +import type { ConnectionStatus } from '../types'; +import { useT } from '../hooks/useLocale'; + +interface Props { + status: ConnectionStatus; +} + +type BannerState = 'hidden' | 'reconnecting' | 'reconnected'; + +export function ConnectionBanner({ status }: Props) { + const t = useT(); + const [banner, setBanner] = useState('hidden'); + const prevStatus = useRef(status); + const dismissTimer = useRef | null>(null); + + useEffect(() => { + const prev = prevStatus.current; + prevStatus.current = status; + + if (dismissTimer.current) { + clearTimeout(dismissTimer.current); + dismissTimer.current = null; + } + + if (status === 'disconnected' || status === 'connecting') { + // Only show reconnecting if we were previously connected (not initial load) + if (prev === 'connected') { + setBanner('reconnecting'); + } + } else if (status === 'connected' && prev !== 'connected') { + // Just reconnected — flash success only if we were showing the banner + if (banner === 'reconnecting' || prev === 'disconnected' || prev === 'connecting') { + setBanner('reconnected'); + dismissTimer.current = setTimeout(() => setBanner('hidden'), 3000); + } + } + + return () => { + if (dismissTimer.current) clearTimeout(dismissTimer.current); + }; + }, [status]); + + if (banner === 'hidden') return null; + + const isReconnecting = banner === 'reconnecting'; + + return ( +
+ {isReconnecting ? ( + <> + + {t('connection.reconnecting')} + + ) : ( + <> + + {t('connection.reconnected')} + + )} +
+ ); +} diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts index e5bd6e9..495e9cf 100644 --- a/src/lib/i18n.ts +++ b/src/lib/i18n.ts @@ -49,6 +49,10 @@ const en = { // Tool call 'tool.result': 'Result', + // Connection banner + 'connection.reconnecting': 'Connection lost — reconnecting…', + 'connection.reconnected': 'Reconnected!', + // Timestamps 'time.yesterday': 'Yesterday', } as const; @@ -89,6 +93,9 @@ const fr: Record = { 'tool.result': 'Résultat', + 'connection.reconnecting': 'Connexion perdue — reconnexion…', + 'connection.reconnected': 'Reconnecté !', + 'time.yesterday': 'Hier', };