@allbluecn/admin-app 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +86 -0
- package/src/client.tsx +31 -0
- package/src/components/admin/admin-user-menu.tsx +90 -0
- package/src/components/admin/developer-log.tsx +356 -0
- package/src/components/ai-provider/model-list-editor.tsx +147 -0
- package/src/components/ai-provider/provider-form.tsx +434 -0
- package/src/components/ai-provider/provider-icon.tsx +43 -0
- package/src/components/ai-provider/provider-logo.tsx +87 -0
- package/src/components/ai-provider/provider-table.tsx +258 -0
- package/src/components/auth/admin-login-form.tsx +214 -0
- package/src/components/auth/auth-brand-panel.tsx +60 -0
- package/src/components/auth/auth-header.tsx +38 -0
- package/src/components/auth/auth-layout.tsx +66 -0
- package/src/components/auth/auth-providers.tsx +34 -0
- package/src/components/auth/icon-input.tsx +46 -0
- package/src/components/auth/oauth-section.tsx +51 -0
- package/src/components/auth/password-input.tsx +67 -0
- package/src/components/common/page-header.tsx +42 -0
- package/src/components/common/placeholder-page.tsx +42 -0
- package/src/components/component/component-form-dialog.tsx +348 -0
- package/src/components/component/component-import-dialog.tsx +235 -0
- package/src/components/component/component-table.tsx +153 -0
- package/src/components/data-table/data-table-pagination.tsx +114 -0
- package/src/components/data-table/data-table.tsx +218 -0
- package/src/components/data-table/table-detail-layout.tsx +99 -0
- package/src/components/dependency/changelog-dialog.tsx +108 -0
- package/src/components/dependency/columns.tsx +232 -0
- package/src/components/dependency/dependency-table.tsx +368 -0
- package/src/components/dependency/edit-dialog.tsx +200 -0
- package/src/components/knowledge-public/bookmark-form-dialog.tsx +215 -0
- package/src/components/knowledge-public/compliance-form-dialog.tsx +308 -0
- package/src/components/knowledge-public/legal-form-dialog.tsx +315 -0
- package/src/components/layout/sidebar-02/app-sidebar.tsx +160 -0
- package/src/components/layout/sidebar-02/index.tsx +33 -0
- package/src/components/layout/sidebar-02/logo.tsx +40 -0
- package/src/components/layout/sidebar-02/nav-main.tsx +153 -0
- package/src/components/layout/sidebar-02/nav-notifications.tsx +78 -0
- package/src/components/layout/sidebar-02/team-switcher.tsx +106 -0
- package/src/components/magicui/animated-theme-toggler.tsx +272 -0
- package/src/components/magicui/rainbow-button.tsx +78 -0
- package/src/components/market/columns.tsx +151 -0
- package/src/components/market/market-app-form.tsx +237 -0
- package/src/components/market/market-app-list.tsx +156 -0
- package/src/components/monitoring/error-list.tsx +143 -0
- package/src/components/monitoring/health-cards.tsx +152 -0
- package/src/components/monitoring/metrics-stats.tsx +184 -0
- package/src/components/monitoring/request-log-table.tsx +179 -0
- package/src/components/providers/theme-toggle.tsx +59 -0
- package/src/components/settings/avatar/avatar-config.ts +46 -0
- package/src/components/ui/alert-dialog.tsx +216 -0
- package/src/components/ui/alert.tsx +76 -0
- package/src/components/ui/avatar.tsx +129 -0
- package/src/components/ui/badge.tsx +66 -0
- package/src/components/ui/button.tsx +84 -0
- package/src/components/ui/card.tsx +120 -0
- package/src/components/ui/checkbox.tsx +50 -0
- package/src/components/ui/collapsible.tsx +50 -0
- package/src/components/ui/dialog.tsx +185 -0
- package/src/components/ui/dropdown-menu.tsx +286 -0
- package/src/components/ui/field.tsx +255 -0
- package/src/components/ui/input-group.tsx +171 -0
- package/src/components/ui/input.tsx +36 -0
- package/src/components/ui/label.tsx +41 -0
- package/src/components/ui/pagination.tsx +146 -0
- package/src/components/ui/select.tsx +209 -0
- package/src/components/ui/separator.tsx +45 -0
- package/src/components/ui/sheet.tsx +164 -0
- package/src/components/ui/sidebar.tsx +719 -0
- package/src/components/ui/skeleton.tsx +30 -0
- package/src/components/ui/sonner.tsx +37 -0
- package/src/components/ui/switch.tsx +48 -0
- package/src/components/ui/table.tsx +133 -0
- package/src/components/ui/textarea.tsx +35 -0
- package/src/components/ui/tooltip.tsx +74 -0
- package/src/components/user/columns.tsx +153 -0
- package/src/components/user/settings-activity-log.tsx +358 -0
- package/src/components/user/settings-profile/constants.ts +42 -0
- package/src/components/user/settings-profile/types.ts +47 -0
- package/src/components/user/settings-profile.tsx +664 -0
- package/src/components/user/user-create-dialog.tsx +137 -0
- package/src/components/user/user-filters.tsx +117 -0
- package/src/hooks/use-mobile.ts +36 -0
- package/src/instrument.client.ts +34 -0
- package/src/lib/anyhub/capabilities.ts +59 -0
- package/src/lib/anyhub/types.ts +43 -0
- package/src/lib/auth-client.ts +83 -0
- package/src/lib/auth-utils.ts +24 -0
- package/src/lib/auth.config.ts +264 -0
- package/src/lib/avatar.ts +41 -0
- package/src/lib/i18n/client.ts +32 -0
- package/src/lib/i18n/config.ts +40 -0
- package/src/lib/i18n/detect.ts +43 -0
- package/src/lib/i18n/locales/en/admin-ai.json +128 -0
- package/src/lib/i18n/locales/en/admin-billing.json +56 -0
- package/src/lib/i18n/locales/en/admin-users.json +88 -0
- package/src/lib/i18n/locales/en/common.json +877 -0
- package/src/lib/i18n/locales/en/enterprise.json +208 -0
- package/src/lib/i18n/locales/zh/admin-ai.json +128 -0
- package/src/lib/i18n/locales/zh/admin-billing.json +56 -0
- package/src/lib/i18n/locales/zh/admin-users.json +88 -0
- package/src/lib/i18n/locales/zh/common.json +877 -0
- package/src/lib/i18n/locales/zh/enterprise.json +208 -0
- package/src/lib/i18n/server.ts +18 -0
- package/src/lib/i18n/types.ts +5 -0
- package/src/lib/monitoring-benchmarks.ts +85 -0
- package/src/lib/monitoring-descriptions.ts +78 -0
- package/src/lib/prisma.ts +18 -0
- package/src/lib/router-context.ts +38 -0
- package/src/lib/session.ts +38 -0
- package/src/lib/utils.ts +23 -0
- package/src/providers/index.tsx +45 -0
- package/src/routeTree.gen.ts +1199 -0
- package/src/router.tsx +51 -0
- package/src/routes/__root.tsx +170 -0
- package/src/routes/_nav/ai-providers/$id/edit/route.lazy.tsx +74 -0
- package/src/routes/_nav/ai-providers/$id/edit/route.tsx +35 -0
- package/src/routes/_nav/ai-providers/index/route.lazy.tsx +33 -0
- package/src/routes/_nav/ai-providers/new/route.lazy.tsx +31 -0
- package/src/routes/_nav/ai-providers/new/route.tsx +20 -0
- package/src/routes/_nav/ai-providers/route.lazy.tsx +26 -0
- package/src/routes/_nav/ai-providers/route.tsx +31 -0
- package/src/routes/_nav/apps/route.lazy.tsx +30 -0
- package/src/routes/_nav/apps/route.tsx +20 -0
- package/src/routes/_nav/audit/route.lazy.tsx +100 -0
- package/src/routes/_nav/audit/route.tsx +25 -0
- package/src/routes/_nav/billing/invoices/route.lazy.tsx +86 -0
- package/src/routes/_nav/billing/invoices/route.tsx +25 -0
- package/src/routes/_nav/billing/route.lazy.tsx +48 -0
- package/src/routes/_nav/billing/route.tsx +27 -0
- package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.lazy.tsx +160 -0
- package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.tsx +25 -0
- package/src/routes/_nav/billing/subscriptions/route.lazy.tsx +113 -0
- package/src/routes/_nav/billing/subscriptions/route.tsx +28 -0
- package/src/routes/_nav/compliance/route.lazy.tsx +184 -0
- package/src/routes/_nav/compliance/route.tsx +25 -0
- package/src/routes/_nav/components/route.lazy.tsx +184 -0
- package/src/routes/_nav/components/route.tsx +40 -0
- package/src/routes/_nav/dashboard/route.lazy.tsx +33 -0
- package/src/routes/_nav/dashboard/route.tsx +20 -0
- package/src/routes/_nav/dependencies/route.lazy.tsx +66 -0
- package/src/routes/_nav/dependencies/route.tsx +32 -0
- package/src/routes/_nav/developer-logs/route.lazy.tsx +6 -0
- package/src/routes/_nav/developer-logs/route.tsx +14 -0
- package/src/routes/_nav/docs/route.lazy.tsx +30 -0
- package/src/routes/_nav/docs/route.tsx +20 -0
- package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +355 -0
- package/src/routes/_nav/knowledge-public/bookmarks/route.tsx +40 -0
- package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +245 -0
- package/src/routes/_nav/knowledge-public/compliance/route.tsx +40 -0
- package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +242 -0
- package/src/routes/_nav/knowledge-public/legal/route.tsx +40 -0
- package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +235 -0
- package/src/routes/_nav/licenses/$licenseId/route.tsx +25 -0
- package/src/routes/_nav/licenses/new/route.lazy.tsx +237 -0
- package/src/routes/_nav/licenses/route.lazy.tsx +143 -0
- package/src/routes/_nav/licenses/route.tsx +25 -0
- package/src/routes/_nav/market/route.lazy.tsx +31 -0
- package/src/routes/_nav/market/route.tsx +20 -0
- package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +305 -0
- package/src/routes/_nav/monitoring/ai-usage/route.tsx +3 -0
- package/src/routes/_nav/monitoring/errors/route.lazy.tsx +111 -0
- package/src/routes/_nav/monitoring/health/route.lazy.tsx +260 -0
- package/src/routes/_nav/monitoring/logs/route.lazy.tsx +165 -0
- package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +240 -0
- package/src/routes/_nav/monitoring/overview/route.lazy.tsx +138 -0
- package/src/routes/_nav/monitoring/route.lazy.tsx +68 -0
- package/src/routes/_nav/monitoring/route.tsx +20 -0
- package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +399 -0
- package/src/routes/_nav/monitoring/sentry/route.tsx +20 -0
- package/src/routes/_nav/permissions/route.lazy.tsx +504 -0
- package/src/routes/_nav/permissions/route.tsx +26 -0
- package/src/routes/_nav/route.lazy.tsx +31 -0
- package/src/routes/_nav/route.tsx +34 -0
- package/src/routes/_nav/settings/route.lazy.tsx +30 -0
- package/src/routes/_nav/settings/route.tsx +20 -0
- package/src/routes/_nav/sso/route.lazy.tsx +145 -0
- package/src/routes/_nav/sso/route.tsx +25 -0
- package/src/routes/_nav/users/$userId/route.lazy.tsx +432 -0
- package/src/routes/_nav/users/$userId/route.tsx +27 -0
- package/src/routes/_nav/users/-components/users-table.tsx +38 -0
- package/src/routes/_nav/users/route.lazy.tsx +74 -0
- package/src/routes/_nav/users/route.tsx +43 -0
- package/src/routes/api/compliance-download/$jobId.ts +35 -0
- package/src/routes/api/observability/errors.ts +41 -0
- package/src/routes/api/observability/health.ts +30 -0
- package/src/routes/api.auth.$.ts +31 -0
- package/src/routes/api.sentry-test.ts +33 -0
- package/src/routes/index/route.lazy.tsx +22 -0
- package/src/routes/index/route.tsx +24 -0
- package/src/routes/login/route.lazy.tsx +46 -0
- package/src/routes/login/route.tsx +28 -0
- package/src/routes/sentry-tunnel.ts +61 -0
- package/src/routes/setup/route.lazy.tsx +94 -0
- package/src/routes/setup/route.tsx +28 -0
- package/src/server/__tests__/admin-ai-alerts.test.ts +113 -0
- package/src/server/__tests__/licenses-issue.test.ts +98 -0
- package/src/server/admin-ai-alerts.ts +177 -0
- package/src/server/admin-ai-usage.ts +134 -0
- package/src/server/auth-helpers.ts +51 -0
- package/src/server/entitlements.ts +24 -0
- package/src/server/knowledge-public/canonical-id.ts +55 -0
- package/src/server/knowledge-public/github-sync.ts +86 -0
- package/src/server/license-issue.ts +78 -0
- package/src/server/middlewares/request.ts +26 -0
- package/src/server/observability/debug-middleware.ts +39 -0
- package/src/server/observability/error-reporter.ts +99 -0
- package/src/server/observability/health-check.ts +140 -0
- package/src/server/observability/index.ts +33 -0
- package/src/server/observability/logger.ts +49 -0
- package/src/server/observability/metrics.ts +105 -0
- package/src/server/observability/request-middleware.ts +125 -0
- package/src/server/observability/timing.ts +42 -0
- package/src/server/pro-services.ts +32 -0
- package/src/server/serverFns/admin-billing.ts +94 -0
- package/src/server/serverFns/ai-provider/create.ts +92 -0
- package/src/server/serverFns/ai-provider/delete.ts +42 -0
- package/src/server/serverFns/ai-provider/get.ts +35 -0
- package/src/server/serverFns/ai-provider/list.ts +92 -0
- package/src/server/serverFns/ai-provider/update.ts +73 -0
- package/src/server/serverFns/anyhub/market.ts +64 -0
- package/src/server/serverFns/audit.ts +57 -0
- package/src/server/serverFns/auth/session.ts +26 -0
- package/src/server/serverFns/auth/setup.ts +73 -0
- package/src/server/serverFns/compliance.ts +85 -0
- package/src/server/serverFns/components/import.ts +197 -0
- package/src/server/serverFns/components/list.ts +51 -0
- package/src/server/serverFns/components/mutations.ts +84 -0
- package/src/server/serverFns/dependency/helpers.ts +245 -0
- package/src/server/serverFns/dependency/list.ts +127 -0
- package/src/server/serverFns/dependency/sync.ts +123 -0
- package/src/server/serverFns/dependency/update.ts +51 -0
- package/src/server/serverFns/developer-log-auto-generate.ts +176 -0
- package/src/server/serverFns/developer-logs.ts +132 -0
- package/src/server/serverFns/knowledge-public/bookmarks/create.ts +53 -0
- package/src/server/serverFns/knowledge-public/bookmarks/delete.ts +31 -0
- package/src/server/serverFns/knowledge-public/bookmarks/detail.ts +32 -0
- package/src/server/serverFns/knowledge-public/bookmarks/list.ts +66 -0
- package/src/server/serverFns/knowledge-public/bookmarks/sync-all-github.ts +56 -0
- package/src/server/serverFns/knowledge-public/bookmarks/update.ts +50 -0
- package/src/server/serverFns/knowledge-public/compliance/create.ts +58 -0
- package/src/server/serverFns/knowledge-public/compliance/delete.ts +31 -0
- package/src/server/serverFns/knowledge-public/compliance/detail.ts +32 -0
- package/src/server/serverFns/knowledge-public/compliance/list.ts +59 -0
- package/src/server/serverFns/knowledge-public/compliance/update.ts +53 -0
- package/src/server/serverFns/knowledge-public/legal/create.ts +62 -0
- package/src/server/serverFns/knowledge-public/legal/delete.ts +31 -0
- package/src/server/serverFns/knowledge-public/legal/detail.ts +32 -0
- package/src/server/serverFns/knowledge-public/legal/list.ts +60 -0
- package/src/server/serverFns/knowledge-public/legal/update.ts +55 -0
- package/src/server/serverFns/licenses.ts +92 -0
- package/src/server/serverFns/locale.ts +10 -0
- package/src/server/serverFns/monitoring/check-ai-alerts.ts +13 -0
- package/src/server/serverFns/monitoring/get-ai-usage-overview.ts +25 -0
- package/src/server/serverFns/monitoring/get-errors.ts +43 -0
- package/src/server/serverFns/monitoring/get-health-settings.ts +35 -0
- package/src/server/serverFns/monitoring/get-health.ts +55 -0
- package/src/server/serverFns/monitoring/get-metrics.ts +46 -0
- package/src/server/serverFns/monitoring/get-request-logs.ts +66 -0
- package/src/server/serverFns/monitoring/report-error.ts +55 -0
- package/src/server/serverFns/monitoring/reset.ts +70 -0
- package/src/server/serverFns/permissions.ts +214 -0
- package/src/server/serverFns/sentry/config.ts +88 -0
- package/src/server/serverFns/sso-config.ts +83 -0
- package/src/server/serverFns/user/create.ts +40 -0
- package/src/server/serverFns/user/delete.ts +29 -0
- package/src/server/serverFns/user/detail.ts +45 -0
- package/src/server/serverFns/user/list.ts +56 -0
- package/src/server/serverFns/user/reset-password.ts +28 -0
- package/src/server/serverFns/user/update-role.ts +26 -0
- package/src/server/serverFns/user/update-status.ts +26 -0
- package/src/server.ts +27 -0
- package/src/start.ts +85 -0
- package/src/styles/_keyframe-animations.scss +91 -0
- package/src/styles/_variables.scss +296 -0
- package/src/styles/globals.css +200 -0
- package/src/types/start-authjs.d.ts +27 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { useTranslation } from 'react-i18next'
|
|
19
|
+
import {
|
|
20
|
+
Sidebar,
|
|
21
|
+
SidebarContent,
|
|
22
|
+
SidebarFooter,
|
|
23
|
+
SidebarHeader,
|
|
24
|
+
SidebarTrigger,
|
|
25
|
+
useSidebar,
|
|
26
|
+
} from "@/components/ui/sidebar";
|
|
27
|
+
import { cn } from "@/lib/utils";
|
|
28
|
+
import { motion } from "motion/react";
|
|
29
|
+
import {
|
|
30
|
+
Home,
|
|
31
|
+
Layers,
|
|
32
|
+
Package2,
|
|
33
|
+
Settings,
|
|
34
|
+
ShoppingBag,
|
|
35
|
+
Users,
|
|
36
|
+
FileText,
|
|
37
|
+
Bot,
|
|
38
|
+
Boxes,
|
|
39
|
+
Library,
|
|
40
|
+
Scale,
|
|
41
|
+
ShieldCheck,
|
|
42
|
+
Bookmark,
|
|
43
|
+
BugPlay,
|
|
44
|
+
Activity,
|
|
45
|
+
CreditCard,
|
|
46
|
+
Shield,
|
|
47
|
+
ScrollText,
|
|
48
|
+
} from "lucide-react";
|
|
49
|
+
import { Logo } from "@/components/layout/sidebar-02/logo";
|
|
50
|
+
import type { Route } from "./nav-main";
|
|
51
|
+
import DashboardNavigation from "@/components/layout/sidebar-02/nav-main";
|
|
52
|
+
import { NotificationsPopover } from "@/components/layout/sidebar-02/nav-notifications";
|
|
53
|
+
import { AdminUserMenu } from "@/components/admin/admin-user-menu";
|
|
54
|
+
|
|
55
|
+
function buildDashboardRoutes(t: (key: string) => string): Route[] {
|
|
56
|
+
return [
|
|
57
|
+
{ id: "home", title: t('nav.dashboard'), icon: <Home className="size-4" />, link: "/dashboard" },
|
|
58
|
+
{ id: "users", title: t('nav.users'), icon: <Users className="size-4" />, link: "/users" },
|
|
59
|
+
{ id: "docs", title: t('nav.docs'), icon: <FileText className="size-4" />, link: "/docs" },
|
|
60
|
+
{ id: "apps", title: t('nav.apps'), icon: <Package2 className="size-4" />, link: "/apps" },
|
|
61
|
+
{
|
|
62
|
+
id: "knowledge-public", title: t('nav.knowledgePublic.title'), icon: <Library className="size-4" />, link: "#",
|
|
63
|
+
subs: [
|
|
64
|
+
{ title: t('nav.knowledgePublic.laws'), link: "/knowledge-public/legal", icon: <Scale className="size-4" /> },
|
|
65
|
+
{ title: t('nav.knowledgePublic.compliance'), link: "/knowledge-public/compliance", icon: <ShieldCheck className="size-4" /> },
|
|
66
|
+
{ title: t('nav.knowledgePublic.bookmarks'), link: "/knowledge-public/bookmarks", icon: <Bookmark className="size-4" /> },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{ id: "ai-providers", title: t('nav.aiProviders'), icon: <Bot className="size-4" />, link: "/ai-providers" },
|
|
70
|
+
{ id: "components", title: t('nav.components'), icon: <Layers className="size-4" />, link: "/components" },
|
|
71
|
+
{ id: "dependencies", title: t('nav.dependencies'), icon: <Boxes className="size-4" />, link: "/dependencies" },
|
|
72
|
+
{ id: "developer-logs", title: t('nav.developerLogs'), icon: <ScrollText className="size-4" />, link: "/developer-logs" },
|
|
73
|
+
{ id: "market", title: t('nav.market'), icon: <ShoppingBag className="size-4" />, link: "/market" },
|
|
74
|
+
{
|
|
75
|
+
id: "billing", title: t('nav.billing.title'), icon: <CreditCard className="size-4" />, link: "#",
|
|
76
|
+
subs: [
|
|
77
|
+
{ title: t('nav.billing.subscriptions'), link: "/billing/subscriptions" },
|
|
78
|
+
{ title: t('nav.billing.invoices'), link: "/billing/invoices" },
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: "enterprise", title: t('nav.enterprise.title'), icon: <Shield className="size-4" />, link: "#",
|
|
83
|
+
subs: [
|
|
84
|
+
{ title: t('nav.enterprise.licenses'), link: "/licenses" },
|
|
85
|
+
{ title: t('nav.enterprise.audit'), link: "/audit" },
|
|
86
|
+
{ title: t('nav.enterprise.sso'), link: "/sso" },
|
|
87
|
+
{ title: t('nav.enterprise.permissions'), link: "/permissions" },
|
|
88
|
+
{ title: t('nav.enterprise.compliance'), link: "/compliance" },
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "monitoring", title: t('nav.monitoring.title'), icon: <Activity className="size-4" />, link: "#",
|
|
93
|
+
subs: [
|
|
94
|
+
{ title: t('nav.monitoring.overview'), link: "/monitoring/overview" },
|
|
95
|
+
{ title: t('nav.monitoring.logs'), link: "/monitoring/logs" },
|
|
96
|
+
{ title: t('nav.monitoring.errors'), link: "/monitoring/errors" },
|
|
97
|
+
{ title: t('nav.monitoring.metrics'), link: "/monitoring/metrics" },
|
|
98
|
+
{ title: t('nav.monitoring.health'), link: "/monitoring/health" },
|
|
99
|
+
{ title: t('nav.monitoring.sentry'), link: "/monitoring/sentry" },
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
{ id: "settings", title: t('nav.settings'), icon: <Settings className="size-4" />, link: "/settings" },
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const teams = [
|
|
107
|
+
{ id: "1", name: "Alpha Inc.", logo: Logo, plan: "Free" },
|
|
108
|
+
{ id: "2", name: "Beta Corp.", logo: Logo, plan: "Free" },
|
|
109
|
+
{ id: "3", name: "Gamma Tech", logo: Logo, plan: "Free" },
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
export function DashboardSidebar({ routes: propRoutes }: { routes?: Route[] }) {
|
|
113
|
+
const { t } = useTranslation('common')
|
|
114
|
+
const { state } = useSidebar();
|
|
115
|
+
const isCollapsed = state === "collapsed";
|
|
116
|
+
const routes = propRoutes || buildDashboardRoutes(t);
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<Sidebar variant="inset" collapsible="icon">
|
|
120
|
+
<SidebarHeader
|
|
121
|
+
className={cn(
|
|
122
|
+
"flex md:pt-3.5",
|
|
123
|
+
isCollapsed
|
|
124
|
+
? "flex-row items-center justify-between gap-y-4 md:flex-col md:items-start md:justify-start"
|
|
125
|
+
: "flex-row items-center justify-between"
|
|
126
|
+
)}
|
|
127
|
+
>
|
|
128
|
+
<a href="#" className="flex items-center gap-2">
|
|
129
|
+
<Logo className="h-8 w-8" />
|
|
130
|
+
{!isCollapsed && (
|
|
131
|
+
<span className="font-semibold text-black dark:text-white">
|
|
132
|
+
Admin
|
|
133
|
+
</span>
|
|
134
|
+
)}
|
|
135
|
+
</a>
|
|
136
|
+
|
|
137
|
+
<motion.div
|
|
138
|
+
key={isCollapsed ? "header-collapsed" : "header-expanded"}
|
|
139
|
+
className={cn(
|
|
140
|
+
"flex items-center gap-2",
|
|
141
|
+
isCollapsed ? "flex-row md:flex-col-reverse" : "flex-row"
|
|
142
|
+
)}
|
|
143
|
+
initial={{ opacity: 0 }}
|
|
144
|
+
animate={{ opacity: 1 }}
|
|
145
|
+
transition={{ duration: 0.8 }}
|
|
146
|
+
>
|
|
147
|
+
<NotificationsPopover notifications={[]} />
|
|
148
|
+
{!isCollapsed && <SidebarTrigger />}
|
|
149
|
+
{isCollapsed && <SidebarTrigger />}
|
|
150
|
+
</motion.div>
|
|
151
|
+
</SidebarHeader>
|
|
152
|
+
<SidebarContent className="gap-4 px-2 py-4">
|
|
153
|
+
<DashboardNavigation routes={routes} />
|
|
154
|
+
</SidebarContent>
|
|
155
|
+
<SidebarFooter className="px-2">
|
|
156
|
+
<AdminUserMenu />
|
|
157
|
+
</SidebarFooter>
|
|
158
|
+
</Sidebar>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import type { ReactNode } from "react"
|
|
19
|
+
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
|
|
20
|
+
import { DashboardSidebar } from "@/components/layout/sidebar-02/app-sidebar";
|
|
21
|
+
|
|
22
|
+
export default function Sidebar02({ children }: { children?: ReactNode }) {
|
|
23
|
+
return (
|
|
24
|
+
<SidebarProvider>
|
|
25
|
+
<div className="relative flex h-dvh w-full overflow-hidden">
|
|
26
|
+
<DashboardSidebar />
|
|
27
|
+
<SidebarInset className="flex min-h-0 flex-col overflow-hidden">
|
|
28
|
+
<div className="min-h-0 flex-1 flex flex-col overflow-hidden">{children}</div>
|
|
29
|
+
</SidebarInset>
|
|
30
|
+
</div>
|
|
31
|
+
</SidebarProvider>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { SVGProps } from "react";
|
|
19
|
+
|
|
20
|
+
export const Logo = (props: SVGProps<SVGSVGElement>) => (
|
|
21
|
+
<svg
|
|
22
|
+
fill="currentColor"
|
|
23
|
+
height="48"
|
|
24
|
+
viewBox="0 0 40 48"
|
|
25
|
+
width="40"
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
<clipPath id="a">
|
|
29
|
+
<path d="m0 0h40v48h-40z" />
|
|
30
|
+
</clipPath>
|
|
31
|
+
<g clipPath="url(#a)">
|
|
32
|
+
<path d="m25.0887 5.05386-3.933-1.05386-3.3145 12.3696-2.9923-11.16736-3.9331 1.05386 3.233 12.0655-8.05262-8.0526-2.87919 2.8792 8.83271 8.8328-10.99975-2.9474-1.05385625 3.933 12.01860625 3.2204c-.1376-.5935-.2104-1.2119-.2104-1.8473 0-4.4976 3.646-8.1436 8.1437-8.1436 4.4976 0 8.1436 3.646 8.1436 8.1436 0 .6313-.0719 1.2459-.2078 1.8359l10.9227 2.9267 1.0538-3.933-12.0664-3.2332 11.0005-2.9476-1.0539-3.933-12.0659 3.233 8.0526-8.0526-2.8792-2.87916-8.7102 8.71026z" />
|
|
33
|
+
<path d="m27.8723 26.2214c-.3372 1.4256-1.0491 2.7063-2.0259 3.7324l7.913 7.9131 2.8792-2.8792z" />
|
|
34
|
+
<path d="m25.7665 30.0366c-.9886 1.0097-2.2379 1.7632-3.6389 2.1515l2.8794 10.746 3.933-1.0539z" />
|
|
35
|
+
<path d="m21.9807 32.2274c-.65.1671-1.3313.2559-2.0334.2559-.7522 0-1.4806-.102-2.1721-.2929l-2.882 10.7558 3.933 1.0538z" />
|
|
36
|
+
<path d="m17.6361 32.1507c-1.3796-.4076-2.6067-1.1707-3.5751-2.1833l-7.9325 7.9325 2.87919 2.8792z" />
|
|
37
|
+
<path d="m13.9956 29.8973c-.9518-1.019-1.6451-2.2826-1.9751-3.6862l-10.95836 2.9363 1.05385 3.933z" />
|
|
38
|
+
</g>
|
|
39
|
+
</svg>
|
|
40
|
+
);
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
Collapsible,
|
|
21
|
+
CollapsibleContent,
|
|
22
|
+
CollapsibleTrigger,
|
|
23
|
+
} from "@/components/ui/collapsible";
|
|
24
|
+
import {
|
|
25
|
+
SidebarMenu,
|
|
26
|
+
SidebarMenuButton,
|
|
27
|
+
SidebarMenuItem,
|
|
28
|
+
SidebarMenuSub,
|
|
29
|
+
SidebarMenuSubButton,
|
|
30
|
+
SidebarMenuItem as SidebarMenuSubItem,
|
|
31
|
+
useSidebar,
|
|
32
|
+
} from "@/components/ui/sidebar";
|
|
33
|
+
import { cn } from "@/lib/utils";
|
|
34
|
+
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
35
|
+
import { Link } from "@tanstack/react-router";
|
|
36
|
+
import { useLocation } from "@tanstack/react-router";
|
|
37
|
+
import type React from "react";
|
|
38
|
+
import { useState } from "react";
|
|
39
|
+
|
|
40
|
+
export type Route = {
|
|
41
|
+
id: string;
|
|
42
|
+
title: string;
|
|
43
|
+
icon?: React.ReactNode;
|
|
44
|
+
link: string;
|
|
45
|
+
subs?: {
|
|
46
|
+
title: string;
|
|
47
|
+
link: string;
|
|
48
|
+
icon?: React.ReactNode;
|
|
49
|
+
}[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default function DashboardNavigation({ routes }: { routes: Route[] }) {
|
|
53
|
+
const { state } = useSidebar();
|
|
54
|
+
const { pathname } = useLocation();
|
|
55
|
+
const isCollapsed = state === "collapsed";
|
|
56
|
+
const [openCollapsible, setOpenCollapsible] = useState<string | null>(null);
|
|
57
|
+
|
|
58
|
+
const isActive = (link: string) => {
|
|
59
|
+
if (link === "#") return false;
|
|
60
|
+
if (link === "/") return pathname === "/";
|
|
61
|
+
return pathname === link;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<SidebarMenu className="space-y-1">
|
|
66
|
+
{routes.map((route) => {
|
|
67
|
+
const isOpen = !isCollapsed && openCollapsible === route.id;
|
|
68
|
+
const hasSubRoutes = !!route.subs?.length;
|
|
69
|
+
const active = isActive(route.link);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<SidebarMenuItem key={route.id}>
|
|
73
|
+
{hasSubRoutes ? (
|
|
74
|
+
<Collapsible
|
|
75
|
+
open={isOpen}
|
|
76
|
+
onOpenChange={(open) =>
|
|
77
|
+
setOpenCollapsible(open ? route.id : null)
|
|
78
|
+
}
|
|
79
|
+
className="w-full"
|
|
80
|
+
>
|
|
81
|
+
<SidebarMenuButton
|
|
82
|
+
isActive={isOpen || active}
|
|
83
|
+
className={cn(isCollapsed && "justify-center")}
|
|
84
|
+
onClick={() => {
|
|
85
|
+
if (!isCollapsed) {
|
|
86
|
+
setOpenCollapsible(isOpen ? null : route.id)
|
|
87
|
+
}
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
{route.icon}
|
|
91
|
+
{!isCollapsed && (
|
|
92
|
+
<span className="ml-2 flex-1 text-sm font-medium">
|
|
93
|
+
{route.title}
|
|
94
|
+
</span>
|
|
95
|
+
)}
|
|
96
|
+
{!isCollapsed && hasSubRoutes && (
|
|
97
|
+
<span className="ml-auto">
|
|
98
|
+
{isOpen ? (
|
|
99
|
+
<ChevronUp className="size-4" />
|
|
100
|
+
) : (
|
|
101
|
+
<ChevronDown className="size-4" />
|
|
102
|
+
)}
|
|
103
|
+
</span>
|
|
104
|
+
)}
|
|
105
|
+
</SidebarMenuButton>
|
|
106
|
+
|
|
107
|
+
{!isCollapsed && (
|
|
108
|
+
<CollapsibleContent >
|
|
109
|
+
<SidebarMenuSub className="my-1 ml-3.5 ">
|
|
110
|
+
{route.subs?.map((subRoute) => (
|
|
111
|
+
<SidebarMenuSubItem
|
|
112
|
+
key={`${route.id}-${subRoute.title}`}
|
|
113
|
+
className="h-auto"
|
|
114
|
+
>
|
|
115
|
+
<SidebarMenuSubButton asChild isActive={isActive(subRoute.link)}>
|
|
116
|
+
<Link
|
|
117
|
+
to={subRoute.link}
|
|
118
|
+
className="flex items-center rounded-md px-4 py-1.5 text-sm font-medium"
|
|
119
|
+
>
|
|
120
|
+
{subRoute.icon}
|
|
121
|
+
{subRoute.title}
|
|
122
|
+
</Link>
|
|
123
|
+
</SidebarMenuSubButton>
|
|
124
|
+
</SidebarMenuSubItem>
|
|
125
|
+
))}
|
|
126
|
+
</SidebarMenuSub>
|
|
127
|
+
</CollapsibleContent>
|
|
128
|
+
)}
|
|
129
|
+
</Collapsible>
|
|
130
|
+
) : (
|
|
131
|
+
<SidebarMenuButton asChild tooltip={route.title} isActive={active} >
|
|
132
|
+
<Link
|
|
133
|
+
to={route.link}
|
|
134
|
+
className={cn(
|
|
135
|
+
"flex items-center rounded-lg px-2 ",
|
|
136
|
+
isCollapsed && "justify-center"
|
|
137
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
{route.icon}
|
|
140
|
+
{!isCollapsed && (
|
|
141
|
+
<span className="ml-2 text-sm font-medium">
|
|
142
|
+
{route.title}
|
|
143
|
+
</span>
|
|
144
|
+
)}
|
|
145
|
+
</Link>
|
|
146
|
+
</SidebarMenuButton>
|
|
147
|
+
)}
|
|
148
|
+
</SidebarMenuItem>
|
|
149
|
+
);
|
|
150
|
+
})}
|
|
151
|
+
</SidebarMenu>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
20
|
+
import { Button } from "@/components/ui/button";
|
|
21
|
+
import {
|
|
22
|
+
DropdownMenu,
|
|
23
|
+
DropdownMenuContent,
|
|
24
|
+
DropdownMenuItem,
|
|
25
|
+
DropdownMenuLabel,
|
|
26
|
+
DropdownMenuSeparator,
|
|
27
|
+
DropdownMenuTrigger,
|
|
28
|
+
} from "@/components/ui/dropdown-menu";
|
|
29
|
+
import { BellIcon } from "lucide-react";
|
|
30
|
+
|
|
31
|
+
type Notification = {
|
|
32
|
+
id: string;
|
|
33
|
+
avatar: string;
|
|
34
|
+
fallback: string;
|
|
35
|
+
text: string;
|
|
36
|
+
time: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function NotificationsPopover({
|
|
40
|
+
notifications,
|
|
41
|
+
}: {
|
|
42
|
+
notifications: Notification[];
|
|
43
|
+
}) {
|
|
44
|
+
return (
|
|
45
|
+
<DropdownMenu>
|
|
46
|
+
<DropdownMenuTrigger asChild>
|
|
47
|
+
<Button
|
|
48
|
+
variant="ghost"
|
|
49
|
+
size="icon"
|
|
50
|
+
className="rounded-full"
|
|
51
|
+
aria-label="Open notifications"
|
|
52
|
+
>
|
|
53
|
+
<BellIcon className="size-5" />
|
|
54
|
+
</Button>
|
|
55
|
+
</DropdownMenuTrigger>
|
|
56
|
+
<DropdownMenuContent side="right" className="w-80 my-6">
|
|
57
|
+
<DropdownMenuLabel>Notifications</DropdownMenuLabel>
|
|
58
|
+
<DropdownMenuSeparator />
|
|
59
|
+
{notifications.map(({ id, avatar, fallback, text, time }) => (
|
|
60
|
+
<DropdownMenuItem key={id} className="flex items-start gap-3">
|
|
61
|
+
<Avatar className="size-8">
|
|
62
|
+
<AvatarImage src={avatar} alt="Avatar" />
|
|
63
|
+
<AvatarFallback>{fallback}</AvatarFallback>
|
|
64
|
+
</Avatar>
|
|
65
|
+
<div className="flex flex-col">
|
|
66
|
+
<span className="text-sm font-medium">{text}</span>
|
|
67
|
+
<span className="text-xs text-muted-foreground">{time}</span>
|
|
68
|
+
</div>
|
|
69
|
+
</DropdownMenuItem>
|
|
70
|
+
))}
|
|
71
|
+
<DropdownMenuSeparator />
|
|
72
|
+
<DropdownMenuItem className="justify-center text-sm text-muted-foreground hover:text-primary">
|
|
73
|
+
View all notifications
|
|
74
|
+
</DropdownMenuItem>
|
|
75
|
+
</DropdownMenuContent>
|
|
76
|
+
</DropdownMenu>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
DropdownMenu,
|
|
21
|
+
DropdownMenuContent,
|
|
22
|
+
DropdownMenuItem,
|
|
23
|
+
DropdownMenuLabel,
|
|
24
|
+
DropdownMenuSeparator,
|
|
25
|
+
DropdownMenuShortcut,
|
|
26
|
+
DropdownMenuTrigger,
|
|
27
|
+
} from "@/components/ui/dropdown-menu";
|
|
28
|
+
import {
|
|
29
|
+
SidebarMenu,
|
|
30
|
+
SidebarMenuButton,
|
|
31
|
+
SidebarMenuItem,
|
|
32
|
+
useSidebar,
|
|
33
|
+
} from "@/components/ui/sidebar";
|
|
34
|
+
import { ChevronsUpDown, Plus } from "lucide-react";
|
|
35
|
+
import * as React from "react";
|
|
36
|
+
|
|
37
|
+
type Team = {
|
|
38
|
+
name: string;
|
|
39
|
+
logo: React.ElementType;
|
|
40
|
+
plan: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function TeamSwitcher({ teams }: { teams: Team[] }) {
|
|
44
|
+
const { isMobile } = useSidebar();
|
|
45
|
+
const [activeTeam, setActiveTeam] = React.useState(teams[0]);
|
|
46
|
+
|
|
47
|
+
if (!activeTeam) return null;
|
|
48
|
+
|
|
49
|
+
const Logo = activeTeam.logo;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<SidebarMenu>
|
|
53
|
+
<SidebarMenuItem>
|
|
54
|
+
<DropdownMenu>
|
|
55
|
+
<DropdownMenuTrigger asChild>
|
|
56
|
+
<SidebarMenuButton
|
|
57
|
+
size="lg"
|
|
58
|
+
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
59
|
+
>
|
|
60
|
+
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-background text-foreground">
|
|
61
|
+
<Logo className="size-4" />
|
|
62
|
+
</div>
|
|
63
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
64
|
+
<span className="truncate font-semibold">
|
|
65
|
+
{activeTeam.name}
|
|
66
|
+
</span>
|
|
67
|
+
<span className="truncate text-xs">{activeTeam.plan}</span>
|
|
68
|
+
</div>
|
|
69
|
+
<ChevronsUpDown className="ml-auto" />
|
|
70
|
+
</SidebarMenuButton>
|
|
71
|
+
</DropdownMenuTrigger>
|
|
72
|
+
<DropdownMenuContent
|
|
73
|
+
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg mb-4"
|
|
74
|
+
align="start"
|
|
75
|
+
side={isMobile ? "bottom" : "right"}
|
|
76
|
+
sideOffset={4}
|
|
77
|
+
>
|
|
78
|
+
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
|
79
|
+
Teams
|
|
80
|
+
</DropdownMenuLabel>
|
|
81
|
+
{teams.map((team, index) => (
|
|
82
|
+
<DropdownMenuItem
|
|
83
|
+
key={team.name}
|
|
84
|
+
onClick={() => setActiveTeam(team)}
|
|
85
|
+
className="gap-2 p-2"
|
|
86
|
+
>
|
|
87
|
+
<div className="flex size-6 items-center justify-center rounded-sm border">
|
|
88
|
+
<team.logo className="size-4 shrink-0" />
|
|
89
|
+
</div>
|
|
90
|
+
{team.name}
|
|
91
|
+
<DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
|
|
92
|
+
</DropdownMenuItem>
|
|
93
|
+
))}
|
|
94
|
+
<DropdownMenuSeparator />
|
|
95
|
+
<DropdownMenuItem className="gap-2 p-2">
|
|
96
|
+
<div className="flex size-6 items-center justify-center rounded-md border bg-background">
|
|
97
|
+
<Plus className="size-4" />
|
|
98
|
+
</div>
|
|
99
|
+
<div className="font-medium text-muted-foreground">Add team</div>
|
|
100
|
+
</DropdownMenuItem>
|
|
101
|
+
</DropdownMenuContent>
|
|
102
|
+
</DropdownMenu>
|
|
103
|
+
</SidebarMenuItem>
|
|
104
|
+
</SidebarMenu>
|
|
105
|
+
);
|
|
106
|
+
}
|