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 && (
-
+
);
});
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;
+ }
+ }
}