From 554054d3a1ca5de57ff58ac5b42daaf69edd7115 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 25 Sep 2025 20:46:00 -0700 Subject: [PATCH] Merged nav menus. --- next.config.ts | 20 ++++---- pnpm-lock.yaml | 48 +++++++++++++++++++- src/app/(main)/App.tsx | 11 +++-- src/app/(main)/SideNav.tsx | 10 ++-- src/assets/switch.svg | 1 + src/components/icons.ts | 4 +- src/components/input/NavButton.tsx | 73 +++++++++++++++++++----------- src/components/messages.ts | 1 + src/components/svg/Download.tsx | 9 ++++ src/components/svg/Export.tsx | 12 +++++ src/components/svg/Switch.tsx | 19 ++++++++ src/components/svg/index.ts | 3 ++ 12 files changed, 164 insertions(+), 47 deletions(-) create mode 100644 src/assets/switch.svg create mode 100644 src/components/svg/Download.tsx create mode 100644 src/components/svg/Export.tsx create mode 100644 src/components/svg/Switch.tsx diff --git a/next.config.ts b/next.config.ts index eac6f327..17705dc2 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,15 +3,16 @@ import pkg from './package.json' assert { type: 'json' }; const TRACKER_SCRIPT = '/script.js'; -const basePath = process.env.BASE_PATH; -const collectApiEndpoint = process.env.COLLECT_API_ENDPOINT; -const cloudMode = !!process.env.CLOUD_MODE; -const corsMaxAge = process.env.CORS_MAX_AGE; -const defaultLocale = process.env.DEFAULT_LOCALE; -const forceSSL = process.env.FORCE_SSL; -const frameAncestors = process.env.ALLOWED_FRAME_URLS ?? ''; -const trackerScriptName = process.env.TRACKER_SCRIPT_NAME; -const trackerScriptURL = process.env.TRACKER_SCRIPT_URL; +const basePath = process.env.BASE_PATH || ''; +const cloudMode = process.env.CLOUD_MODE || ''; +const cloudUrl = process.env.CLOUD_URL || ''; +const collectApiEndpoint = process.env.COLLECT_API_ENDPOINT || ''; +const corsMaxAge = process.env.CORS_MAX_AGE || ''; +const defaultLocale = process.env.DEFAULT_LOCALE || ''; +const forceSSL = process.env.FORCE_SSL || ''; +const frameAncestors = process.env.ALLOWED_FRAME_URLS || ''; +const trackerScriptName = process.env.TRACKER_SCRIPT_NAME || ''; +const trackerScriptURL = process.env.TRACKER_SCRIPT_URL || ''; const contentSecurityPolicy = [ `default-src 'self'`, @@ -163,6 +164,7 @@ export default { env: { basePath, cloudMode, + cloudUrl, currentVersion: pkg.version, defaultLocale, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61707399..94992862 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -367,7 +367,44 @@ importers: specifier: ^5.9.2 version: 5.9.2 - dist: {} + dist: + dependencies: + chart.js: + specifier: ^4.5.0 + version: 4.5.0 + chartjs-adapter-date-fns: + specifier: ^3.0.0 + version: 3.0.0(chart.js@4.5.0)(date-fns@2.30.0) + colord: + specifier: ^2.9.2 + version: 2.9.3 + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.2 + lucide-react: + specifier: ^0.542.0 + version: 0.542.0(react@19.1.1) + pure-rand: + specifier: ^7.0.1 + version: 7.0.1 + react-simple-maps: + specifier: ^2.3.0 + version: 2.3.0(prop-types@15.8.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react-use-measure: + specifier: ^2.0.4 + version: 2.1.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react-window: + specifier: ^1.8.6 + version: 1.8.11(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + serialize-error: + specifier: ^12.0.0 + version: 12.0.0 + thenby: + specifier: ^1.3.4 + version: 1.3.4 + uuid: + specifier: ^11.1.0 + version: 11.1.0 packages: @@ -5246,6 +5283,11 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lucide-react@0.542.0: + resolution: {integrity: sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lucide-react@0.543.0: resolution: {integrity: sha512-fpVfuOQO0V3HBaOA1stIiP/A2fPCXHIleRZL16Mx3HmjTYwNSbimhnFBygs2CAfU1geexMX5ItUcWBGUaqw5CA==} peerDependencies: @@ -13376,6 +13418,10 @@ snapshots: dependencies: react: 19.1.1 + lucide-react@0.542.0(react@19.1.1): + dependencies: + react: 19.1.1 + lucide-react@0.543.0(react@19.1.1): dependencies: react: 19.1.1 diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx index 6c14a484..c3f88c5b 100644 --- a/src/app/(main)/App.tsx +++ b/src/app/(main)/App.tsx @@ -1,22 +1,25 @@ 'use client'; import { Grid, Loading, Column } from '@umami/react-zen'; import Script from 'next/script'; -import { usePathname } from 'next/navigation'; import { UpdateNotice } from './UpdateNotice'; import { SideNav } from '@/app/(main)/SideNav'; -import { useLoginQuery, useConfig } from '@/components/hooks'; +import { useLoginQuery, useConfig, useNavigation } from '@/components/hooks'; export function App({ children }) { const { user, isLoading, error } = useLoginQuery(); const config = useConfig(); - const pathname = usePathname(); + const { pathname, router } = useNavigation(); if (isLoading || !config) { return ; } if (error) { - window.location.href = `${process.env.basePath || ''}/login`; + if (process.env.cloudMode) { + window.location.href = '/login'; + } else { + router.push('/login'); + } return null; } diff --git a/src/app/(main)/SideNav.tsx b/src/app/(main)/SideNav.tsx index a50f0c1f..4398009e 100644 --- a/src/app/(main)/SideNav.tsx +++ b/src/app/(main)/SideNav.tsx @@ -1,3 +1,4 @@ +import { Key } from 'react'; import Link from 'next/link'; import { Sidebar, @@ -12,8 +13,7 @@ import { Globe, LinkIcon, LogoSvg, Grid2x2, PanelLeft } from '@/components/icons import { useMessages, useNavigation, useGlobalState } from '@/components/hooks'; import { NavButton } from '@/components/input/NavButton'; import { PanelButton } from '@/components/input/PanelButton'; -import { Key } from 'react'; -import { SettingsButton } from '@/components/input/SettingsButton'; +import { LanguageButton } from '@/components/input/LanguageButton'; export function SideNav(props: SidebarProps) { const { formatMessage, labels } = useMessages(); @@ -77,9 +77,9 @@ export function SideNav(props: SidebarProps) { })} - - - {!isCollapsed && !hasNav && } + + + diff --git a/src/assets/switch.svg b/src/assets/switch.svg new file mode 100644 index 00000000..86166cc5 --- /dev/null +++ b/src/assets/switch.svg @@ -0,0 +1 @@ + diff --git a/src/components/icons.ts b/src/components/icons.ts index ddcda3b6..e0e7c678 100644 --- a/src/components/icons.ts +++ b/src/components/icons.ts @@ -1,14 +1,14 @@ export * from 'lucide-react'; export { - Logo as LogoSvg, Compare as CompareSvg, Funnel as FunnelSvg, Lightning as LightningSvg, Location as LocationSvg, + Logo as LogoSvg, Magnet as MagnetSvg, Money as MoneySvg, Network as NetworkSvg, Path as PathSvg, + Switch as SwitchSvg, Target as TargetSvg, - AddUser as AddUserSvg, } from '@/components/svg'; diff --git a/src/components/input/NavButton.tsx b/src/components/input/NavButton.tsx index 39bff58d..240c6735 100644 --- a/src/components/input/NavButton.tsx +++ b/src/components/input/NavButton.tsx @@ -24,9 +24,10 @@ import { Settings, User, Users, + SwitchSvg, } from '@/components/icons'; import { DOCS_URL } from '@/lib/constants'; -import * as url from 'node:url'; +import { ArrowRight } from 'lucide-react'; export interface TeamsButtonProps { showText?: boolean; @@ -43,7 +44,7 @@ export function NavButton({ showText = true }: TeamsButtonProps) { const label = teamId ? team?.name : user.username; const getUrl = (url: string) => { - return cloudMode ? `${process.env.cloudUrl}/${url}` : url; + return cloudMode ? `${process.env.cloudUrl}${url}` : url; }; const handleAction = async () => {}; @@ -75,42 +76,52 @@ export function NavButton({ showText = true }: TeamsButtonProps) { - - - - } label={user.username} /> - - - + - } label={formatMessage(labels.teams)} /> + } label={formatMessage(labels.switchAccount)} /> - + + + + } label={user.username} /> + + + {user?.teams?.map(({ id, name }) => ( - - - - - + + }> {name} - + ))} + {user?.teams?.length === 0 && ( + + + + Manage teams + + + + + + + )} - } label={formatMessage(labels.settings)} /> + + } + label={formatMessage(labels.settings)} + /> {cloudMode && ( <> )} - {user.isAdmin && ( + {!cloudMode && user.isAdmin && ( <> - } label={formatMessage(labels.admin)} /> + } + label={formatMessage(labels.admin)} + /> )} - } label={formatMessage(labels.logout)} /> + } + label={formatMessage(labels.logout)} + /> diff --git a/src/components/messages.ts b/src/components/messages.ts index c40a3b05..0438c06e 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -362,6 +362,7 @@ export const labels = defineMessages({ share: { id: 'label.share', defaultMessage: 'Share' }, support: { id: 'label.support', defaultMessage: 'Support' }, documentation: { id: 'label.documentation', defaultMessage: 'Documentation' }, + switchAccount: { id: 'label.switch-account', defaultMessage: 'Switch account' }, }); export const messages = defineMessages({ diff --git a/src/components/svg/Download.tsx b/src/components/svg/Download.tsx new file mode 100644 index 00000000..56d4d683 --- /dev/null +++ b/src/components/svg/Download.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; +import type { SVGProps } from 'react'; +const SvgDownload = (props: SVGProps) => ( + + + + +); +export default SvgDownload; diff --git a/src/components/svg/Export.tsx b/src/components/svg/Export.tsx new file mode 100644 index 00000000..355321cf --- /dev/null +++ b/src/components/svg/Export.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; +import type { SVGProps } from 'react'; +const SvgExport = (props: SVGProps) => ( + + + + + + + +); +export default SvgExport; diff --git a/src/components/svg/Switch.tsx b/src/components/svg/Switch.tsx new file mode 100644 index 00000000..2a12f393 --- /dev/null +++ b/src/components/svg/Switch.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import type { SVGProps } from 'react'; +const SvgSwitch = (props: SVGProps) => ( + + + +); +export default SvgSwitch; diff --git a/src/components/svg/index.ts b/src/components/svg/index.ts index 86c3ea94..1bfc728a 100644 --- a/src/components/svg/index.ts +++ b/src/components/svg/index.ts @@ -6,7 +6,9 @@ export { default as Bookmark } from './Bookmark'; export { default as Change } from './Change'; export { default as Compare } from './Compare'; export { default as Dashboard } from './Dashboard'; +export { default as Download } from './Download'; export { default as Expand } from './Expand'; +export { default as Export } from './Export'; export { default as Flag } from './Flag'; export { default as Funnel } from './Funnel'; export { default as Gear } from './Gear'; @@ -28,6 +30,7 @@ export { default as Redo } from './Redo'; export { default as Reports } from './Reports'; export { default as Security } from './Security'; export { default as Speaker } from './Speaker'; +export { default as Switch } from './Switch'; export { default as Tag } from './Tag'; export { default as Target } from './Target'; export { default as Visitor } from './Visitor';