mirror of
https://github.com/umami-software/umami.git
synced 2025-12-08 05:12:36 +01:00
Updated nav.
This commit is contained in:
parent
25f96f6b6b
commit
a025fc9552
11 changed files with 971 additions and 1005 deletions
|
|
@ -82,7 +82,7 @@
|
||||||
"@react-spring/web": "^9.7.3",
|
"@react-spring/web": "^9.7.3",
|
||||||
"@svgr/cli": "^8.1.0",
|
"@svgr/cli": "^8.1.0",
|
||||||
"@tanstack/react-query": "^5.80.10",
|
"@tanstack/react-query": "^5.80.10",
|
||||||
"@umami/react-zen": "^0.150.0",
|
"@umami/react-zen": "^0.153.0",
|
||||||
"@umami/redis-client": "^0.27.0",
|
"@umami/redis-client": "^0.27.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
|
|
@ -111,7 +111,7 @@
|
||||||
"lucide-react": "^0.517.0",
|
"lucide-react": "^0.517.0",
|
||||||
"maxmind": "^4.3.27",
|
"maxmind": "^4.3.27",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"next": "15.4.1",
|
"next": "15.4.3",
|
||||||
"node-fetch": "^3.2.8",
|
"node-fetch": "^3.2.8",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
|
|
|
||||||
1839
pnpm-lock.yaml
generated
1839
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -18,6 +18,8 @@ import {
|
||||||
} from '@/components/icons';
|
} from '@/components/icons';
|
||||||
import { useMessages, useNavigation, useGlobalState } from '@/components/hooks';
|
import { useMessages, useNavigation, useGlobalState } from '@/components/hooks';
|
||||||
import { WebsiteNav } from '@/app/(main)/websites/[websiteId]/WebsiteNav';
|
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) {
|
export function SideNav(props: SidebarProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
@ -73,7 +75,6 @@ export function SideNav(props: SidebarProps) {
|
||||||
{...props}
|
{...props}
|
||||||
isCollapsed={isCollapsed || isWebsite}
|
isCollapsed={isCollapsed || isWebsite}
|
||||||
muteItems={false}
|
muteItems={false}
|
||||||
variant="quiet"
|
|
||||||
showBorder={false}
|
showBorder={false}
|
||||||
>
|
>
|
||||||
<SidebarSection>
|
<SidebarSection>
|
||||||
|
|
@ -98,6 +99,12 @@ export function SideNav(props: SidebarProps) {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</SidebarSection>
|
</SidebarSection>
|
||||||
|
<SidebarSection>
|
||||||
|
<TeamsButton showText={!isCollapsed} />
|
||||||
|
<Row>
|
||||||
|
<PanelButton isDisabled={!!isWebsite} />
|
||||||
|
</Row>
|
||||||
|
</SidebarSection>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
{isWebsite && <WebsiteNav websiteId={websiteId} />}
|
{isWebsite && <WebsiteNav websiteId={websiteId} />}
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
||||||
|
|
@ -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 { LanguageButton } from '@/components/input/LanguageButton';
|
||||||
import { ProfileButton } from '@/components/input/ProfileButton';
|
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() {
|
export function TopNav() {
|
||||||
const { teamId, websiteId, pathname } = useNavigation();
|
|
||||||
const isWebsite = websiteId && !pathname.includes('/settings');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
justifyContent="space-between"
|
position="absolute"
|
||||||
|
top="0"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
|
justifyContent="flex-end"
|
||||||
paddingY="2"
|
paddingY="2"
|
||||||
paddingX="3"
|
paddingX="3"
|
||||||
paddingRight="5"
|
paddingRight="5"
|
||||||
width="100%"
|
width="100%"
|
||||||
style={{ position: 'sticky', top: 0 }}
|
style={{ position: 'sticky', top: 0 }}
|
||||||
backgroundColor="2"
|
|
||||||
zIndex={1}
|
zIndex={1}
|
||||||
>
|
>
|
||||||
<Row alignItems="center">
|
<Row alignItems="center" justifyContent="flex-end" backgroundColor="2" borderRadius>
|
||||||
<PanelButton isDisabled={!!isWebsite} />
|
|
||||||
<Seperator />
|
|
||||||
<TeamsButton />
|
|
||||||
{isWebsite && (
|
|
||||||
<>
|
|
||||||
<Seperator />
|
|
||||||
<WebsiteSelect
|
|
||||||
buttonProps={{ variant: 'quiet' }}
|
|
||||||
websiteId={websiteId}
|
|
||||||
teamId={teamId}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Row>
|
|
||||||
<Row alignItems="center" justifyContent="flex-end">
|
|
||||||
<ThemeButton />
|
<ThemeButton />
|
||||||
<LanguageButton />
|
<LanguageButton />
|
||||||
<ProfileButton />
|
<ProfileButton />
|
||||||
|
|
@ -46,11 +24,3 @@ export function TopNav() {
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Seperator = () => {
|
|
||||||
return (
|
|
||||||
<Icon strokeColor="7" rotate={-25}>
|
|
||||||
<Slash />
|
|
||||||
</Icon>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,6 @@ export function SettingsLayout({ children }: { children: ReactNode }) {
|
||||||
label: formatMessage(labels.profile),
|
label: formatMessage(labels.profile),
|
||||||
url: '/settings/profile',
|
url: '/settings/profile',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'websites',
|
|
||||||
label: formatMessage(labels.websites),
|
|
||||||
url: '/settings/websites',
|
|
||||||
},
|
|
||||||
{ id: 'teams', label: formatMessage(labels.teams), url: '/settings/teams' },
|
{ id: 'teams', label: formatMessage(labels.teams), url: '/settings/teams' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,4 @@
|
||||||
import {
|
import { Button, Icon, Text, useToast, DialogTrigger, Dialog, Modal } from '@umami/react-zen';
|
||||||
Button,
|
|
||||||
Icon,
|
|
||||||
Text,
|
|
||||||
useToast,
|
|
||||||
DialogTrigger,
|
|
||||||
Dialog,
|
|
||||||
Modal,
|
|
||||||
Column,
|
|
||||||
} from '@umami/react-zen';
|
|
||||||
import { PasswordEditForm } from './PasswordEditForm';
|
import { PasswordEditForm } from './PasswordEditForm';
|
||||||
import { LockKeyhole } from '@/components/icons';
|
import { LockKeyhole } from '@/components/icons';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
|
|
@ -30,11 +21,7 @@ export function PasswordChangeButton() {
|
||||||
</Button>
|
</Button>
|
||||||
<Modal>
|
<Modal>
|
||||||
<Dialog title={formatMessage(labels.changePassword)} style={{ width: 400 }}>
|
<Dialog title={formatMessage(labels.changePassword)} style={{ width: 400 }}>
|
||||||
{({ close }) => (
|
{({ close }) => <PasswordEditForm onSave={handleSave} onClose={close} />}
|
||||||
<Column width="300px">
|
|
||||||
<PasswordEditForm onSave={handleSave} onClose={close} />
|
|
||||||
</Column>
|
|
||||||
)}
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Modal>
|
</Modal>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,11 @@ import {
|
||||||
} from '@/components/icons';
|
} from '@/components/icons';
|
||||||
import { useMessages, useNavigation } from '@/components/hooks';
|
import { useMessages, useNavigation } from '@/components/hooks';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||||
|
|
||||||
export function WebsiteNav({ websiteId }: { websiteId: string }) {
|
export function WebsiteNav({ websiteId }: { websiteId: string }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { pathname, renderUrl } = useNavigation();
|
const { pathname, renderUrl, teamId } = useNavigation();
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{
|
{
|
||||||
|
|
@ -135,7 +136,8 @@ export function WebsiteNav({ websiteId }: { websiteId: string }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column gap padding width="240px" border="left" overflowY="auto">
|
<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 }) => {
|
{links.map(({ label, items }) => {
|
||||||
return (
|
return (
|
||||||
<NavMenuGroup title={label} key={label} gap="1">
|
<NavMenuGroup title={label} key={label} gap="1">
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ export {
|
||||||
Trash,
|
Trash,
|
||||||
Upload,
|
Upload,
|
||||||
User,
|
User,
|
||||||
|
CircleUserRound as UserCircle,
|
||||||
Users,
|
Users,
|
||||||
UserPlus,
|
UserPlus,
|
||||||
X as Close,
|
X as Close,
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ import {
|
||||||
Text,
|
Text,
|
||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { User, LogOut, CircleUserRound } from 'lucide-react';
|
|
||||||
import { useMessages, useLoginQuery } from '@/components/hooks';
|
import { useMessages, useLoginQuery } from '@/components/hooks';
|
||||||
|
import { LogOut, Settings, UserCircle, LockKeyhole } from '@/components/icons';
|
||||||
|
|
||||||
export function ProfileButton() {
|
export function ProfileButton() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
@ -21,31 +21,34 @@ export function ProfileButton() {
|
||||||
const cloudMode = !!process.env.cloudMode;
|
const cloudMode = !!process.env.cloudMode;
|
||||||
|
|
||||||
const handleSelect = (key: Key) => {
|
const handleSelect = (key: Key) => {
|
||||||
if (key === 'profile') {
|
router.push(`/${key}`);
|
||||||
router.push('/settings/profile');
|
|
||||||
}
|
|
||||||
if (key === 'logout') {
|
|
||||||
router.push('/logout');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuTrigger>
|
<MenuTrigger>
|
||||||
<Button data-test="button-profile" variant="quiet">
|
<Button data-test="button-profile" variant="quiet">
|
||||||
<Icon>
|
<Icon>
|
||||||
<CircleUserRound />
|
<UserCircle />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
<Popover placement="bottom end">
|
<Popover placement="bottom end">
|
||||||
<Menu autoFocus="last" onAction={handleSelect}>
|
<Menu autoFocus="last" onAction={handleSelect}>
|
||||||
<MenuSection title={user.username}>
|
<MenuSection title={user.username}>
|
||||||
<MenuSeparator />
|
<MenuSeparator />
|
||||||
<MenuItem id="profile">
|
<MenuItem id="settings">
|
||||||
<Icon>
|
<Icon>
|
||||||
<User />
|
<Settings />
|
||||||
</Icon>
|
</Icon>
|
||||||
<Text>{formatMessage(labels.profile)}</Text>
|
<Text>{formatMessage(labels.settings)}</Text>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
{user.isAdmin && (
|
||||||
|
<MenuItem id="admin">
|
||||||
|
<Icon>
|
||||||
|
<LockKeyhole />
|
||||||
|
</Icon>
|
||||||
|
<Text>{formatMessage(labels.admin)}</Text>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
{!cloudMode && (
|
{!cloudMode && (
|
||||||
<MenuItem data-test="item-logout" id="logout">
|
<MenuItem data-test="item-logout" id="logout">
|
||||||
<Icon>
|
<Icon>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { useRouter } from 'next/navigation';
|
||||||
import {
|
import {
|
||||||
Text,
|
Text,
|
||||||
Icon,
|
Icon,
|
||||||
Button,
|
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
MenuTrigger,
|
MenuTrigger,
|
||||||
|
|
@ -12,6 +11,8 @@ import {
|
||||||
Popover,
|
Popover,
|
||||||
Row,
|
Row,
|
||||||
Box,
|
Box,
|
||||||
|
SidebarItem,
|
||||||
|
Pressable,
|
||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
import { useLoginQuery, useMessages, useUserTeamsQuery, useNavigation } from '@/components/hooks';
|
import { useLoginQuery, useMessages, useUserTeamsQuery, useNavigation } from '@/components/hooks';
|
||||||
import { Chevron, User, Users } from '@/components/icons';
|
import { Chevron, User, Users } from '@/components/icons';
|
||||||
|
|
@ -35,19 +36,20 @@ export function TeamsButton({ showText = true }: { showText?: boolean }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuTrigger>
|
<MenuTrigger>
|
||||||
<Button variant="quiet">
|
<Pressable>
|
||||||
<Row alignItems="center" justifyContent="space-between" width="100%" gap>
|
<SidebarItem
|
||||||
<Row alignItems="center" gap>
|
label={teamId ? team?.name : user.username}
|
||||||
<Icon>{teamId ? <Users /> : <User />}</Icon>
|
icon={teamId ? <Users /> : <User />}
|
||||||
{showText && <Text truncate>{teamId ? team?.name : user.username}</Text>}
|
>
|
||||||
|
<Row alignItems="center" justifyContent="space-between" width="100%" gap>
|
||||||
|
{showText && (
|
||||||
|
<Icon rotate={90} size="sm">
|
||||||
|
<Chevron />
|
||||||
|
</Icon>
|
||||||
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
{showText && (
|
</SidebarItem>
|
||||||
<Icon rotate={90} size="sm">
|
</Pressable>
|
||||||
<Chevron />
|
|
||||||
</Icon>
|
|
||||||
)}
|
|
||||||
</Row>
|
|
||||||
</Button>
|
|
||||||
<Popover placement="bottom start">
|
<Popover placement="bottom start">
|
||||||
<Box minWidth="300px">
|
<Box minWidth="300px">
|
||||||
<Menu
|
<Menu
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export function Legend({
|
||||||
return (
|
return (
|
||||||
<Row key={text} onClick={() => onClick(item)}>
|
<Row key={text} onClick={() => onClick(item)}>
|
||||||
<StatusLight color={color.alpha(color.alpha() + 0.2).toHex()}>
|
<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}
|
||||||
</Text>
|
</Text>
|
||||||
</StatusLight>
|
</StatusLight>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue