95 lines
3.1 KiB
TypeScript
95 lines
3.1 KiB
TypeScript
|
|
import React, { useState, useEffect } from 'react';
|
|||
|
|
import { Wifi, WifiOff } from 'lucide-react';
|
|||
|
|
import { connectionService, type ConnectionStatus } from '../services/connectionService';
|
|||
|
|
|
|||
|
|
export const ConnectionIndicator: React.FC = () => {
|
|||
|
|
const [status, setStatus] = useState<ConnectionStatus>(connectionService.getStatus());
|
|||
|
|
const [apiBaseUrl] = useState<string>(import.meta.env.VITE_API_BASE_URL || '');
|
|||
|
|
const [showIndicator, setShowIndicator] = useState(false);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
const unsubscribe = connectionService.subscribe((newStatus) => {
|
|||
|
|
setStatus(newStatus);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Не показываем индикатор сразу при загрузке, даём время на проверку подключения
|
|||
|
|
const timer = setTimeout(() => {
|
|||
|
|
setShowIndicator(true);
|
|||
|
|
}, 2000); // 2 секунды задержка
|
|||
|
|
|
|||
|
|
return () => {
|
|||
|
|
unsubscribe();
|
|||
|
|
clearTimeout(timer);
|
|||
|
|
};
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
// Если API не настроен, не показываем индикатор (используется только localStorage)
|
|||
|
|
if (!apiBaseUrl) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Не показываем индикатор сразу при загрузке
|
|||
|
|
if (!showIndicator) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// На ПК показываем индикатор только если есть проблема с подключением
|
|||
|
|
// На мобильных показываем всегда для информативности
|
|||
|
|
const isDesktop = typeof window !== 'undefined' && window.innerWidth >= 768;
|
|||
|
|
if (isDesktop && status === 'connected') {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const getStatusColor = () => {
|
|||
|
|
switch (status) {
|
|||
|
|
case 'connected':
|
|||
|
|
return 'text-emerald-500';
|
|||
|
|
case 'connecting':
|
|||
|
|
return 'text-amber-500';
|
|||
|
|
case 'disconnected':
|
|||
|
|
return 'text-red-500';
|
|||
|
|
default:
|
|||
|
|
return 'text-slate-400';
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const getStatusBg = () => {
|
|||
|
|
switch (status) {
|
|||
|
|
case 'connected':
|
|||
|
|
return 'bg-emerald-50';
|
|||
|
|
case 'connecting':
|
|||
|
|
return 'bg-amber-50';
|
|||
|
|
case 'disconnected':
|
|||
|
|
return 'bg-red-50';
|
|||
|
|
default:
|
|||
|
|
return 'bg-slate-50';
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const getStatusText = () => {
|
|||
|
|
switch (status) {
|
|||
|
|
case 'connected':
|
|||
|
|
return 'Подключено';
|
|||
|
|
case 'connecting':
|
|||
|
|
return 'Подключение...';
|
|||
|
|
case 'disconnected':
|
|||
|
|
return 'Нет подключения';
|
|||
|
|
default:
|
|||
|
|
return 'Неизвестно';
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className={`flex items-center gap-2 px-3 py-1.5 rounded-lg border ${getStatusBg()} ${status === 'disconnected' ? 'border-red-200' : status === 'connecting' ? 'border-amber-200' : 'border-emerald-200'} transition-all ${status === 'disconnected' ? 'shadow-sm shadow-red-500/10' : ''}`}>
|
|||
|
|
{status === 'disconnected' ? (
|
|||
|
|
<WifiOff className={`w-4 h-4 ${getStatusColor()}`} />
|
|||
|
|
) : (
|
|||
|
|
<Wifi className={`w-4 h-4 ${getStatusColor()} ${status === 'connecting' ? 'animate-pulse' : ''}`} />
|
|||
|
|
)}
|
|||
|
|
<span className={`text-xs font-bold ${getStatusColor()} hidden sm:inline`}>
|
|||
|
|
{getStatusText()}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|