Updated nav.

This commit is contained in:
Mike Cao 2025-07-23 17:47:56 -07:00
parent 25f96f6b6b
commit a025fc9552
11 changed files with 971 additions and 1005 deletions

View file

@ -82,7 +82,7 @@
"@react-spring/web": "^9.7.3",
"@svgr/cli": "^8.1.0",
"@tanstack/react-query": "^5.80.10",
"@umami/react-zen": "^0.150.0",
"@umami/react-zen": "^0.153.0",
"@umami/redis-client": "^0.27.0",
"bcryptjs": "^2.4.3",
"chalk": "^4.1.1",
@ -111,7 +111,7 @@
"lucide-react": "^0.517.0",
"maxmind": "^4.3.27",
"md5": "^2.3.0",
"next": "15.4.1",
"next": "15.4.3",
"node-fetch": "^3.2.8",
"npm-run-all": "^4.1.5",
"pg": "^8.16.3",

1839
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -18,6 +18,8 @@ import {
} from '@/components/icons';
import { useMessages, useNavigation, useGlobalState } from '@/components/hooks';
import { WebsiteNav } from '@/app/(main)/websites/[websiteId]/WebsiteNav';
import { TeamsButton } from '@/components/input/TeamsButton';
import { PanelButton } from '@/components/input/PanelButton';
export function SideNav(props: SidebarProps) {
const { formatMessage, labels } = useMessages();
@ -73,7 +75,6 @@ export function SideNav(props: SidebarProps) {
{...props}
isCollapsed={isCollapsed || isWebsite}
muteItems={false}
variant="quiet"
showBorder={false}
>
<SidebarSection>
@ -98,6 +99,12 @@ export function SideNav(props: SidebarProps) {
);
})}
</SidebarSection>
<SidebarSection>
<TeamsButton showText={!isCollapsed} />
<Row>
<PanelButton isDisabled={!!isWebsite} />
</Row>
</SidebarSection>
</Sidebar>
{isWebsite && <WebsiteNav websiteId={websiteId} />}
</Row>

View file

@ -1,44 +1,22 @@
import { ThemeButton, Row, Icon } from '@umami/react-zen';
import { ThemeButton, Row } from '@umami/react-zen';
import { LanguageButton } from '@/components/input/LanguageButton';
import { ProfileButton } from '@/components/input/ProfileButton';
import { TeamsButton } from '@/components/input/TeamsButton';
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
import { Slash } from '@/components/icons';
import { useNavigation } from '@/components/hooks';
import { PanelButton } from '@/components/input/PanelButton';
export function TopNav() {
const { teamId, websiteId, pathname } = useNavigation();
const isWebsite = websiteId && !pathname.includes('/settings');
return (
<Row
justifyContent="space-between"
position="absolute"
top="0"
alignItems="center"
justifyContent="flex-end"
paddingY="2"
paddingX="3"
paddingRight="5"
width="100%"
style={{ position: 'sticky', top: 0 }}
backgroundColor="2"
zIndex={1}
>
<Row alignItems="center">
<PanelButton isDisabled={!!isWebsite} />
<Seperator />
<TeamsButton />
{isWebsite && (
<>
<Seperator />
<WebsiteSelect
buttonProps={{ variant: 'quiet' }}
websiteId={websiteId}
teamId={teamId}
/>
</>
)}
</Row>
<Row alignItems="center" justifyContent="flex-end">
<Row alignItems="center" justifyContent="flex-end" backgroundColor="2" borderRadius>
<ThemeButton />
<LanguageButton />
<ProfileButton />
@ -46,11 +24,3 @@ export function TopNav() {
</Row>
);
}
const Seperator = () => {
return (
<Icon strokeColor="7" rotate={-25}>
<Slash />
</Icon>
);
};

View file

@ -22,11 +22,6 @@ export function SettingsLayout({ children }: { children: ReactNode }) {
label: formatMessage(labels.profile),
url: '/settings/profile',
},
{
id: 'websites',
label: formatMessage(labels.websites),
url: '/settings/websites',
},
{ id: 'teams', label: formatMessage(labels.teams), url: '/settings/teams' },
];

View file

@ -1,13 +1,4 @@
import {
Button,
Icon,
Text,
useToast,
DialogTrigger,
Dialog,
Modal,
Column,
} from '@umami/react-zen';
import { Button, Icon, Text, useToast, DialogTrigger, Dialog, Modal } from '@umami/react-zen';
import { PasswordEditForm } from './PasswordEditForm';
import { LockKeyhole } from '@/components/icons';
import { useMessages } from '@/components/hooks';
@ -30,11 +21,7 @@ export function PasswordChangeButton() {
</Button>
<Modal>
<Dialog title={formatMessage(labels.changePassword)} style={{ width: 400 }}>
{({ close }) => (
<Column width="300px">
<PasswordEditForm onSave={handleSave} onClose={close} />
</Column>
)}
{({ close }) => <PasswordEditForm onSave={handleSave} onClose={close} />}
</Dialog>
</Modal>
</DialogTrigger>

View file

@ -17,10 +17,11 @@ import {
} from '@/components/icons';
import { useMessages, useNavigation } from '@/components/hooks';
import Link from 'next/link';
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
export function WebsiteNav({ websiteId }: { websiteId: string }) {
const { formatMessage, labels } = useMessages();
const { pathname, renderUrl } = useNavigation();
const { pathname, renderUrl, teamId } = useNavigation();
const links = [
{
@ -135,7 +136,8 @@ export function WebsiteNav({ websiteId }: { websiteId: string }) {
return (
<Column gap padding width="240px" border="left" overflowY="auto">
<NavMenu highlightColor="2">
<WebsiteSelect buttonProps={{ variant: 'quiet' }} websiteId={websiteId} teamId={teamId} />
<NavMenu muteItems={false}>
{links.map(({ label, items }) => {
return (
<NavMenuGroup title={label} key={label} gap="1">

View file

@ -44,6 +44,7 @@ export {
Trash,
Upload,
User,
CircleUserRound as UserCircle,
Users,
UserPlus,
X as Close,

View file

@ -11,8 +11,8 @@ import {
Text,
} from '@umami/react-zen';
import { useRouter } from 'next/navigation';
import { User, LogOut, CircleUserRound } from 'lucide-react';
import { useMessages, useLoginQuery } from '@/components/hooks';
import { LogOut, Settings, UserCircle, LockKeyhole } from '@/components/icons';
export function ProfileButton() {
const { formatMessage, labels } = useMessages();
@ -21,31 +21,34 @@ export function ProfileButton() {
const cloudMode = !!process.env.cloudMode;
const handleSelect = (key: Key) => {
if (key === 'profile') {
router.push('/settings/profile');
}
if (key === 'logout') {
router.push('/logout');
}
router.push(`/${key}`);
};
return (
<MenuTrigger>
<Button data-test="button-profile" variant="quiet">
<Icon>
<CircleUserRound />
<UserCircle />
</Icon>
</Button>
<Popover placement="bottom end">
<Menu autoFocus="last" onAction={handleSelect}>
<MenuSection title={user.username}>
<MenuSeparator />
<MenuItem id="profile">
<MenuItem id="settings">
<Icon>
<User />
<Settings />
</Icon>
<Text>{formatMessage(labels.profile)}</Text>
<Text>{formatMessage(labels.settings)}</Text>
</MenuItem>
{user.isAdmin && (
<MenuItem id="admin">
<Icon>
<LockKeyhole />
</Icon>
<Text>{formatMessage(labels.admin)}</Text>
</MenuItem>
)}
{!cloudMode && (
<MenuItem data-test="item-logout" id="logout">
<Icon>

View file

@ -3,7 +3,6 @@ import { useRouter } from 'next/navigation';
import {
Text,
Icon,
Button,
Menu,
MenuItem,
MenuTrigger,
@ -12,6 +11,8 @@ import {
Popover,
Row,
Box,
SidebarItem,
Pressable,
} from '@umami/react-zen';
import { useLoginQuery, useMessages, useUserTeamsQuery, useNavigation } from '@/components/hooks';
import { Chevron, User, Users } from '@/components/icons';
@ -35,19 +36,20 @@ export function TeamsButton({ showText = true }: { showText?: boolean }) {
return (
<MenuTrigger>
<Button variant="quiet">
<Row alignItems="center" justifyContent="space-between" width="100%" gap>
<Row alignItems="center" gap>
<Icon>{teamId ? <Users /> : <User />}</Icon>
{showText && <Text truncate>{teamId ? team?.name : user.username}</Text>}
<Pressable>
<SidebarItem
label={teamId ? team?.name : user.username}
icon={teamId ? <Users /> : <User />}
>
<Row alignItems="center" justifyContent="space-between" width="100%" gap>
{showText && (
<Icon rotate={90} size="sm">
<Chevron />
</Icon>
)}
</Row>
{showText && (
<Icon rotate={90} size="sm">
<Chevron />
</Icon>
)}
</Row>
</Button>
</SidebarItem>
</Pressable>
<Popover placement="bottom start">
<Box minWidth="300px">
<Menu

View file

@ -22,7 +22,7 @@ export function Legend({
return (
<Row key={text} onClick={() => onClick(item)}>
<StatusLight color={color.alpha(color.alpha() + 0.2).toHex()}>
<Text size="1" color={hidden ? 'disabled' : undefined} wrap="nowrap">
<Text size="2" color={hidden ? 'disabled' : undefined} wrap="nowrap">
{text}
</Text>
</StatusLight>