diff --git a/package.json b/package.json
index 761ba566..5e0b988d 100644
--- a/package.json
+++ b/package.json
@@ -79,7 +79,7 @@
"@prisma/extension-read-replicas": "^0.4.1",
"@react-spring/web": "^9.7.3",
"@tanstack/react-query": "^5.74.11",
- "@umami/react-zen": "^0.89.0",
+ "@umami/react-zen": "^0.90.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 79487c73..78de93d3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -39,8 +39,8 @@ importers:
specifier: ^5.74.11
version: 5.74.11(react@19.1.0)
'@umami/react-zen':
- specifier: ^0.89.0
- version: 0.89.0(@babel/core@7.26.10)(@types/react@19.1.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))
+ specifier: ^0.90.0
+ version: 0.90.0(@babel/core@7.26.10)(@types/react@19.1.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
@@ -2992,8 +2992,8 @@ packages:
resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==}
engines: {node: ^16.0.0 || >=18.0.0}
- '@umami/react-zen@0.89.0':
- resolution: {integrity: sha512-Lcvgh6Y4DKlUUDE84WowvxvJkgI4INW6lVM32L8+XUJVxBrEBa41RF7jF6KTgD6IizAwHtSouh4gVLzzBDmlCw==}
+ '@umami/react-zen@0.90.0':
+ resolution: {integrity: sha512-Hj0/GSQPUtiRwq1ri3nX+anWp5udNQmrKZcHOH/j1B3z4KL/AW+llYyXqP9loG1N+NgEzW66P791Pac8Wo7qpw==}
'@umami/redis-client@0.27.0':
resolution: {integrity: sha512-SbHTpxhgeZyTBUSp2zdZM+XUtpsaSL4Tad8QXIEhEtjWhvvfoornyT5kLuyYCVtzSAT4daALeGmOO1z6EE1KcA==}
@@ -10784,7 +10784,7 @@ snapshots:
'@typescript-eslint/types': 6.21.0
eslint-visitor-keys: 3.4.3
- '@umami/react-zen@0.89.0(@babel/core@7.26.10)(@types/react@19.1.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))':
+ '@umami/react-zen@0.90.0(@babel/core@7.26.10)(@types/react@19.1.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))':
dependencies:
'@fontsource/jetbrains-mono': 5.2.5
'@internationalized/date': 3.8.0
diff --git a/src/app/(main)/MenuBar.tsx b/src/app/(main)/MenuBar.tsx
index fd6486d1..f3fbe3f1 100644
--- a/src/app/(main)/MenuBar.tsx
+++ b/src/app/(main)/MenuBar.tsx
@@ -5,6 +5,7 @@ import { TeamsButton } from '@/components/input/TeamsButton';
import type { RowProps } from '@umami/react-zen/Row';
import useGlobalState from '@/components/hooks/useGlobalState';
import { Lucide } from '@/components/icons';
+import { WebsiteSelect } from '@/components/input/WebsiteSelect';
export function MenuBar(props: RowProps) {
const [isCollapsed, setCollapsed] = useGlobalState('sidenav-collapsed');
@@ -20,15 +21,19 @@ export function MenuBar(props: RowProps) {
backgroundColor="2"
border="bottom"
>
-
+
+
+
+
+
-
+
diff --git a/src/app/(main)/settings/profile/ProfilePage.tsx b/src/app/(main)/settings/profile/ProfilePage.tsx
index 46c21b0c..aaec3f58 100644
--- a/src/app/(main)/settings/profile/ProfilePage.tsx
+++ b/src/app/(main)/settings/profile/ProfilePage.tsx
@@ -2,15 +2,15 @@
import { ProfileSettings } from './ProfileSettings';
import { useMessages } from '@/components/hooks';
import { SectionHeader } from '@/components/common/SectionHeader';
+import { Column } from '@umami/react-zen';
export function ProfilePage() {
const { formatMessage, labels } = useMessages();
return (
- <>
+
-
- >
+
);
}
diff --git a/src/app/(main)/teams/[teamId]/settings/TeamSettingsLayout.tsx b/src/app/(main)/teams/[teamId]/settings/TeamSettingsLayout.tsx
index 07250a92..79266a23 100644
--- a/src/app/(main)/teams/[teamId]/settings/TeamSettingsLayout.tsx
+++ b/src/app/(main)/teams/[teamId]/settings/TeamSettingsLayout.tsx
@@ -28,12 +28,11 @@ export function TeamSettingsLayout({ children }: { children: ReactNode }) {
},
].filter(n => n);
- const value = items.find(({ url }) => pathname.endsWith(url))?.id;
+ const value = items.find(({ url }) => pathname.includes(url))?.id;
return (
-
diff --git a/src/app/share/[...shareId]/Footer.module.css b/src/app/share/[...shareId]/Footer.module.css
deleted file mode 100644
index 5dc2d584..00000000
--- a/src/app/share/[...shareId]/Footer.module.css
+++ /dev/null
@@ -1,12 +0,0 @@
-.footer {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: flex-end;
- font-size: var(--font-size-sm);
- height: 100px;
-}
-
-.footer a {
- color: var(--font-color100);
-}
diff --git a/src/app/share/[...shareId]/Footer.tsx b/src/app/share/[...shareId]/Footer.tsx
index 495b0e3d..4683e982 100644
--- a/src/app/share/[...shareId]/Footer.tsx
+++ b/src/app/share/[...shareId]/Footer.tsx
@@ -1,12 +1,12 @@
+import { Row, Text } from '@umami/react-zen';
import { CURRENT_VERSION, HOMEPAGE_URL } from '@/lib/constants';
-import styles from './Footer.module.css';
export function Footer() {
return (
-
+
);
}
diff --git a/src/app/share/[...shareId]/Header.module.css b/src/app/share/[...shareId]/Header.module.css
deleted file mode 100644
index 9fc946c7..00000000
--- a/src/app/share/[...shareId]/Header.module.css
+++ /dev/null
@@ -1,31 +0,0 @@
-.header {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- width: 100%;
- height: 100px;
-}
-
-.title {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 10px;
- font-size: var(--font-size-lg);
- font-weight: 700;
- color: var(--font-color100) !important;
-}
-
-.buttons {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: flex-end;
-}
-
-@media only screen and (max-width: 992px) {
- .header .buttons {
- flex: 1;
- }
-}
diff --git a/src/app/share/[...shareId]/Header.tsx b/src/app/share/[...shareId]/Header.tsx
index c7a87347..2c0d90f6 100644
--- a/src/app/share/[...shareId]/Header.tsx
+++ b/src/app/share/[...shareId]/Header.tsx
@@ -1,27 +1,25 @@
-import { ThemeButton } from '@umami/react-zen';
-import { Icon, Text } from '@umami/react-zen';
+import { Row, Icon, Text, ThemeButton } from '@umami/react-zen';
import Link from 'next/link';
import { LanguageButton } from '@/components/input/LanguageButton';
import { SettingsButton } from '@/components/input/SettingsButton';
import { Icons } from '@/components/icons';
-import styles from './Header.module.css';
export function Header() {
return (
-
-
-
+
+
+
umami
-
-
+
+
-
-
+
+
);
}
diff --git a/src/app/share/[...shareId]/SharePage.module.css b/src/app/share/[...shareId]/SharePage.module.css
deleted file mode 100644
index f6c68cf6..00000000
--- a/src/app/share/[...shareId]/SharePage.module.css
+++ /dev/null
@@ -1,5 +0,0 @@
-.container {
- flex: 1;
- min-height: calc(100vh - 200px);
- min-height: calc(100dvh - 200px);
-}
diff --git a/src/app/share/[...shareId]/SharePage.tsx b/src/app/share/[...shareId]/SharePage.tsx
index 3e0d2c87..6d283c88 100644
--- a/src/app/share/[...shareId]/SharePage.tsx
+++ b/src/app/share/[...shareId]/SharePage.tsx
@@ -1,11 +1,10 @@
'use client';
-import { WebsiteDetailsPage } from '../../(main)/websites/[websiteId]/WebsiteDetailsPage';
+import { WebsiteProvider } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
+import { WebsiteDetailsPage } from '@/app/(main)/websites/[websiteId]/WebsiteDetailsPage';
import { useShareTokenQuery } from '@/components/hooks';
import { Page } from '@/components/common/Page';
import { Header } from './Header';
import { Footer } from './Footer';
-import styles from './SharePage.module.css';
-import { WebsiteProvider } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
export function SharePage({ shareId }) {
const { shareToken, isLoading } = useShareTokenQuery(shareId);
@@ -15,14 +14,12 @@ export function SharePage({ shareId }) {
}
return (
-
+
+
+
+
+
+
+
);
}
diff --git a/src/assets/security.svg b/src/assets/security.svg
new file mode 100644
index 00000000..dd20891c
--- /dev/null
+++ b/src/assets/security.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/charts/BarChart.tsx b/src/components/charts/BarChart.tsx
index eda4fbc2..e5502ae7 100644
--- a/src/components/charts/BarChart.tsx
+++ b/src/components/charts/BarChart.tsx
@@ -1,9 +1,24 @@
import { useMemo, useState } from 'react';
import { useTheme } from '@umami/react-zen';
-import { BarChartTooltip } from '@/components/charts/BarChartTooltip';
+import { ChartTooltip } from '@/components/charts/ChartTooltip';
import { Chart, ChartProps } from '@/components/charts/Chart';
+import { useLocale } from '@/components/hooks';
import { renderNumberLabels } from '@/lib/charts';
import { getThemeColors } from '@/lib/colors';
+import { formatDate } from '@/lib/date';
+import { formatLongCurrency, formatLongNumber } from '@/lib/format';
+
+const dateFormats = {
+ millisecond: 'T',
+ second: 'pp',
+ minute: 'p',
+ hour: 'p - PP',
+ day: 'PPPP',
+ week: 'PPPP',
+ month: 'LLLL yyyy',
+ quarter: 'qqq',
+ year: 'yyyy',
+};
export interface BarChartProps extends ChartProps {
unit: string;
@@ -18,22 +33,23 @@ export interface BarChartProps extends ChartProps {
isAllTime?: boolean;
}
-export function BarChart(props: BarChartProps) {
+export function BarChart({
+ renderXLabel,
+ renderYLabel,
+ unit,
+ XAxisType = 'time',
+ YAxisType = 'linear',
+ stacked = false,
+ minDate,
+ maxDate,
+ currency,
+ isAllTime,
+ ...props
+}: BarChartProps) {
const [tooltip, setTooltip] = useState(null);
const { theme } = useTheme();
+ const { locale } = useLocale();
const { colors } = getThemeColors(theme);
- const {
- renderXLabel,
- renderYLabel,
- unit,
- XAxisType = 'time',
- YAxisType = 'linear',
- stacked = false,
- minDate,
- maxDate,
- currency,
- isAllTime,
- } = props;
const options: any = useMemo(() => {
return {
@@ -80,9 +96,23 @@ export function BarChart(props: BarChartProps) {
}, [colors, unit, stacked, renderXLabel, renderYLabel]);
const handleTooltip = ({ tooltip }: { tooltip: any }) => {
- const { opacity } = tooltip;
+ const { opacity, labelColors, dataPoints } = tooltip;
- setTooltip(opacity ? tooltip : null);
+ if (opacity) {
+ setTooltip({
+ title: formatDate(
+ new Date(dataPoints[0].raw?.d || dataPoints[0].raw?.x || dataPoints[0].raw),
+ dateFormats[unit],
+ locale,
+ ),
+ color: labelColors?.[0]?.backgroundColor,
+ value: currency
+ ? formatLongCurrency(dataPoints[0].raw.y, currency)
+ : `${formatLongNumber(dataPoints[0].raw.y)} ${dataPoints[0].dataset.label}`,
+ });
+ } else {
+ setTooltip(null);
+ }
};
return (
@@ -92,9 +122,9 @@ export function BarChart(props: BarChartProps) {
type="bar"
chartOptions={options}
onTooltip={handleTooltip}
- style={{ height: 400 }}
+ height="400px"
/>
- {tooltip && }
+ {tooltip && }
>
);
}
diff --git a/src/components/charts/BarChartTooltip.tsx b/src/components/charts/BarChartTooltip.tsx
deleted file mode 100644
index 49aa7e0b..00000000
--- a/src/components/charts/BarChartTooltip.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { useLocale } from '@/components/hooks';
-import { formatDate } from '@/lib/date';
-import { formatLongCurrency, formatLongNumber } from '@/lib/format';
-import { Column, Row, StatusLight, FloatingTooltip } from '@umami/react-zen';
-
-const formats = {
- millisecond: 'T',
- second: 'pp',
- minute: 'p',
- hour: 'p - PP',
- day: 'PPPP',
- week: 'PPPP',
- month: 'LLLL yyyy',
- quarter: 'qqq',
- year: 'yyyy',
-};
-
-export function BarChartTooltip({ tooltip, unit, currency }) {
- const { locale } = useLocale();
- const { labelColors, dataPoints } = tooltip;
-
- return (
-
-
-
- {formatDate(new Date(dataPoints[0].raw.d || dataPoints[0].raw.x), formats[unit], locale)}
-
-
-
- {currency
- ? formatLongCurrency(dataPoints[0].raw.y, currency)
- : `${formatLongNumber(dataPoints[0].raw.y)} ${dataPoints[0].dataset.label}`}
-
-
-
-
- );
-}
diff --git a/src/components/charts/BubbleChart.tsx b/src/components/charts/BubbleChart.tsx
index 9d1c367e..91c47b59 100644
--- a/src/components/charts/BubbleChart.tsx
+++ b/src/components/charts/BubbleChart.tsx
@@ -1,27 +1,31 @@
import { Chart, ChartProps } from '@/components/charts/Chart';
import { useState } from 'react';
-import { StatusLight } from '@umami/react-zen';
-import { formatLongNumber } from '@/lib/format';
+import { ChartTooltip } from '@/components/charts/ChartTooltip';
export interface BubbleChartProps extends ChartProps {
type?: 'bubble';
}
-export function BubbleChart(props: BubbleChartProps) {
+export function BubbleChart({ type = 'bubble', ...props }: BubbleChartProps) {
const [tooltip, setTooltip] = useState(null);
- const { type = 'bubble' } = props;
const handleTooltip = ({ tooltip }) => {
- const { labelColors, dataPoints } = tooltip;
+ const { opacity, labelColors, title, dataPoints } = tooltip;
setTooltip(
- tooltip.opacity ? (
-
- {formatLongNumber(dataPoints?.[0]?.raw)} {dataPoints?.[0]?.label}
-
- ) : null,
+ opacity
+ ? {
+ color: labelColors?.[0]?.backgroundColor,
+ value: `${title}: ${dataPoints[0].raw}`,
+ }
+ : null,
);
};
- return ;
+ return (
+ <>
+
+ {tooltip && }
+ >
+ );
}
diff --git a/src/components/charts/Chart.tsx b/src/components/charts/Chart.tsx
index 5cf621b9..e9e834cc 100644
--- a/src/components/charts/Chart.tsx
+++ b/src/components/charts/Chart.tsx
@@ -1,10 +1,11 @@
-import { useState, useRef, useEffect, useMemo, HTMLAttributes } from 'react';
-import { Loading } from '@umami/react-zen';
+import { useState, useRef, useEffect, useMemo } from 'react';
+import { Loading, Box, Column } from '@umami/react-zen';
import ChartJS, { LegendItem, ChartOptions } from 'chart.js/auto';
import { Legend } from '@/components/metrics/Legend';
import { DEFAULT_ANIMATION_DURATION } from '@/lib/constants';
+import type { BoxProps } from '@umami/react-zen/Box';
-export interface ChartProps extends HTMLAttributes {
+export interface ChartProps extends BoxProps {
type?: 'bar' | 'bubble' | 'doughnut' | 'pie' | 'line' | 'polarArea' | 'radar' | 'scatter';
data?: object;
isLoading?: boolean;
@@ -138,12 +139,12 @@ export function Chart({
}, [data, options]);
return (
- <>
-
+
+
{isLoading && }
-
+
- >
+
);
}
diff --git a/src/components/charts/ChartTooltip.tsx b/src/components/charts/ChartTooltip.tsx
new file mode 100644
index 00000000..e93d669e
--- /dev/null
+++ b/src/components/charts/ChartTooltip.tsx
@@ -0,0 +1,23 @@
+import { ReactNode } from 'react';
+import { Column, Row, StatusLight, FloatingTooltip } from '@umami/react-zen';
+
+export function ChartTooltip({
+ title,
+ color,
+ value,
+}: {
+ title?: string;
+ color?: string;
+ value?: ReactNode;
+}) {
+ return (
+
+
+ {title && {title}
}
+
+ {value}
+
+
+
+ );
+}
diff --git a/src/components/charts/PieChart.tsx b/src/components/charts/PieChart.tsx
index 738c1ec3..f9a9ea52 100644
--- a/src/components/charts/PieChart.tsx
+++ b/src/components/charts/PieChart.tsx
@@ -1,27 +1,31 @@
import { Chart, ChartProps } from '@/components/charts/Chart';
import { useState } from 'react';
-import { StatusLight } from '@umami/react-zen';
-import { formatLongNumber } from '@/lib/format';
+import { ChartTooltip } from '@/components/charts/ChartTooltip';
export interface PieChartProps extends ChartProps {
type?: 'doughnut' | 'pie';
}
-export function PieChart(props: PieChartProps) {
+export function PieChart({ type = 'pie', ...props }: PieChartProps) {
const [tooltip, setTooltip] = useState(null);
- const { type = 'pie' } = props;
const handleTooltip = ({ tooltip }) => {
- const { labelColors, dataPoints } = tooltip;
+ const { opacity, labelColors, title, dataPoints } = tooltip;
setTooltip(
- tooltip.opacity ? (
-
- {formatLongNumber(dataPoints?.[0]?.raw)} {dataPoints?.[0]?.label}
-
- ) : null,
+ opacity
+ ? {
+ color: labelColors?.[0]?.backgroundColor,
+ value: `${title}: ${dataPoints[0].raw}`,
+ }
+ : null,
);
};
- return ;
+ return (
+ <>
+
+ {tooltip && }
+ >
+ );
}
diff --git a/src/components/common/SideMenu.tsx b/src/components/common/SideMenu.tsx
index 6137bba6..af997b64 100644
--- a/src/components/common/SideMenu.tsx
+++ b/src/components/common/SideMenu.tsx
@@ -1,11 +1,11 @@
import { Text, List, ListItem } from '@umami/react-zen';
-export interface MenuNavProps {
+export interface SideMenuProps {
items: { id: string; label: string; url: string }[];
selectedKey?: string;
}
-export function SideMenu({ items, selectedKey }: MenuNavProps) {
+export function SideMenu({ items, selectedKey }: SideMenuProps) {
return (
{items.map(({ id, label, url }) => {
diff --git a/src/components/input/DateFilter.tsx b/src/components/input/DateFilter.tsx
index 499524cf..d316d677 100644
--- a/src/components/input/DateFilter.tsx
+++ b/src/components/input/DateFilter.tsx
@@ -22,6 +22,7 @@ export function DateFilter({
value,
onChange,
showAllTime = false,
+ ...props
}: DateFilterProps) {
const { formatMessage, labels } = useMessages();
const [showPicker, setShowPicker] = useState(false);
@@ -102,6 +103,7 @@ export function DateFilter({
return (
<>