Replace legacy text-kodo-cyan/border-kodo-cyan/bg-kodo-cyan with semantic text-primary/border-primary/bg-primary across 51 components. The brand primary color now uses the design system token, enabling proper theme adaptation. Covers UI primitives, search, dashboard, chat, playlists, settings, social, marketplace, and auth components. Co-authored-by: Cursor <cursoragent@cursor.com>
190 lines
8.1 KiB
TypeScript
190 lines
8.1 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { useOnlineStatus } from '@/hooks/useOnlineStatus';
|
|
import { offlineQueue } from '@/services/offlineQueue';
|
|
import { WifiOff, Loader2, List } from 'lucide-react';
|
|
import { hasRecentNetworkError } from '@/utils/networkErrorTracker';
|
|
import { OfflineQueueManager } from './OfflineQueueManager';
|
|
|
|
/**
|
|
* Composant pour afficher un indicateur de mode hors ligne avec nombre de requêtes en attente
|
|
*/
|
|
export function OfflineIndicator() {
|
|
const isOnline = useOnlineStatus();
|
|
const [queueSize, setQueueSize] = useState(0);
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
const [hasNetworkError, setHasNetworkError] = useState(false);
|
|
const [showQueueManager, setShowQueueManager] = useState(false);
|
|
const [shouldShowSyncBar, setShouldShowSyncBar] = useState(false);
|
|
|
|
// Mettre à jour la taille de la file d'attente
|
|
useEffect(() => {
|
|
const updateQueueSize = () => {
|
|
const size = offlineQueue.getQueueSize();
|
|
// #region agent log
|
|
// fetch('http://127.0.0.1:7242/ingest/09c5ea5e-2380-4cc3-92aa-d26f3b3d26f6',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OfflineIndicator.tsx:updateQueueSize',message:'Queue size updated',data:{queueSize:size,isOnline},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{});
|
|
// #endregion
|
|
setQueueSize(size);
|
|
};
|
|
|
|
// Mettre à jour immédiatement
|
|
updateQueueSize();
|
|
|
|
// Mettre à jour toutes les secondes
|
|
const interval = setInterval(updateQueueSize, 1000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
// Vérifier si la file est en cours de traitement
|
|
useEffect(() => {
|
|
if (isOnline && queueSize > 0) {
|
|
setIsProcessing(true);
|
|
// Vérifier périodiquement si le traitement est terminé
|
|
const checkProcessing = setInterval(() => {
|
|
const currentSize = offlineQueue.getQueueSize();
|
|
if (currentSize === 0) {
|
|
setIsProcessing(false);
|
|
clearInterval(checkProcessing);
|
|
}
|
|
}, 500);
|
|
return () => clearInterval(checkProcessing);
|
|
} else {
|
|
setIsProcessing(false);
|
|
return undefined;
|
|
}
|
|
}, [isOnline, queueSize]);
|
|
|
|
// Check for recent network errors
|
|
useEffect(() => {
|
|
const checkNetworkError = () => {
|
|
setHasNetworkError(hasRecentNetworkError());
|
|
};
|
|
|
|
// Check immediately
|
|
checkNetworkError();
|
|
|
|
// Check periodically (every 2 seconds) to update when error expires
|
|
const interval = setInterval(checkNetworkError, 2000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
// FIX: Délai avant d'afficher la barre de synchronisation pour éviter les flashs
|
|
useEffect(() => {
|
|
// #region agent log
|
|
// fetch('http://127.0.0.1:7242/ingest/09c5ea5e-2380-4cc3-92aa-d26f3b3d26f6',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OfflineIndicator.tsx:useEffect',message:'Sync bar effect triggered',data:{isProcessing,queueSize,isOnline,shouldShowSyncBar},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
|
// #endregion
|
|
if (isProcessing && queueSize > 0 && isOnline) {
|
|
const timer = setTimeout(() => {
|
|
// #region agent log
|
|
// fetch('http://127.0.0.1:7242/ingest/09c5ea5e-2380-4cc3-92aa-d26f3b3d26f6',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OfflineIndicator.tsx:setTimeout',message:'Setting shouldShowSyncBar to true',data:{queueSize,isProcessing},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
|
// #endregion
|
|
setShouldShowSyncBar(true);
|
|
}, 500);
|
|
return () => {
|
|
clearTimeout(timer);
|
|
setShouldShowSyncBar(false);
|
|
};
|
|
} else {
|
|
setShouldShowSyncBar(false);
|
|
return undefined;
|
|
}
|
|
}, [isProcessing, queueSize, isOnline]);
|
|
|
|
// Ne rien afficher si en ligne, aucune requête en attente, et pas d'erreur réseau récente
|
|
if (isOnline && queueSize === 0 && !isProcessing && !hasNetworkError) {
|
|
return null;
|
|
}
|
|
|
|
// Mode hors ligne ou erreur réseau récente
|
|
if (!isOnline || hasNetworkError) {
|
|
return (
|
|
<>
|
|
<div className="fixed top-0 left-0 right-0 bg-kodo-red/90 backdrop-blur-sm text-white px-4 py-2.5 text-sm z-50 flex items-center justify-center gap-2 shadow-lg border-b border-kodo-red">
|
|
<WifiOff className="w-4 h-4" />
|
|
<span>
|
|
Mode hors ligne
|
|
{queueSize > 0 && (
|
|
<span className="ml-2 font-semibold">
|
|
- {queueSize} {queueSize === 1 ? 'requête' : 'requêtes'} en
|
|
attente
|
|
</span>
|
|
)}
|
|
</span>
|
|
{queueSize > 0 && (
|
|
<button
|
|
onClick={() => setShowQueueManager(true)}
|
|
className="ml-3 px-2 py-1 bg-white/10 hover:bg-white/20 rounded border border-white/20 transition-colors flex items-center gap-1.5 text-xs font-medium"
|
|
title="View queued requests"
|
|
>
|
|
<List className="w-3.5 h-3.5" />
|
|
View Queue
|
|
</button>
|
|
)}
|
|
</div>
|
|
<OfflineQueueManager
|
|
open={showQueueManager}
|
|
onClose={() => setShowQueueManager(false)}
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
|
|
// En ligne mais traitement de la file en cours
|
|
// FIX: Ne pas afficher la barre si les requêtes sont rapidement traitées (moins de 500ms)
|
|
// Cela évite d'afficher la barre pour des requêtes qui sont déjà en cours de traitement
|
|
// #region agent log
|
|
if (isProcessing && queueSize > 0) {
|
|
// fetch('http://127.0.0.1:7242/ingest/09c5ea5e-2380-4cc3-92aa-d26f3b3d26f6',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OfflineIndicator.tsx:123',message:'Checking sync bar display',data:{isProcessing,queueSize,shouldShowSyncBar,willShow:shouldShowSyncBar},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'B'})}).catch(()=>{});
|
|
}
|
|
// #endregion
|
|
if (isProcessing && queueSize > 0 && shouldShowSyncBar) {
|
|
return (
|
|
<>
|
|
<div className="fixed top-0 left-0 right-0 bg-primary/90 backdrop-blur-sm text-kodo-void px-4 py-2.5 text-sm z-50 flex items-center justify-center gap-2 shadow-lg border-b border-border">
|
|
<Loader2 className="w-4 h-4 animate-spin" />
|
|
<span>
|
|
Synchronisation en cours
|
|
{queueSize > 0 && (
|
|
<span className="ml-2 font-semibold">
|
|
- {queueSize} {queueSize === 1 ? 'requête' : 'requêtes'} restante
|
|
{queueSize > 1 ? 's' : ''}
|
|
</span>
|
|
)}
|
|
</span>
|
|
{queueSize > 0 && (
|
|
<>
|
|
<button
|
|
onClick={async () => {
|
|
// #region agent log
|
|
// fetch('http://127.0.0.1:7242/ingest/09c5ea5e-2380-4cc3-92aa-d26f3b3d26f6',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'OfflineIndicator.tsx:clearQueue',message:'User clicked clear queue',data:{queueSize},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'C'})}).catch(()=>{});
|
|
// #endregion
|
|
await offlineQueue.clearQueue();
|
|
setQueueSize(0);
|
|
}}
|
|
className="ml-2 px-2 py-1 bg-kodo-red/20 hover:bg-kodo-red/30 rounded border border-kodo-red/30 transition-colors flex items-center gap-1.5 text-xs font-medium"
|
|
title="Clear queued requests"
|
|
>
|
|
Clear Queue
|
|
</button>
|
|
<button
|
|
onClick={() => setShowQueueManager(true)}
|
|
className="ml-2 px-2 py-1 bg-kodo-void/20 hover:bg-kodo-void/30 rounded border border-kodo-void/30 transition-colors flex items-center gap-1.5 text-xs font-medium"
|
|
title="View queued requests"
|
|
>
|
|
<List className="w-3.5 h-3.5" />
|
|
View Queue
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
<OfflineQueueManager
|
|
open={showQueueManager}
|
|
onClose={() => setShowQueueManager(false)}
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|