Обновление файла

This commit is contained in:
2026-07-25 04:27:55 +03:00
parent 23b072a6d3
commit f34850e157
+111 -60
View File
@@ -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>
); );
} }