New menu layout.

This commit is contained in:
Mike Cao 2025-05-14 13:31:07 -07:00
parent 0cfee43814
commit 1c22c18de5
17 changed files with 103 additions and 47 deletions

View file

@ -19,6 +19,7 @@ export function App({ children }) {
if (error) { if (error) {
window.location.href = `${process.env.basePath || ''}/login`; window.location.href = `${process.env.basePath || ''}/login`;
return null;
} }
if (!user || !config) { if (!user || !config) {
@ -30,7 +31,7 @@ export function App({ children }) {
} }
return ( return (
<Grid height="100vh" width="100%" columns="auto 1fr" rows="auto 1fr" overflow="hidden"> <Grid height="100vh" width="100%" columns="auto 1fr" rows="auto 1fr">
<Nav gridColumn="1 / 2" gridRow="1 / 3" /> <Nav gridColumn="1 / 2" gridRow="1 / 3" />
<MenuBar gridColumn="2 / 3" gridRow="1 / 2" /> <MenuBar gridColumn="2 / 3" gridRow="1 / 2" />
<Column <Column
@ -39,6 +40,7 @@ export function App({ children }) {
alignItems="center" alignItems="center"
overflow="auto" overflow="auto"
backgroundColor="2" backgroundColor="2"
position="relative"
> >
<Page> <Page>
{children} {children}

View file

@ -38,7 +38,7 @@ export function Nav(props: any) {
].filter(n => n); ].filter(n => n);
return ( return (
<SideNav {...props} isCollapsed={isCollapsed} variant="0" showBorder={true}> <SideNav {...props} isCollapsed={isCollapsed} variant="2" showBorder={true}>
<SideNavSection> <SideNavSection>
<SideNavHeader label="umami" icon={<Icons.Logo />} /> <SideNavHeader label="umami" icon={<Icons.Logo />} />
</SideNavSection> </SideNavSection>

View file

@ -4,7 +4,6 @@ import { Panel } from '@/components/common/Panel';
import { useNavigation } from '@/components/hooks'; import { useNavigation } from '@/components/hooks';
import { WebsiteChart } from './WebsiteChart'; import { WebsiteChart } from './WebsiteChart';
import { WebsiteExpandedView } from './WebsiteExpandedView'; import { WebsiteExpandedView } from './WebsiteExpandedView';
import { WebsiteHeader } from './WebsiteHeader';
import { WebsiteMetricsBar } from './WebsiteMetricsBar'; import { WebsiteMetricsBar } from './WebsiteMetricsBar';
import { WebsiteTableView } from './WebsiteTableView'; import { WebsiteTableView } from './WebsiteTableView';
import { WebsiteCompareTables } from './WebsiteCompareTables'; import { WebsiteCompareTables } from './WebsiteCompareTables';
@ -15,8 +14,7 @@ export function WebsiteDetailsPage({ websiteId }: { websiteId: string }) {
} = useNavigation(); } = useNavigation();
return ( return (
<Column gap="3"> <Column gap>
<WebsiteHeader websiteId={websiteId} />
<Panel> <Panel>
<WebsiteMetricsBar websiteId={websiteId} showFilter={true} showChange={true} /> <WebsiteMetricsBar websiteId={websiteId} showFilter={true} showChange={true} />
</Panel> </Panel>

View file

@ -0,0 +1,13 @@
'use client';
import { ReactNode } from 'react';
import { WebsiteProvider } from './WebsiteProvider';
import { WebsiteHeader } from '@/app/(main)/websites/[websiteId]/WebsiteHeader';
export function WebsiteLayout({ websiteId, children }: { websiteId: string; children: ReactNode }) {
return (
<WebsiteProvider websiteId={websiteId}>
<WebsiteHeader websiteId={websiteId} />
{children}
</WebsiteProvider>
);
}

View file

@ -1,6 +1,7 @@
import { Tabs, TabList, Tab, Icon, Text, Row } from '@umami/react-zen'; import { Column, Icon, Text, Row } from '@umami/react-zen';
import { Icons } from '@/components/icons'; import { Icons } from '@/components/icons';
import { useMessages, useNavigation } from '@/components/hooks'; import { useMessages, useNavigation } from '@/components/hooks';
import Link from 'next/link';
export function WebsiteTabs({ websiteId }: { websiteId: string }) { export function WebsiteTabs({ websiteId }: { websiteId: string }) {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
@ -31,6 +32,54 @@ export function WebsiteTabs({ websiteId }: { websiteId: string }) {
icon: <Icons.Clock />, icon: <Icons.Clock />,
path: '/realtime', path: '/realtime',
}, },
{
id: 'insights',
label: formatMessage(labels.insights),
icon: <Icons.Lightbulb />,
path: '/insights',
},
{
id: 'goals',
label: formatMessage(labels.goals),
icon: <Icons.Target />,
path: '/goals',
},
{
id: 'funnel',
label: formatMessage(labels.funnel),
icon: <Icons.Funnel />,
path: '/funnels',
},
{
id: 'journeys',
label: formatMessage(labels.journey),
icon: <Icons.Path />,
path: '/goals',
},
{
id: 'retention',
label: formatMessage(labels.retention),
icon: <Icons.Magnet />,
path: '/funnels',
},
{
id: 'utm',
label: formatMessage(labels.utm),
icon: <Icons.Tag />,
path: '/utm',
},
{
id: 'revenue',
label: formatMessage(labels.revenue),
icon: <Icons.Money />,
path: '/revenue',
},
{
id: 'attribution',
label: formatMessage(labels.attribution),
icon: <Icons.Network />,
path: '/attribution',
},
{ {
id: 'reports', id: 'reports',
label: formatMessage(labels.reports), label: formatMessage(labels.reports),
@ -39,24 +88,29 @@ export function WebsiteTabs({ websiteId }: { websiteId: string }) {
}, },
]; ];
const selectedKey = links const selected = links
? links.find(({ path }) => path && pathname.endsWith(path))?.id ? links.find(({ path }) => path && pathname.endsWith(path))?.id
: 'overview'; : 'overview';
return ( return (
<Tabs selectedKey={selectedKey}> <Column gap="2" position="absolute" padding="4" style={{ top: 0, left: 0, bottom: 0 }}>
<TabList items={links}> {links.map(({ id, label, icon, path }) => {
{({ id, label, icon, path }) => { return (
return ( <Link key={id} href={renderTeamUrl(`/websites/${websiteId}/${path}`)}>
<Tab id={id} href={renderTeamUrl(`/websites/${websiteId}/${path}`)}> <Row
<Row gap="3" alignItems="center"> alignItems="center"
<Icon fillColor="currentColor">{icon}</Icon> padding
<Text>{label}</Text> gap
</Row> hoverBackgroundColor="3"
</Tab> borderRadius
); width="160px"
}} >
</TabList> <Icon fillColor="currentColor">{icon}</Icon>
</Tabs> <Text weight={selected === id ? 'bold' : undefined}>{label}</Text>
</Row>
</Link>
);
})}
</Column>
); );
} }

View file

@ -2,7 +2,6 @@
import { TabList, Tab, Tabs, TabPanel, Column } from '@umami/react-zen'; import { TabList, Tab, Tabs, TabPanel, Column } from '@umami/react-zen';
import { EventsTable } from '@/components/metrics/EventsTable'; import { EventsTable } from '@/components/metrics/EventsTable';
import { useState } from 'react'; import { useState } from 'react';
import { WebsiteHeader } from '../WebsiteHeader';
import { EventsDataTable } from './EventsDataTable'; import { EventsDataTable } from './EventsDataTable';
import { EventsMetricsBar } from './EventsMetricsBar'; import { EventsMetricsBar } from './EventsMetricsBar';
import { Panel } from '@/components/common/Panel'; import { Panel } from '@/components/common/Panel';
@ -22,7 +21,6 @@ export function EventsPage({ websiteId }) {
return ( return (
<Column gap="3"> <Column gap="3">
<WebsiteHeader websiteId={websiteId} />
<Panel> <Panel>
<EventsMetricsBar websiteId={websiteId} /> <EventsMetricsBar websiteId={websiteId} />
</Panel> </Panel>

View file

@ -1,5 +1,5 @@
import { Metadata } from 'next'; import { Metadata } from 'next';
import { WebsiteProvider } from './WebsiteProvider'; import { WebsiteLayout } from '@/app/(main)/websites/[websiteId]/WebsiteLayout';
export default async function ({ export default async function ({
children, children,
@ -10,7 +10,7 @@ export default async function ({
}) { }) {
const { websiteId } = await params; const { websiteId } = await params;
return <WebsiteProvider websiteId={websiteId}>{children}</WebsiteProvider>; return <WebsiteLayout websiteId={websiteId}>{children}</WebsiteLayout>;
} }
export const metadata: Metadata = { export const metadata: Metadata = {

View file

@ -11,7 +11,6 @@ import { RealtimeLog } from './RealtimeLog';
import { RealtimeHeader } from './RealtimeHeader'; import { RealtimeHeader } from './RealtimeHeader';
import { RealtimeUrls } from './RealtimeUrls'; import { RealtimeUrls } from './RealtimeUrls';
import { RealtimeCountries } from './RealtimeCountries'; import { RealtimeCountries } from './RealtimeCountries';
import { WebsiteHeader } from '../WebsiteHeader';
import { percentFilter } from '@/lib/filters'; import { percentFilter } from '@/lib/filters';
export function WebsiteRealtimePage({ websiteId }: { websiteId: string }) { export function WebsiteRealtimePage({ websiteId }: { websiteId: string }) {
@ -29,7 +28,6 @@ export function WebsiteRealtimePage({ websiteId }: { websiteId: string }) {
return ( return (
<Grid gap="3"> <Grid gap="3">
<WebsiteHeader websiteId={websiteId} />
<Panel> <Panel>
<RealtimeHeader data={data} /> <RealtimeHeader data={data} />
</Panel> </Panel>

View file

@ -2,7 +2,6 @@
import Link from 'next/link'; import Link from 'next/link';
import { Button, Flexbox, Icon, Icons, Text } from '@umami/react-zen'; import { Button, Flexbox, Icon, Icons, Text } from '@umami/react-zen';
import { useMessages, useNavigation } from '@/components/hooks'; import { useMessages, useNavigation } from '@/components/hooks';
import { WebsiteHeader } from '../WebsiteHeader';
import { ReportsDataTable } from '@/app/(main)/reports/ReportsDataTable'; import { ReportsDataTable } from '@/app/(main)/reports/ReportsDataTable';
export function WebsiteReportsPage({ websiteId }) { export function WebsiteReportsPage({ websiteId }) {
@ -11,7 +10,6 @@ export function WebsiteReportsPage({ websiteId }) {
return ( return (
<> <>
<WebsiteHeader websiteId={websiteId} />
<Flexbox alignItems="center" justifyContent="end"> <Flexbox alignItems="center" justifyContent="end">
<Link href={renderTeamUrl('/reports/create')}> <Link href={renderTeamUrl('/reports/create')}>
<Button variant="primary"> <Button variant="primary">

View file

@ -1,7 +1,6 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState } from 'react';
import { TabList, Tab, Tabs, TabPanel, Column } from '@umami/react-zen'; import { TabList, Tab, Tabs, TabPanel, Column } from '@umami/react-zen';
import { WebsiteHeader } from '../WebsiteHeader';
import { SessionsDataTable } from './SessionsDataTable'; import { SessionsDataTable } from './SessionsDataTable';
import { SessionsMetricsBar } from './SessionsMetricsBar'; import { SessionsMetricsBar } from './SessionsMetricsBar';
import { SessionProperties } from './SessionProperties'; import { SessionProperties } from './SessionProperties';
@ -17,7 +16,6 @@ export function SessionsPage({ websiteId }) {
return ( return (
<Column gap="3"> <Column gap="3">
<WebsiteHeader websiteId={websiteId} />
<Panel> <Panel>
<SessionsMetricsBar websiteId={websiteId} /> <SessionsMetricsBar websiteId={websiteId} />
</Panel> </Panel>

View file

@ -3,7 +3,6 @@ import { Grid, Row, Column } from '@umami/react-zen';
import { Avatar } from '@/components/common/Avatar'; import { Avatar } from '@/components/common/Avatar';
import { LoadingPanel } from '@/components/common/LoadingPanel'; import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useWebsiteSessionQuery } from '@/components/hooks'; import { useWebsiteSessionQuery } from '@/components/hooks';
import { WebsiteHeader } from '@/app/(main)/websites/[websiteId]/WebsiteHeader';
import { SessionActivity } from './SessionActivity'; import { SessionActivity } from './SessionActivity';
import { SessionData } from './SessionData'; import { SessionData } from './SessionData';
import { SessionInfo } from './SessionInfo'; import { SessionInfo } from './SessionInfo';
@ -21,7 +20,6 @@ export function SessionDetailsPage({
return ( return (
<LoadingPanel {...query} loadingIcon="spinner" data={data}> <LoadingPanel {...query} loadingIcon="spinner" data={data}>
<WebsiteHeader websiteId={websiteId} />
<Grid <Grid
gap gap
columns={{ xs: '1fr', sm: '1fr', md: '1fr 1fr', lg: '1fr 2fr 1fr', xl: '1fr 2fr 1fr' }} columns={{ xs: '1fr', sm: '1fr', md: '1fr 1fr', lg: '1fr 2fr 1fr', xl: '1fr 2fr 1fr' }}

View file

@ -1,7 +1,7 @@
import { Icon, Row, Text } from '@umami/react-zen'; import { Icon, Row, Text } from '@umami/react-zen';
import { differenceInDays, isSameDay } from 'date-fns'; import { differenceInDays, isSameDay } from 'date-fns';
import { useLocale } from '@/components/hooks'; import { useLocale } from '@/components/hooks';
import { Icons } from '@/components/icons'; import { Lucide } from '@/components/icons';
import { formatDate } from '@/lib/date'; import { formatDate } from '@/lib/date';
export function DateDisplay({ startDate, endDate }) { export function DateDisplay({ startDate, endDate }) {
@ -11,7 +11,7 @@ export function DateDisplay({ startDate, endDate }) {
return ( return (
<Row gap="3" alignItems="center" wrap="nowrap"> <Row gap="3" alignItems="center" wrap="nowrap">
<Icon> <Icon>
<Icons.Calendar /> <Lucide.Calendar />
</Icon> </Icon>
<Text wrap="nowrap"> <Text wrap="nowrap">
{isSingleDate ? ( {isSingleDate ? (

View file

@ -106,6 +106,7 @@ export function DateFilter({
placeholder={formatMessage(labels.selectDate)} placeholder={formatMessage(labels.selectDate)}
onChange={handleChange} onChange={handleChange}
renderValue={renderValue} renderValue={renderValue}
popoverProps={{ style: { width: 200 } }}
> >
{options.map(({ label, value, divider }: any) => { {options.map(({ label, value, divider }: any) => {
return ( return (

View file

@ -85,8 +85,8 @@ export function WebsiteDateFilter({
/> />
{!isAllTime && compare && ( {!isAllTime && compare && (
<Row alignItems="center" gap> <Row alignItems="center" gap>
<Text>VS</Text> <Text weight="bold">VS</Text>
<Select selectedKey={compare} onSelectionChange={handleSelect} style={{ width: '200px' }}> <Select value={compare} onChange={handleSelect} popoverProps={{ style: { width: 200 } }}>
<ListItem id="prev">{formatMessage(labels.previousPeriod)}</ListItem> <ListItem id="prev">{formatMessage(labels.previousPeriod)}</ListItem>
<ListItem id="yoy">{formatMessage(labels.previousYear)}</ListItem> <ListItem id="yoy">{formatMessage(labels.previousYear)}</ListItem>
</Select> </Select>

View file

@ -26,16 +26,16 @@ export function FilterBar({ websiteId }: { websiteId: string }) {
return ( return (
<Row <Row
gap gap
backgroundColor="1" backgroundColor="3"
alignItems="center" alignItems="center"
justifyContent="space-between" justifyContent="space-between"
paddingY="3" paddingY="2"
paddingLeft="5" paddingLeft="3"
paddingRight="2" paddingRight="2"
border border
borderRadius="2" borderRadius="2"
> >
<Row alignItems="center" gap="3" wrap="wrap"> <Row alignItems="center" gap="3" wrap="wrap" paddingX="2">
<Text color="11" weight="bold"> <Text color="11" weight="bold">
{formatMessage(labels.filters)} {formatMessage(labels.filters)}
</Text> </Text>
@ -61,11 +61,9 @@ export function FilterBar({ websiteId }: { websiteId: string }) {
<Text color="11">{operatorLabels[operator]}</Text> <Text color="11">{operatorLabels[operator]}</Text>
<Text weight="bold">{paramValue}</Text> <Text weight="bold">{paramValue}</Text>
</Row> </Row>
<Button variant="quiet" size="xs" style={{ left: '5px' }}> <Icon onClick={e => handleCloseFilter(name, e)} size="xs">
<Icon onClick={e => handleCloseFilter(name, e)} size="xs"> <Icons.Close />
<Icons.Close /> </Icon>
</Icon>
</Button>
</Row> </Row>
</Row> </Row>
); );

View file

@ -6,3 +6,4 @@ declare module 'fs-extra';
declare module 'jsonwebtoken'; declare module 'jsonwebtoken';
declare module 'md5'; declare module 'md5';
declare module 'prettier'; declare module 'prettier';
declare module 'semver';

View file

@ -6,7 +6,6 @@ body {
background-color: var(--background-color); background-color: var(--background-color);
width: 100%; width: 100%;
min-height: 100vh; min-height: 100vh;
overflow: hidden;
} }
a, a,