Обновление файла
This commit is contained in:
+111
-60
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState, createContext, useContext } from "react";
|
import React, { useEffect, useState, createContext, useContext } from "react";
|
||||||
import { Routes, Route, Navigate, Link, useNavigate } from "react-router-dom";
|
import { Routes, Route, Navigate, NavLink, useNavigate, Link } from "react-router-dom";
|
||||||
import { api } from "./api.js";
|
import { api } from "./api.js";
|
||||||
import Login from "./pages/Login.jsx";
|
import Login from "./pages/Login.jsx";
|
||||||
import Register from "./pages/Register.jsx";
|
import Register from "./pages/Register.jsx";
|
||||||
@@ -38,34 +38,83 @@ function Protected({ user, loading, children }) {
|
|||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Topbar() {
|
// Иконки (inline SVG — без зависимостей).
|
||||||
|
const Icon = ({ d, size = 18 }) => (
|
||||||
|
<svg viewBox="0 0 24 24" width={size} height={size} fill="none" stroke="currentColor"
|
||||||
|
strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d={d} />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Icons = {
|
||||||
|
server: "M5 4h14a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1zM5 14h14a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1zM7 6h.01M7 16h.01",
|
||||||
|
grid: "M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z",
|
||||||
|
users: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75",
|
||||||
|
logout: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9",
|
||||||
|
layers: "M12 2 2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5",
|
||||||
|
};
|
||||||
|
|
||||||
|
function Sidebar() {
|
||||||
const { user, setUser } = useAuth();
|
const { user, setUser } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
if (!user) return null;
|
||||||
|
|
||||||
|
const navLinkClass = ({ isActive }) =>
|
||||||
|
"nav-link" + (isActive ? " active" : "");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="topbar">
|
<aside className="sidebar">
|
||||||
<div className="brand">
|
<div className="sidebar-brand">
|
||||||
<span className="prompt">$</span> vps-panel
|
<div className="sidebar-brand-mark">VP</div>
|
||||||
|
<div className="sidebar-brand-text">VPS Panel</div>
|
||||||
</div>
|
</div>
|
||||||
{user && (
|
|
||||||
<div className="topbar-right">
|
<nav className="sidebar-nav">
|
||||||
<Link to="/">Мои VPS</Link>
|
<NavLink to="/" end className={navLinkClass}>
|
||||||
{user.role === "admin" && <Link to="/admin/templates">Шаблоны</Link>}
|
<Icon d={Icons.server} />
|
||||||
{user.role === "admin" && <Link to="/admin/users">Пользователи</Link>}
|
<span>Мои VPS</span>
|
||||||
<span className="badge">{user.email}</span>
|
</NavLink>
|
||||||
<button
|
{user.role === "admin" && (
|
||||||
className="btn btn-sm"
|
<>
|
||||||
onClick={() => {
|
<NavLink to="/admin/templates" className={navLinkClass}>
|
||||||
api.logout();
|
<Icon d={Icons.layers} />
|
||||||
setUser(null);
|
<span>Шаблоны</span>
|
||||||
navigate("/login");
|
</NavLink>
|
||||||
}}
|
<NavLink to="/admin/users" className={navLinkClass}>
|
||||||
>
|
<Icon d={Icons.users} />
|
||||||
Выйти
|
<span>Пользователи</span>
|
||||||
</button>
|
</NavLink>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className="sidebar-footer">
|
||||||
|
<div className="sidebar-user">
|
||||||
|
<div className="sidebar-user-avatar">
|
||||||
|
{user.email[0].toUpperCase()}
|
||||||
|
</div>
|
||||||
|
<div className="sidebar-user-info">
|
||||||
|
<div className="sidebar-user-email">{user.email}</div>
|
||||||
|
<div className="sidebar-user-role">
|
||||||
|
{user.role === "admin" ? "Администратор" : "Клиент"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<button
|
||||||
</div>
|
className="btn btn-sm btn-logout"
|
||||||
|
onClick={() => {
|
||||||
|
api.logout();
|
||||||
|
setUser(null);
|
||||||
|
navigate("/login");
|
||||||
|
}}
|
||||||
|
title="Выйти"
|
||||||
|
>
|
||||||
|
<Icon d={Icons.logout} size={16} />
|
||||||
|
<span>Выйти</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,44 +124,46 @@ export default function App() {
|
|||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ user, setUser }}>
|
<AuthContext.Provider value={{ user, setUser }}>
|
||||||
<div className="shell">
|
<div className="shell">
|
||||||
<Topbar />
|
{user && <Sidebar />}
|
||||||
<Routes>
|
<main className="main">
|
||||||
<Route path="/login" element={<Login />} />
|
<Routes>
|
||||||
<Route path="/register" element={<Register />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route
|
<Route path="/register" element={<Register />} />
|
||||||
path="/"
|
<Route
|
||||||
element={
|
path="/"
|
||||||
<Protected user={user} loading={loading}>
|
element={
|
||||||
<Dashboard />
|
<Protected user={user} loading={loading}>
|
||||||
|
<Dashboard />
|
||||||
</Protected>
|
</Protected>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/instances/:id"
|
path="/instances/:id"
|
||||||
element={
|
element={
|
||||||
<Protected user={user} loading={loading}>
|
<Protected user={user} loading={loading}>
|
||||||
<InstanceDetail />
|
<InstanceDetail />
|
||||||
</Protected>
|
</Protected>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/admin/templates"
|
path="/admin/templates"
|
||||||
element={
|
element={
|
||||||
<Protected user={user} loading={loading}>
|
<Protected user={user} loading={loading}>
|
||||||
<AdminTemplates />
|
<AdminTemplates />
|
||||||
</Protected>
|
</Protected>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/admin/users"
|
path="/admin/users"
|
||||||
element={
|
element={
|
||||||
<Protected user={user} loading={loading}>
|
<Protected user={user} loading={loading}>
|
||||||
<AdminUsers />
|
<AdminUsers />
|
||||||
</Protected>
|
</Protected>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user