diff --git a/plane-src/apps/admin/app/(all)/(dashboard)/layout.tsx b/plane-src/apps/admin/app/(all)/(dashboard)/layout.tsx index b0ec4b8..6eb3f9a 100644 --- a/plane-src/apps/admin/app/(all)/(dashboard)/layout.tsx +++ b/plane-src/apps/admin/app/(all)/(dashboard)/layout.tsx @@ -16,7 +16,6 @@ import { NewUserPopup } from "@/components/common/new-user-popup"; import { useUser } from "@/hooks/store"; // local components import type { Route } from "./+types/layout"; -import { AdminSidebar } from "./sidebar"; function AdminLayout(_props: Route.ComponentProps) { // router @@ -39,7 +38,6 @@ function AdminLayout(_props: Route.ComponentProps) { if (isUserLoggedIn) { return (
-
diff --git a/plane-src/apps/admin/app/assets/logos/nodedc-logo.svg b/plane-src/apps/admin/app/assets/logos/nodedc-logo.svg new file mode 100644 index 0000000..6f00864 --- /dev/null +++ b/plane-src/apps/admin/app/assets/logos/nodedc-logo.svg @@ -0,0 +1 @@ + diff --git a/plane-src/apps/admin/components/common/header/index.tsx b/plane-src/apps/admin/components/common/header/index.tsx index 3bb84ce..a635873 100644 --- a/plane-src/apps/admin/components/common/header/index.tsx +++ b/plane-src/apps/admin/components/common/header/index.tsx @@ -4,82 +4,179 @@ * See the LICENSE file for details. */ -import { Fragment } from "react"; +import { Fragment, useEffect, useState } from "react"; import { observer } from "mobx-react"; +import { useTheme as useNextTheme } from "next-themes"; +import Link from "next/link"; import { usePathname } from "next/navigation"; -import { ChevronRight, Menu, Settings } from "lucide-react"; -// components -import { BreadcrumbLink } from "../breadcrumb-link"; +import { BrainCog, ChevronDown, ExternalLink, Image, LogOut, Mail, Palette, Settings, UserCog2 } from "lucide-react"; +import { Menu, Transition } from "@headlessui/react"; +// plane imports +import { API_BASE_URL } from "@plane/constants"; +import { LockIcon, WorkspaceIcon } from "@plane/propel/icons"; +import { AuthService } from "@plane/services"; +import { Avatar } from "@plane/ui"; +import { cn, getFileURL } from "@plane/utils"; +// assets +import NodeDcLogo from "@/app/assets/logos/nodedc-logo.svg?url"; // hooks -import { useTheme } from "@/hooks/store"; +import { useUser } from "@/hooks/store"; // local imports -import { CORE_HEADER_SEGMENT_LABELS } from "./core"; -import { EXTENDED_HEADER_SEGMENT_LABELS } from "./extended"; -export const HamburgerToggle = observer(function HamburgerToggle() { - const { isSidebarCollapsed, toggleSidebar } = useTheme(); - return ( - - ); -}); +const authService = new AuthService(); -const HEADER_SEGMENT_LABELS = { - ...CORE_HEADER_SEGMENT_LABELS, - ...EXTENDED_HEADER_SEGMENT_LABELS, -}; +const PRIMARY_NAVIGATION = [ + { label: "Основное", href: "/general/", Icon: Settings }, + { label: "Почта", href: "/email/", Icon: Mail }, + { label: "Аутентификация", href: "/authentication/", Icon: LockIcon }, + { label: "Воркспейсы", href: "/workspace/", Icon: WorkspaceIcon }, +]; + +const FEATURE_NAVIGATION = [ + { label: "ИИ", href: "/ai/", Icon: BrainCog, description: "OpenAI модель и API-ключ" }, + { label: "Изображения", href: "/image/", Icon: Image, description: "Внешние библиотеки изображений" }, +]; export const AdminHeader = observer(function AdminHeader() { const pathName = usePathname(); + const { currentUser, signOut } = useUser(); + const { resolvedTheme, setTheme } = useNextTheme(); + const [csrfToken, setCsrfToken] = useState(undefined); - // Function to dynamically generate breadcrumb items based on pathname - const generateBreadcrumbItems = (pathname: string) => { - const pathSegments = pathname.split("/").slice(1); // removing the first empty string. - pathSegments.pop(); + const isFeatureRoute = FEATURE_NAVIGATION.some((item) => pathName?.startsWith(item.href)); + const adminName = currentUser?.display_name || currentUser?.email || "Глобальный админ"; + const avatarName = currentUser?.display_name || currentUser?.email || "DC"; - let currentUrl = ""; - const breadcrumbItems = pathSegments.map((segment) => { - currentUrl += "/" + segment; - return { - title: HEADER_SEGMENT_LABELS[segment] ?? segment.toUpperCase(), - href: currentUrl, - }; - }); - return breadcrumbItems; + const handleThemeSwitch = () => { + const newTheme = resolvedTheme === "dark" ? "light" : "dark"; + setTheme(newTheme); }; - const breadcrumbItems = generateBreadcrumbItems(pathName || ""); + useEffect(() => { + if (csrfToken === undefined) + void authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token)); + }, [csrfToken]); return ( -
-
- - {breadcrumbItems.length >= 0 && ( - - )} + + + + + + {adminName} + + + {currentUser ? ( + + ) : ( + + )} + + + + +
+ {adminName} + {currentUser?.email} +
+
+ + + {resolvedTheme === "dark" ? "Светлая тема" : "Темная тема"} + +
+
+
+ + + + Выйти + +
+
+
+
+
-
+ ); }); diff --git a/plane-src/apps/admin/styles/globals.css b/plane-src/apps/admin/styles/globals.css index e475d1b..5d4e683 100644 --- a/plane-src/apps/admin/styles/globals.css +++ b/plane-src/apps/admin/styles/globals.css @@ -126,8 +126,7 @@ body { .nodedc-technical-confirm-modal { background: - linear-gradient(180deg, rgba(255, 255, 255, 0.048) 0%, rgba(255, 255, 255, 0.014) 100%), - rgba(6, 6, 8, 0.76) !important; + linear-gradient(180deg, rgba(255, 255, 255, 0.048) 0%, rgba(255, 255, 255, 0.014) 100%), rgba(6, 6, 8, 0.76) !important; -webkit-backdrop-filter: blur(54px) saturate(130%); backdrop-filter: blur(54px) saturate(130%); box-shadow: @@ -151,8 +150,7 @@ body { border: 0 !important; border-radius: 1.25rem !important; background: - linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.012) 100%), - rgba(8, 8, 11, 0.92) !important; + linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.012) 100%), rgba(8, 8, 11, 0.92) !important; padding: 0.75rem !important; -webkit-backdrop-filter: blur(44px); backdrop-filter: blur(44px); @@ -183,11 +181,133 @@ body { } .nodedc-admin-header { - min-height: 4.25rem; + min-height: 5.75rem; border: 0 !important; - border-radius: 0 0 1.35rem 1.35rem; - margin: 0.65rem 0.75rem 0; - width: calc(100% - 1.5rem) !important; + background: transparent !important; + padding: 1.75rem clamp(1.25rem, 3vw, 2.75rem) 0.5rem !important; + } + + .nodedc-admin-header-top { + grid-template-columns: minmax(11rem, 1fr) auto minmax(11rem, 1fr); + } + + .nodedc-admin-logo { + display: block; + width: 9.25rem; + height: auto; + } + + .nodedc-admin-top-nav { + display: inline-flex; + max-width: min(100%, 48rem); + align-items: center; + justify-content: center; + gap: 0.35rem; + border-radius: 999px; + background: transparent !important; + padding: 0; + -webkit-backdrop-filter: none; + backdrop-filter: none; + } + + .nodedc-admin-top-nav-item { + display: inline-flex; + min-height: 2.35rem; + align-items: center; + justify-content: center; + gap: 0.45rem; + border: 0 !important; + border-radius: 999px !important; + background: transparent !important; + color: rgba(255, 255, 255, 0.72) !important; + padding: 0.55rem 0.85rem !important; + font-size: 0.75rem; + font-weight: 600; + line-height: 1; + white-space: nowrap; + box-shadow: none !important; + transition: + background-color 160ms ease, + color 160ms ease, + transform 160ms ease; + } + + .nodedc-admin-top-nav-item:hover { + background: rgba(255, 255, 255, 0.045) !important; + color: var(--text-color-primary) !important; + } + + .nodedc-admin-top-nav-item[data-active="true"] { + background: rgba(255, 255, 255, 0.94) !important; + color: #08090b !important; + } + + .nodedc-admin-top-nav-item[data-active="true"] * { + color: #08090b !important; + } + + .nodedc-admin-feature-menu-item { + display: flex; + width: 100%; + align-items: center; + gap: 0.75rem; + border-radius: 1rem; + padding: 0.65rem; + color: rgba(255, 255, 255, 0.74); + transition: + background-color 160ms ease, + color 160ms ease; + } + + .nodedc-admin-feature-menu-item.is-hovered, + .nodedc-admin-feature-menu-item:hover { + background: rgba(255, 255, 255, 0.06); + color: var(--text-color-primary); + } + + .nodedc-admin-feature-menu-item.is-active { + background: rgba(255, 255, 255, 0.09); + } + + .nodedc-admin-feature-menu-item.is-active svg { + color: rgb(var(--nodedc-accent-rgb)); + } + + .nodedc-admin-user-button { + display: inline-flex; + min-width: 0; + align-items: center; + justify-content: flex-end; + gap: 0.75rem; + border: 0 !important; + border-radius: 999px !important; + background: transparent !important; + padding: 0 !important; + box-shadow: none !important; + } + + .nodedc-admin-user-button:hover > span:last-child { + background: rgba(255, 255, 255, 0.11) !important; + } + + .nodedc-admin-menu-action { + display: flex; + min-height: 2.35rem; + width: 100%; + align-items: center; + gap: 0.55rem; + border-radius: 0.9rem !important; + padding: 0.55rem 0.65rem !important; + color: rgba(255, 255, 255, 0.72) !important; + text-align: left; + transition: + background-color 160ms ease, + color 160ms ease; + } + + .nodedc-admin-menu-action:hover { + background: rgba(255, 255, 255, 0.06) !important; + color: var(--text-color-primary) !important; } .nodedc-admin-breadcrumbs { @@ -198,14 +318,13 @@ body { } .nodedc-admin-breadcrumb-pill { - min-height: 2.5rem; + min-height: 2.2rem; border: 0 !important; - border-radius: 1.25rem !important; + border-radius: 999px !important; background: - linear-gradient(180deg, rgba(255, 255, 255, 0.036) 0%, rgba(255, 255, 255, 0.014) 100%), - rgba(255, 255, 255, 0.04) !important; + linear-gradient(180deg, rgba(255, 255, 255, 0.036) 0%, rgba(255, 255, 255, 0.014) 100%), rgba(255, 255, 255, 0.04) !important; color: rgba(255, 255, 255, 0.72) !important; - padding: 0.55rem 0.9rem !important; + padding: 0.5rem 0.8rem !important; box-shadow: none !important; transition: background-color 160ms ease, @@ -214,8 +333,7 @@ body { .nodedc-admin-breadcrumb-pill:hover { background: - linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.018) 100%), - rgba(255, 255, 255, 0.065) !important; + linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.018) 100%), rgba(255, 255, 255, 0.065) !important; color: var(--text-color-primary) !important; } @@ -232,7 +350,7 @@ body { } .nodedc-page { - padding: 1rem 0 1.5rem; + padding: 0.5rem 0 1.5rem; } .nodedc-page-header { @@ -268,8 +386,7 @@ body { border: 0 !important; border-radius: 1.35rem !important; background: - linear-gradient(180deg, rgba(255, 255, 255, 0.044) 0%, rgba(255, 255, 255, 0.016) 100%), - rgba(255, 255, 255, 0.04) !important; + linear-gradient(180deg, rgba(255, 255, 255, 0.044) 0%, rgba(255, 255, 255, 0.016) 100%), rgba(255, 255, 255, 0.04) !important; box-shadow: none !important; -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px); @@ -279,8 +396,7 @@ body { border: 0 !important; border-radius: 0.65rem !important; background: - linear-gradient(180deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.014) 100%), - rgba(0, 0, 0, 0.32) !important; + linear-gradient(180deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.014) 100%), rgba(0, 0, 0, 0.32) !important; color: rgb(var(--nodedc-accent-rgb)) !important; box-shadow: none !important; } @@ -335,8 +451,7 @@ body { .nodedc-admin-sidebar-action:hover { background: - linear-gradient(180deg, rgba(255, 255, 255, 0.052) 0%, rgba(255, 255, 255, 0.018) 100%), - rgba(255, 255, 255, 0.07) !important; + linear-gradient(180deg, rgba(255, 255, 255, 0.052) 0%, rgba(255, 255, 255, 0.018) 100%), rgba(255, 255, 255, 0.07) !important; color: var(--text-color-primary) !important; } @@ -490,8 +605,7 @@ body { border: 0 !important; border-radius: 1.25rem !important; background: - linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.022) 100%), - rgba(255, 255, 255, 0.055) !important; + linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.022) 100%), rgba(255, 255, 255, 0.055) !important; color: rgba(255, 255, 255, 0.76) !important; box-shadow: none !important; padding-inline: 1.25rem !important; @@ -568,4 +682,41 @@ body { background: rgba(255, 255, 255, 0.08) !important; color: rgb(var(--nodedc-card-active-rgb)) !important; } + + @media (max-width: 1180px) { + .nodedc-admin-header { + min-height: 8.25rem; + } + + .nodedc-admin-header-top { + grid-template-columns: 1fr auto; + } + + .nodedc-admin-top-nav { + grid-column: 1 / -1; + grid-row: 2; + justify-self: start; + overflow-x: auto; + } + } + + @media (max-width: 720px) { + .nodedc-admin-header { + min-height: 10.5rem; + padding-inline: 1rem !important; + } + + .nodedc-admin-header-top { + grid-template-columns: 1fr; + } + + .nodedc-admin-top-nav { + width: 100%; + justify-content: flex-start; + } + + .nodedc-admin-user-button { + justify-self: start; + } + } }