File size: 7,332 Bytes
6a7089a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import { useCallback, useEffect, useRef, useState } from "react";
import { NavLink, useLocation, useNavigate } from "react-router-dom";
import { useAppStore } from "../../stores/useAppStore";
import { clearStoredAuthToken, getStoredAuthToken } from "../../services/auth";
import "./NavBar.css";

interface Tab {
  id: string;
  path: string;
  label: string;
}

const tabs: Tab[] = [
  { id: "monitoring", path: "/dashboard/monitoring", label: "Monitoring" },
  { id: "profiles", path: "/dashboard/profiles", label: "Profiles" },
  { id: "settings", path: "/dashboard/settings", label: "Settings" },
];

interface NavBarProps {
  onRefresh?: () => void;
}

export default function NavBar({ onRefresh }: NavBarProps) {
  const { serverInfo } = useAppStore();
  const [refreshing, setRefreshing] = useState(false);
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
  const tabsRef = useRef<HTMLElement>(null);
  const location = useLocation();
  const navigate = useNavigate();
  const hasStoredToken = getStoredAuthToken() !== "";

  // Close mobile menu on route change
  useEffect(() => {
    setMobileMenuOpen(false);
  }, [location]);

  const handleRefresh = useCallback(() => {
    if (!onRefresh || refreshing) return;
    setRefreshing(true);
    onRefresh();
    setTimeout(() => setRefreshing(false), 800);
  }, [onRefresh, refreshing]);

  const handleLogout = useCallback(() => {
    clearStoredAuthToken();
    setMobileMenuOpen(false);
    navigate("/login", { replace: true });
  }, [navigate]);

  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      if (!e.metaKey && !e.ctrlKey) return;
      const num = parseInt(e.key);
      if (num >= 1 && num <= tabs.length) {
        e.preventDefault();
        navigate(tabs[num - 1].path);
        return;
      }
      if (e.key === "r" && onRefresh) {
        e.preventDefault();
        handleRefresh();
      }
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [navigate, onRefresh, handleRefresh]);

  return (
    <header className="sticky top-0 z-50 border-b border-border-subtle bg-bg-app/95 backdrop-blur">
      <div className="flex h-[60px] items-center gap-0 px-4 sm:px-5">
        <span className="min-w-32 text-sm font-semibold tracking-[0.2em] text-text-primary uppercase">
          PinchTab
        </span>

        {/* Desktop nav */}
        <nav className="ml-6 hidden items-center gap-0.5 sm:flex" ref={tabsRef}>
          {tabs.map((tab, i) => (
            <NavLink
              key={tab.id}
              to={tab.path}
              className={({ isActive }) =>
                `navbar-tab relative cursor-pointer rounded-sm border border-transparent bg-transparent px-3.5 py-2.5 text-sm font-medium leading-none whitespace-nowrap transition-all duration-150 hover:border-border-subtle hover:bg-bg-hover/70 hover:text-text-primary focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 ${
                  isActive
                    ? "active border-primary/20 bg-primary/10 text-text-primary"
                    : "text-text-secondary"
                }`
              }
              title={`${tab.label} (⌘${i + 1})`}
            >
              {tab.label}
            </NavLink>
          ))}
        </nav>

        <div className="ml-auto flex items-center gap-1.5">
          {serverInfo && (
            <div
              className={`mr-2 flex items-center gap-1.5 rounded-full px-2.5 py-1 ${
                serverInfo.restartRequired
                  ? "border border-warning/25 bg-warning/10"
                  : "border border-success/20 bg-success/10"
              }`}
              title={
                serverInfo.restartRequired
                  ? serverInfo.restartReasons?.join(", ") || "Restart required"
                  : "Server running"
              }
            >
              <div
                className={`h-1.5 w-1.5 rounded-full ${
                  serverInfo.restartRequired
                    ? "bg-warning"
                    : "bg-success animate-pulse"
                }`}
              />
              <span
                className={`text-[10px] font-bold uppercase tracking-wider ${
                  serverInfo.restartRequired ? "text-warning" : "text-success"
                }`}
              >
                {serverInfo.restartRequired ? "Restart Required" : "Running"}
              </span>
            </div>
          )}
          {hasStoredToken && (
            <button
              type="button"
              className="mr-2 rounded-sm border border-transparent px-3 py-1.5 text-xs font-medium uppercase tracking-[0.08em] text-text-muted transition-all duration-150 hover:border-border-subtle hover:bg-bg-hover hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30"
              onClick={handleLogout}
            >
              Logout
            </button>
          )}
          {onRefresh && (
            <button
              className={`navbar-icon-btn flex h-8 w-8 cursor-pointer items-center justify-center rounded-sm border border-transparent bg-transparent text-base text-text-muted transition-all duration-150 hover:border-border-subtle hover:bg-bg-hover hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 ${
                refreshing ? "spinning" : ""
              }`}
              onClick={handleRefresh}
              title="Refresh (⌘R)"
            >

            </button>
          )}
          {/* Mobile menu button */}
          <button
            className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-sm border border-transparent bg-transparent text-lg text-text-muted transition-all duration-150 hover:border-border-subtle hover:bg-bg-hover hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 sm:hidden"
            onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
            aria-label="Toggle menu"
          >
            {mobileMenuOpen ? "✕" : "☰"}
          </button>
        </div>
      </div>

      {/* Mobile menu dropdown */}
      {mobileMenuOpen && (
        <nav className="flex flex-col border-t border-border-subtle bg-bg-surface sm:hidden">
          {tabs.map((tab) => (
            <NavLink
              key={tab.id}
              to={tab.path}
              className={({ isActive }) =>
                `px-4 py-3 text-sm font-medium transition-colors duration-150 ${
                  isActive
                    ? "bg-primary/10 text-text-primary"
                    : "text-text-secondary hover:bg-bg-elevated hover:text-text-primary"
                }`
              }
            >
              {tab.label}
            </NavLink>
          ))}
          {hasStoredToken && (
            <button
              type="button"
              className="border-t border-border-subtle px-4 py-3 text-left text-sm font-medium text-text-secondary transition-colors duration-150 hover:bg-bg-elevated hover:text-text-primary"
              onClick={handleLogout}
            >
              Logout
            </button>
          )}
        </nav>
      )}
    </header>
  );
}