diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx
index eba4e264f..33e998075 100644
--- a/src/app/(main)/App.tsx
+++ b/src/app/(main)/App.tsx
@@ -5,6 +5,7 @@ import { useEffect } from 'react';
import { MobileNav } from '@/app/(main)/MobileNav';
import { SideNav } from '@/app/(main)/SideNav';
import { useConfig, useLoginQuery, useNavigation } from '@/components/hooks';
+import { TeamsButton } from '@/components/input/TeamsButton';
import { LAST_TEAM_CONFIG } from '@/lib/constants';
import { removeItem, setItem } from '@/lib/storage';
import { UpdateNotice } from './UpdateNotice';
@@ -50,6 +51,18 @@ export function App({ children }) {
+
+
+
+
+
{children}
diff --git a/src/app/(main)/MobileNav.tsx b/src/app/(main)/MobileNav.tsx
index 158886db3..85d03b175 100644
--- a/src/app/(main)/MobileNav.tsx
+++ b/src/app/(main)/MobileNav.tsx
@@ -5,7 +5,7 @@ import { IconLabel } from '@/components/common/IconLabel';
import { useMessages, useNavigation } from '@/components/hooks';
import { Globe, Grid2x2, LinkIcon } from '@/components/icons';
import { MobileMenuButton } from '@/components/input/MobileMenuButton';
-import { NavButton } from '@/components/input/NavButton';
+import { TeamsButton } from '@/components/input/TeamsButton';
import { Logo } from '@/components/svg';
import { AdminNav } from './admin/AdminNav';
import { SettingsNav } from './settings/SettingsNav';
@@ -44,7 +44,7 @@ export function MobileNav() {
return (
<>
-
+
{links.map(link => {
return (
diff --git a/src/app/(main)/SideNav.tsx b/src/app/(main)/SideNav.tsx
index 865a9e280..92172be98 100644
--- a/src/app/(main)/SideNav.tsx
+++ b/src/app/(main)/SideNav.tsx
@@ -6,26 +6,21 @@ import {
Icon,
Row,
Text,
- ThemeButton,
Tooltip,
TooltipTrigger,
} from '@umami/react-zen';
import Link from 'next/link';
-import type { Key } from 'react';
import { WebsiteNav } from '@/app/(main)/websites/[websiteId]/WebsiteNav';
import { IconLabel } from '@/components/common/IconLabel';
import { useGlobalState, useMessages, useNavigation } from '@/components/hooks';
import { Globe, Grid2x2, LayoutDashboard, LinkIcon, PanelLeft } from '@/components/icons';
-import { LanguageButton } from '@/components/input/LanguageButton';
-import { NavButton } from '@/components/input/NavButton';
+import { UserButton } from '@/components/input/UserButton';
import { Logo } from '@/components/svg';
export function SideNav(props: any) {
const { t, labels } = useMessages();
- const { pathname, renderUrl, websiteId, router } = useNavigation();
- const [isCollapsed, setIsCollapsed] = useGlobalState('sidenav-collapsed', false);
-
- const hasNav = !!(websiteId || pathname.startsWith('/admin') || pathname.includes('/settings'));
+ const { pathname, renderUrl, websiteId } = useNavigation();
+ const [isCollapsed] = useGlobalState('sidenav-collapsed', false);
const links = [
{
@@ -54,10 +49,6 @@ export function SideNav(props: any) {
},
];
- const handleSelect = (id: Key) => {
- router.push(id === 'user' ? '/websites' : `/teams/${id}/websites`);
- };
-
return (
-
-
-
{websiteId ? (
) : (
@@ -126,9 +114,8 @@ export function SideNav(props: any) {
)}
-
-
-
+
+
);
diff --git a/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx b/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx
index 6bd892e40..01833eebb 100644
--- a/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx
+++ b/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx
@@ -32,97 +32,66 @@ export function WebsiteNav({
router.push(renderUrl(`/websites/${value}`));
};
- const renderValue = (value: any) => {
- return (
-
- {value?.selectedItem?.name}
-
- );
- };
-
- if (isCollapsed !== undefined) {
- return (
-
-
-
-
-
- } label={isCollapsed ? '' : t(labels.back)} padding />
-
-
- {t(labels.back)}
-
-
- {!isCollapsed && (
-
-
-
- )}
- {items.map(({ label: sectionLabel, items: sectionItems }, index) => (
-
- {!isCollapsed && (
-
- {sectionLabel}
-
- )}
- {sectionItems.map(({ id, path, label, icon }) => {
- const isSelected = selectedKey === id;
- return (
-
-
-
-
-
-
-
- {label}
-
-
- );
- })}
-
- ))}
-
- );
- }
-
return (
-
-
-
+
+
+
+
+
+ } label={isCollapsed ? '' : t(labels.back)} padding />
+
+
+ {t(labels.back)}
+
+
+
+
+
+ {items.map(({ label: sectionLabel, items: sectionItems }, index) => (
+
+ {!isCollapsed && (
+
+ {sectionLabel}
+
+ )}
+ {sectionItems.map(({ id, path, label, icon }) => {
+ const isSelected = selectedKey === id;
+ return (
+
+
+
+
+
+
+
+ {label}
+
+
+ );
+ })}
+
+ ))}
);
}
diff --git a/src/components/input/NavButton.tsx b/src/components/input/NavButton.tsx
index 9f11ab2a0..e7c7504c6 100644
--- a/src/components/input/NavButton.tsx
+++ b/src/components/input/NavButton.tsx
@@ -15,26 +15,10 @@ import {
import { ArrowRight } from 'lucide-react';
import type { Key } from 'react';
import { IconLabel } from '@/components/common/IconLabel';
-import {
- useConfig,
- useLoginQuery,
- useMessages,
- useMobile,
- useNavigation,
-} from '@/components/hooks';
-import {
- BookText,
- ChevronRight,
- ExternalLink,
- LifeBuoy,
- LockKeyhole,
- LogOut,
- Settings,
- User,
- Users,
-} from '@/components/icons';
+import { useLoginQuery, useMessages, useMobile, useNavigation } from '@/components/hooks';
+import { ChevronRight, User, Users } from '@/components/icons';
import { Switch } from '@/components/svg';
-import { DOCS_URL, LAST_TEAM_CONFIG } from '@/lib/constants';
+import { LAST_TEAM_CONFIG } from '@/lib/constants';
import { removeItem } from '@/lib/storage';
export interface TeamsButtonProps {
@@ -44,13 +28,13 @@ export interface TeamsButtonProps {
export function NavButton({ showText = true }: TeamsButtonProps) {
const { user } = useLoginQuery();
- const { cloudMode } = useConfig();
const { t, labels } = useMessages();
const { teamId, router } = useNavigation();
const { isMobile } = useMobile();
const team = user?.teams?.find(({ id }) => id === teamId);
const selectedKeys = new Set([teamId || 'user']);
const label = teamId ? team?.name : user.username;
+ const cloudMode = !!process.env.cloudMode;
const getUrl = (url: string) => {
return cloudMode ? `${process.env.cloudUrl}${url}` : url;
@@ -134,52 +118,6 @@ export function NavButton({ showText = true }: TeamsButtonProps) {
-
- }
- label={t(labels.settings)}
- />
- {cloudMode && (
- <>
- }
- label={t(labels.documentation)}
- >
-
-
-
-
- }
- label={t(labels.support)}
- />
- >
- )}
- {!cloudMode && user.isAdmin && (
- <>
-
- }
- label={t(labels.admin)}
- />
- >
- )}
-
- }
- label={t(labels.logout)}
- />
diff --git a/src/components/input/TeamsButton.tsx b/src/components/input/TeamsButton.tsx
new file mode 100644
index 000000000..c9e27d5fe
--- /dev/null
+++ b/src/components/input/TeamsButton.tsx
@@ -0,0 +1,92 @@
+import {
+ Button,
+ Column,
+ Icon,
+ Menu,
+ MenuItem,
+ MenuSection,
+ MenuSeparator,
+ MenuTrigger,
+ Popover,
+ Row,
+ Text,
+} from '@umami/react-zen';
+import { ArrowRight } from 'lucide-react';
+import type { Key } from 'react';
+import { IconLabel } from '@/components/common/IconLabel';
+import { useLoginQuery, useMessages, useMobile, useNavigation } from '@/components/hooks';
+import { ChevronRight, User, Users } from '@/components/icons';
+import { LAST_TEAM_CONFIG } from '@/lib/constants';
+import { removeItem } from '@/lib/storage';
+
+export function TeamsButton() {
+ const { user } = useLoginQuery();
+ const { t, labels } = useMessages();
+ const { teamId, router } = useNavigation();
+ const team = user?.teams?.find(({ id }) => id === teamId);
+ const selectedKeys = new Set([teamId || 'user']);
+ const label = teamId ? team?.name : user.username;
+
+ const cloudMode = !!process.env.cloudMode;
+
+ const getUrl = (url: string) => {
+ return cloudMode ? `${process.env.cloudUrl}${url}` : url;
+ };
+
+ const handleAction = async (key: Key) => {
+ if (key === 'user') {
+ removeItem(LAST_TEAM_CONFIG);
+ if (cloudMode) {
+ window.location.href = '/';
+ } else {
+ router.push('/');
+ }
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/input/UserButton.tsx b/src/components/input/UserButton.tsx
new file mode 100644
index 000000000..8804f8e5d
--- /dev/null
+++ b/src/components/input/UserButton.tsx
@@ -0,0 +1,204 @@
+import {
+ Column,
+ Icon,
+ Menu,
+ MenuItem,
+ MenuSeparator,
+ MenuTrigger,
+ Popover,
+ Pressable,
+ Row,
+ SubmenuTrigger,
+ Text,
+ Tooltip,
+ TooltipTrigger,
+ useTheme,
+} from '@umami/react-zen';
+import { useConfig, useLocale, useLoginQuery, useMessages, useMobile } from '@/components/hooks';
+import {
+ BookText,
+ ExternalLink,
+ Globe,
+ LifeBuoy,
+ LockKeyhole,
+ LogOut,
+ Moon,
+ Settings,
+ Sun,
+ SunMoon,
+ UserCircle,
+} from '@/components/icons';
+import { DOCS_URL } from '@/lib/constants';
+import { languages } from '@/lib/lang';
+
+export interface UserButtonProps {
+ showText?: boolean;
+}
+
+export function UserButton({ showText = true }: UserButtonProps) {
+ const { user } = useLoginQuery();
+ const { cloudMode } = useConfig();
+ const { t, labels } = useMessages();
+ const { locale, saveLocale } = useLocale();
+ const { theme, setTheme } = useTheme();
+ const { isMobile } = useMobile();
+
+ const getUrl = (url: string) => {
+ return cloudMode ? `${process.env.cloudUrl}${url}` : url;
+ };
+
+ const languageItems = Object.keys(languages).map(key => ({
+ value: key,
+ label: languages[key].label,
+ }));
+
+ const items = [
+ cloudMode && {
+ id: 'docs',
+ label: t(labels.documentation),
+ path: DOCS_URL,
+ icon: ,
+ target: '_blank',
+ external: true,
+ },
+ cloudMode && {
+ id: 'support',
+ label: t(labels.support),
+ path: getUrl('/settings/support'),
+ icon: ,
+ },
+ !cloudMode &&
+ user.isAdmin && {
+ id: 'admin',
+ label: t(labels.admin),
+ path: '/admin',
+ icon: ,
+ },
+ {
+ id: 'separator',
+ separator: true,
+ },
+ {
+ id: 'logout',
+ label: t(labels.logout),
+ path: getUrl('/logout'),
+ icon: ,
+ },
+ ].filter(Boolean);
+
+ return (
+
+
+
+
+
+
+
+
+ {showText && {user.username}}
+
+
+
+ {user.username}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/input/WebsiteSelect.tsx b/src/components/input/WebsiteSelect.tsx
index f3b8e7ff5..62051bdb5 100644
--- a/src/components/input/WebsiteSelect.tsx
+++ b/src/components/input/WebsiteSelect.tsx
@@ -13,11 +13,13 @@ export function WebsiteSelect({
teamId,
onChange,
includeTeams,
+ isCollapsed,
...props
}: {
websiteId?: string;
teamId?: string;
includeTeams?: boolean;
+ isCollapsed?: boolean;
} & SelectProps) {
const { t, messages } = useMessages();
const { data: website } = useWebsiteQuery(websiteId);
@@ -43,14 +45,6 @@ export function WebsiteSelect({
onChange(id);
};
- const renderValue = () => {
- return (
-
- {name ?? website?.name}
-
- );
- };
-
return (