tahamajs/IE / Projects /front /src /components /layout /SystemStatusBar.tsx
tahamajs's picture
download
raw
9.03 kB
// src/components/SystemStatusBar.tsx
import { useEffect, useMemo, useState } from "react";
import {
Cloud, CloudOff, Loader2, Radio, ShieldCheck, Wifi, WifiOff,
CheckCircle, AlertCircle, RefreshCw, Zap, Battery, Activity as Signal,
Activity, Database, Server, Globe, Lock, Eye, EyeOff
} from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
import { useAuth } from "@/context/AuthContext";
import { useOffline } from "@/context/OfflineContext";
import { useWebSocket } from "@/context/WebSocketContext";
function getBrowserOnline() {
return typeof navigator === "undefined" ? true : navigator.onLine;
}
interface SystemStatusBarProps {
showDetails?: boolean;
compact?: boolean;
onStatusClick?: () => void;
}
export function SystemStatusBar({ showDetails = true, compact = false, onStatusClick }: SystemStatusBarProps) {
const { isOfflineMode } = useAuth();
const { isOnline, queueLength, processQueue, syncProgress, lastSyncTime } = useOffline();
const { connectionStatus, reconnect, pingLatency } = useWebSocket();
const [browserOnline, setBrowserOnline] = useState(getBrowserOnline);
const [showTooltip, setShowTooltip] = useState(false);
useEffect(() => {
const update = () => setBrowserOnline(getBrowserOnline());
update();
window.addEventListener("online", update);
window.addEventListener("offline", update);
return () => {
window.removeEventListener("online", update);
window.removeEventListener("offline", update);
};
}, []);
const status = useMemo(() => {
if (isOfflineMode) {
return {
label: "Offline session",
detail: queueLength > 0 ? `${queueLength} queued actions` : "Saved login session",
icon: WifiOff,
color: "amber",
actions: queueLength > 0 ? `${queueLength} pending` : null,
};
}
if (!browserOnline || !isOnline) {
return {
label: "No connection",
detail: queueLength > 0 ? `${queueLength} queued actions` : "Local mode active",
icon: WifiOff,
color: "red",
actions: queueLength > 0 ? "Retry sync" : null,
};
}
if (connectionStatus === "connecting") {
return {
label: "Connecting",
detail: queueLength > 0 ? `${queueLength} queued actions` : "Establishing secure connection",
icon: Loader2,
color: "amber",
actions: "Connecting...",
};
}
if (connectionStatus === "error" || connectionStatus === "disconnected") {
return {
label: "Live updates off",
detail: "Real-time chat unavailable. REST API still works.",
icon: CloudOff,
color: "amber",
actions: "Reconnect",
};
}
if (syncProgress && syncProgress < 100) {
return {
label: "Syncing",
detail: `${Math.round(syncProgress)}% complete`,
icon: RefreshCw,
color: "blue",
actions: `${Math.round(syncProgress)}%`,
};
}
return {
label: "Online",
detail: queueLength > 0 ? `Syncing ${queueLength} items` : "All systems operational",
icon: Wifi,
color: "green",
actions: queueLength > 0 ? `${queueLength} pending` : null,
};
}, [browserOnline, connectionStatus, isOfflineMode, isOnline, queueLength, syncProgress]);
const getColorClasses = () => {
const colors = {
green: "premium-glass border-emerald-400/30 bg-emerald-100/40 text-emerald-800 dark:border-emerald-500/20 dark:bg-emerald-950/40 dark:text-emerald-300",
amber: "premium-glass border-amber-400/30 bg-amber-100/40 text-amber-800 dark:border-amber-500/20 dark:bg-amber-950/40 dark:text-amber-300",
red: "premium-glass border-red-400/30 bg-red-100/40 text-red-800 dark:border-red-500/20 dark:bg-red-950/40 dark:text-red-300",
blue: "premium-glass border-blue-400/30 bg-blue-100/40 text-blue-800 dark:border-blue-500/20 dark:bg-blue-950/40 dark:text-blue-300",
};
return colors[status.color as keyof typeof colors] || colors.green;
};
const StatusIcon = status.icon;
const canRetry = browserOnline && isOnline && (connectionStatus === "disconnected" || connectionStatus === "error" || queueLength > 0);
const handleRetry = () => {
if (connectionStatus === "disconnected" || connectionStatus === "error") reconnect();
if (queueLength > 0) void processQueue();
};
if (compact) {
return (
<div
className={`relative flex items-center gap-1.5 rounded-full px-2 py-1 text-xs transition-all cursor-pointer hover:scale-105 ${getColorClasses()}`}
onClick={onStatusClick || handleRetry}
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
>
<StatusIcon className={`h-3 w-3 ${connectionStatus === "connecting" ? "animate-spin" : ""}`} />
<span className="hidden sm:inline">
{status.label} {pingLatency !== null && `(${pingLatency}ms)`}
</span>
{showTooltip && (
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-2 py-1 bg-gray-900 text-white text-xs rounded whitespace-nowrap z-50 shadow-lg">
{status.detail} {pingLatency !== null && `| Latency: ${pingLatency}ms`}
</div>
)}
</div>
);
}
if (status.label === "Online" && !syncProgress) {
return null; // Hide normal status bar if fully connected
}
return (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className={`flex min-w-0 items-center gap-2 rounded-lg border px-3 py-2 text-xs shadow-sm transition-all cursor-pointer hover:shadow-md ${getColorClasses()}`}
onClick={onStatusClick || handleRetry}
role="status"
aria-live="polite"
>
<StatusIcon className={`h-4 w-4 shrink-0 ${connectionStatus === "connecting" || syncProgress ? "animate-spin" : ""}`} />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 font-medium leading-none">
<span>{status.label}</span>
<span className="text-current/30">/</span>
<span className="truncate text-current/70">{status.detail}</span>
</div>
{showDetails && (
<div className="flex items-center gap-3 mt-1 text-[10px] text-current/60">
{pingLatency && (
<span className="flex items-center gap-0.5">
<Zap className="h-2.5 w-2.5" /> {pingLatency}ms
</span>
)}
{lastSyncTime && (
<span className="flex items-center gap-0.5">
<Database className="h-2.5 w-2.5" /> Last sync: {new Date(lastSyncTime).toLocaleTimeString()}
</span>
)}
{connectionStatus === "connected" && (
<span className="flex items-center gap-0.5">
<ShieldCheck className="h-2.5 w-2.5" /> Secure connection
</span>
)}
</div>
)}
</div>
{/* Status Icons */}
<div className="hidden items-center gap-1.5 text-current/60 lg:flex">
{isOfflineMode ? (
<Lock className="h-3.5 w-3.5" />
) : connectionStatus === "connected" ? (
<>
<Radio className="h-3.5 w-3.5" />
<Cloud className="h-3.5 w-3.5" />
<ShieldCheck className="h-3.5 w-3.5" />
{queueLength === 0 && <CheckCircle className="h-3.5 w-3.5 text-green-500" />}
</>
) : (
<AlertCircle className="h-3.5 w-3.5" />
)}
</div>
{/* Action Button */}
{canRetry && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
handleRetry();
}}
className="hidden rounded-md px-2 py-0.5 text-xs font-medium text-current/80 hover:bg-black/5 dark:hover:bg-white/10 md:inline-flex items-center gap-1"
>
<RefreshCw className="h-3 w-3" /> Retry
</button>
)}
{/* Progress Indicator */}
{syncProgress && syncProgress < 100 && (
<div className="w-16 h-1 bg-current/20 rounded-full overflow-hidden">
<motion.div
className="h-full bg-current rounded-full"
initial={{ width: 0 }}
animate={{ width: `${syncProgress}%` }}
transition={{ duration: 0.3 }}
/>
</div>
)}
</motion.div>
);
}
// Small version for mobile
export function MobileStatusIndicator() {
const { connectionStatus } = useWebSocket();
const { isOnline } = useOffline();
const isConnected = connectionStatus === "connected" && isOnline;
return (
<div className="flex items-center gap-1">
<div className={`h-2 w-2 rounded-full ${isConnected ? "bg-green-500 animate-pulse" : "bg-red-500"}`} />
<span className="text-xs text-gray-500">
{isConnected ? "Live" : "Offline"}
</span>
</div>
);
}
// Export all
export default SystemStatusBar;

Xet Storage Details

Size:
9.03 kB
·
Xet hash:
21e05379e636323f347a788381ceace5e27739d78a68449316d88f0815673309

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.