diff --git a/package.json b/package.json index 3726a638..c1784e5b 100644 --- a/package.json +++ b/package.json @@ -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.148.0", + "@umami/react-zen": "^0.150.0", "@umami/redis-client": "^0.27.0", "bcryptjs": "^2.4.3", "chalk": "^4.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39232881..1ce41ec3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: ^5.80.10 version: 5.80.10(react@19.1.0) '@umami/react-zen': - specifier: ^0.148.0 - version: 0.148.0(@babel/core@7.27.1)(@types/react@19.1.8)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0)) + specifier: ^0.150.0 + version: 0.150.0(@babel/core@7.27.1)(@types/react@19.1.8)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0)) '@umami/redis-client': specifier: ^0.27.0 version: 0.27.0 @@ -2553,8 +2553,8 @@ packages: resolution: {integrity: sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@umami/react-zen@0.148.0': - resolution: {integrity: sha512-4bI8wBgqep6bYNsnhQ87lSAYqiKLj/9o1emFfIot0NIxKd2jsmgEwGrymxDjmunXK4OwRs8ukOYvzuH3WbieGA==} + '@umami/react-zen@0.150.0': + resolution: {integrity: sha512-ogtdNm7jg7BnzWnQtchOAE1eE36EuxRLZaoalPLm8VrK4eQweqnbRFw+ddeAA12xI0hPP8gKOxOJDhH9pJpVVg==} '@umami/redis-client@0.27.0': resolution: {integrity: sha512-SbHTpxhgeZyTBUSp2zdZM+XUtpsaSL4Tad8QXIEhEtjWhvvfoornyT5kLuyYCVtzSAT4daALeGmOO1z6EE1KcA==} @@ -9739,7 +9739,7 @@ snapshots: '@typescript-eslint/types': 8.34.1 eslint-visitor-keys: 4.2.1 - '@umami/react-zen@0.148.0(@babel/core@7.27.1)(@types/react@19.1.8)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))': + '@umami/react-zen@0.150.0(@babel/core@7.27.1)(@types/react@19.1.8)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))': dependencies: '@fontsource/jetbrains-mono': 5.2.6 '@internationalized/date': 3.8.2 diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx index ecff5023..53d305fe 100644 --- a/src/app/(main)/App.tsx +++ b/src/app/(main)/App.tsx @@ -1,5 +1,5 @@ 'use client'; -import { Grid, Loading, Column, Row } from '@umami/react-zen'; +import { Grid, Loading, Column } from '@umami/react-zen'; import Script from 'next/script'; import { usePathname } from 'next/navigation'; import { UpdateNotice } from './UpdateNotice'; @@ -30,20 +30,12 @@ export function App({ children }) { } return ( - - + + - + - - {children} diff --git a/src/app/(main)/SideNav.tsx b/src/app/(main)/SideNav.tsx index 12b0d4ba..4cfe78a7 100644 --- a/src/app/(main)/SideNav.tsx +++ b/src/app/(main)/SideNav.tsx @@ -1,12 +1,11 @@ import Link from 'next/link'; import { Sidebar, - SidebarHeader, SidebarSection, SidebarItem, - Button, - Icon, + SidebarHeader, Row, + SidebarProps, } from '@umami/react-zen'; import { Globe, @@ -16,14 +15,14 @@ import { Grid2X2, Settings, LockKeyhole, - PanelLeft, } from '@/components/icons'; import { useMessages, useNavigation, useGlobalState } from '@/components/hooks'; +import { WebsiteNav } from '@/app/(main)/websites/[websiteId]/WebsiteNav'; -export function SideNav(props: any) { +export function SideNav(props: SidebarProps) { const { formatMessage, labels } = useMessages(); - const { pathname, renderUrl } = useNavigation(); - const [isCollapsed, setCollapsed] = useGlobalState('sidenav-collapsed'); + const { pathname, renderUrl, websiteId } = useNavigation(); + const [isCollapsed] = useGlobalState('sidenav-collapsed'); const links = [ { @@ -68,37 +67,37 @@ export function SideNav(props: any) { ]; return ( - - - } /> - - - {links.map(({ id, path, label, icon }) => { - return ( - - - - ); - })} - - - {bottomLinks.map(({ id, path, label, icon }) => { - return ( - - - - ); - })} - - - - - - - + + + + } /> + + + {links.map(({ id, path, label, icon }) => { + return ( + + + + ); + })} + + + {bottomLinks.map(({ id, path, label, icon }) => { + return ( + + + + ); + })} + + + {websiteId && } + ); } diff --git a/src/app/(main)/TopNav.tsx b/src/app/(main)/TopNav.tsx index db1eefdb..ff2b8f54 100644 --- a/src/app/(main)/TopNav.tsx +++ b/src/app/(main)/TopNav.tsx @@ -5,6 +5,7 @@ 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(); @@ -14,21 +15,27 @@ export function TopNav() { + + {websiteId && !isSettings && ( <> - - - - + + )} @@ -41,3 +48,11 @@ export function TopNav() { ); } + +const Seperator = () => { + return ( + + + + ); +}; diff --git a/src/app/(main)/settings/SettingsLayout.tsx b/src/app/(main)/settings/SettingsLayout.tsx index 1f5e3439..4a9a01cf 100644 --- a/src/app/(main)/settings/SettingsLayout.tsx +++ b/src/app/(main)/settings/SettingsLayout.tsx @@ -1,14 +1,13 @@ 'use client'; import { ReactNode } from 'react'; import { Grid, Column } from '@umami/react-zen'; -import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks'; +import { useMessages, useNavigation } from '@/components/hooks'; import { SideMenu } from '@/components/common/SideMenu'; import { PageHeader } from '@/components/common/PageHeader'; import { Panel } from '@/components/common/Panel'; import { PageBody } from '@/components/common/PageBody'; export function SettingsLayout({ children }: { children: ReactNode }) { - const { user } = useLoginQuery(); const { formatMessage, labels } = useMessages(); const { pathname } = useNavigation(); @@ -23,13 +22,13 @@ export function SettingsLayout({ children }: { children: ReactNode }) { label: formatMessage(labels.profile), url: '/settings/profile', }, - { id: 'teams', label: formatMessage(labels.teams), url: '/settings/teams' }, - user.isAdmin && { + { id: 'websites', label: formatMessage(labels.websites), url: '/settings/websites', }, - ].filter(n => n); + { id: 'teams', label: formatMessage(labels.teams), url: '/settings/teams' }, + ]; const value = items.find(({ url }) => pathname.includes(url))?.id; diff --git a/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx b/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx index 7d8d0f4a..ae2cf1bb 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx @@ -12,7 +12,7 @@ export function WebsiteHeader() { const website = useWebsite(); return ( - }> + } marginBottom="3"> diff --git a/src/app/(main)/websites/[websiteId]/WebsiteLayout.tsx b/src/app/(main)/websites/[websiteId]/WebsiteLayout.tsx index 3c751ae1..14374904 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteLayout.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteLayout.tsx @@ -1,25 +1,17 @@ 'use client'; import { ReactNode } from 'react'; -import { Column, Grid } from '@umami/react-zen'; +import { Column } from '@umami/react-zen'; import { WebsiteProvider } from './WebsiteProvider'; import { PageBody } from '@/components/common/PageBody'; import { WebsiteHeader } from './WebsiteHeader'; -import { WebsiteNav } from '@/app/(main)/websites/[websiteId]/WebsiteNav'; export function WebsiteLayout({ websiteId, children }: { websiteId: string; children: ReactNode }) { return ( - - - - - - - - {children} - - - + + + {children} + ); } diff --git a/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx b/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx index d9b1345d..37411c0a 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx @@ -1,4 +1,4 @@ -import { Icon, Text, Row, NavMenu, NavMenuItem, NavMenuGroup } from '@umami/react-zen'; +import { Icon, Text, Row, NavMenu, NavMenuItem, NavMenuGroup, Column } from '@umami/react-zen'; import { Eye, Lightning, @@ -12,6 +12,8 @@ import { Tag, Money, Network, + UserPlus, + ChartPie, } from '@/components/icons'; import { useMessages, useNavigation } from '@/components/hooks'; import Link from 'next/link'; @@ -22,7 +24,7 @@ export function WebsiteNav({ websiteId }: { websiteId: string }) { const links = [ { - label: formatMessage(labels.core), + label: formatMessage(labels.traffic), items: [ { id: 'overview', @@ -88,6 +90,18 @@ export function WebsiteNav({ websiteId }: { websiteId: string }) { icon: , path: '/breakdown', }, + { + id: 'segments', + label: formatMessage(labels.segments), + icon: , + path: '/segments', + }, + { + id: 'cohorts', + label: formatMessage(labels.cohorts), + icon: , + path: '/cohorts', + }, ], }, { @@ -120,27 +134,29 @@ export function WebsiteNav({ websiteId }: { websiteId: string }) { 'overview'; return ( - - {links.map(({ label, items }) => { - return ( - - {items.map(({ id, label, icon, path }) => { - const isSelected = selected === id; + + + {links.map(({ label, items }) => { + return ( + + {items.map(({ id, label, icon, path }) => { + const isSelected = selected === id; - return ( - - - - {icon} - {label} - - - - ); - })} - - ); - })} - + return ( + + + + {icon} + {label} + + + + ); + })} + + ); + })} + + ); } diff --git a/src/app/(main)/websites/[websiteId]/events/EventProperties.tsx b/src/app/(main)/websites/[websiteId]/events/EventProperties.tsx index 3ed887c7..25e65405 100644 --- a/src/app/(main)/websites/[websiteId]/events/EventProperties.tsx +++ b/src/app/(main)/websites/[websiteId]/events/EventProperties.tsx @@ -28,41 +28,43 @@ export function EventProperties({ websiteId }: { websiteId: string }) { return ( - - - - - {propertyName && ( + {data && ( + + + + + )} + {eventName && propertyName && ( )} @@ -113,10 +115,12 @@ const EventValues = ({ websiteId, eventName, propertyName }) => { minHeight="300px" gap="6" > - - {values && } - - + {values && ( + + + + + )} ); }; diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionProperties.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionProperties.tsx index b3c2a960..d60713d9 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/SessionProperties.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/SessionProperties.tsx @@ -26,20 +26,22 @@ export function SessionProperties({ websiteId }: { websiteId: string }) { minHeight="300px" gap="6" > - - - + {data && ( + + + + )} {propertyName && } ); @@ -84,10 +86,12 @@ const SessionValues = ({ websiteId, propertyName }) => { minHeight="300px" gap="6" > - - {data && } - - + {data && ( + + + + + )} ); }; diff --git a/src/components/common/Empty.tsx b/src/components/common/Empty.tsx index 8a20be3a..8bd8d82d 100644 --- a/src/components/common/Empty.tsx +++ b/src/components/common/Empty.tsx @@ -16,6 +16,7 @@ export function Empty({ message }: EmptyProps) { width="100%" height="100%" minHeight="70px" + flexGrow={1} > {message || formatMessage(messages.noDataAvailable)} diff --git a/src/components/common/PageHeader.tsx b/src/components/common/PageHeader.tsx index 26cf3c54..9f3365ad 100644 --- a/src/components/common/PageHeader.tsx +++ b/src/components/common/PageHeader.tsx @@ -1,5 +1,5 @@ import { ReactNode } from 'react'; -import { Heading, Icon, Row, Text } from '@umami/react-zen'; +import { Heading, Icon, Row, RowProps, Text } from '@umami/react-zen'; export function PageHeader({ title, @@ -7,6 +7,7 @@ export function PageHeader({ icon, showBorder = true, children, + ...props }: { title: string; description?: string; @@ -15,7 +16,7 @@ export function PageHeader({ allowEdit?: boolean; className?: string; children?: ReactNode; -}) { +} & RowProps) { return ( {icon && {icon}} diff --git a/src/components/icons.ts b/src/components/icons.ts index c566e4b3..0f7e3a0f 100644 --- a/src/components/icons.ts +++ b/src/components/icons.ts @@ -45,6 +45,7 @@ export { Upload, User, Users, + UserPlus, X as Close, } from 'lucide-react'; export * from '@/components/svg'; diff --git a/src/components/input/PanelButton.tsx b/src/components/input/PanelButton.tsx new file mode 100644 index 00000000..fe6c8b66 --- /dev/null +++ b/src/components/input/PanelButton.tsx @@ -0,0 +1,14 @@ +import { Button, Icon } from '@umami/react-zen'; +import { PanelLeft } from '@/components/icons'; +import { useGlobalState } from '@/components/hooks'; + +export function PanelButton() { + const [isCollapsed, setIsCollapsed] = useGlobalState('sidenav-collapsed'); + return ( + + ); +} diff --git a/src/components/input/TeamsButton.tsx b/src/components/input/TeamsButton.tsx index 5b5d6a9f..a0048909 100644 --- a/src/components/input/TeamsButton.tsx +++ b/src/components/input/TeamsButton.tsx @@ -16,13 +16,7 @@ import { import { useLoginQuery, useMessages, useUserTeamsQuery, useNavigation } from '@/components/hooks'; import { Chevron, User, Users } from '@/components/icons'; -export function TeamsButton({ - className, - showText = true, -}: { - className?: string; - showText?: boolean; -}) { +export function TeamsButton({ showText = true }: { showText?: boolean }) { const { user } = useLoginQuery(); const { formatMessage, labels } = useMessages(); const { data } = useUserTeamsQuery(user.id); @@ -41,13 +35,17 @@ export function TeamsButton({ return ( - diff --git a/src/components/input/WebsiteSelect.tsx b/src/components/input/WebsiteSelect.tsx index 11c50718..3b5bd669 100644 --- a/src/components/input/WebsiteSelect.tsx +++ b/src/components/input/WebsiteSelect.tsx @@ -1,16 +1,17 @@ import { useState } from 'react'; import { Select, SelectProps, ListItem } from '@umami/react-zen'; import { useUserWebsitesQuery, useWebsiteQuery, useNavigation } from '@/components/hooks'; +import { ButtonProps } from 'react-basics'; export function WebsiteSelect({ websiteId, teamId, - variant, + buttonProps, ...props }: { websiteId?: string; teamId?: string; - variant?: 'primary' | 'outline' | 'quiet' | 'danger' | 'zero'; + buttonProps?: ButtonProps; } & SelectProps) { const { router, renderUrl } = useNavigation(); const [search, setSearch] = useState(''); @@ -32,7 +33,7 @@ export function WebsiteSelect({ items={data?.['data'] || []} value={websiteId} isLoading={isLoading} - buttonProps={{ variant }} + buttonProps={buttonProps} allowSearch={true} searchValue={search} onSearch={handleSearch} diff --git a/src/components/messages.ts b/src/components/messages.ts index 1ebc2939..d6d5a87a 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -338,7 +338,7 @@ export const labels = defineMessages({ location: { id: 'label.location', defaultMessage: 'Location' }, chart: { id: 'label.chart', defaultMessage: 'Chart' }, table: { id: 'label.table', defaultMessage: 'Table' }, - core: { id: 'label.core', defaultMessage: 'Core' }, + traffic: { id: 'label.traffic', defaultMessage: 'Traffic' }, behavior: { id: 'label.behavior', defaultMessage: 'Behavior' }, growth: { id: 'label.growth', defaultMessage: 'Growth' }, });