diff --git a/next.config.ts b/next.config.ts index a4ba1c36..86aaae95 100644 --- a/next.config.ts +++ b/next.config.ts @@ -163,12 +163,6 @@ if (trackerScriptName) { } if (cloudMode && cloudUrl) { - redirects.push({ - source: '/settings/:path*', - destination: `${cloudUrl}/settings/:path*`, - permanent: false, - }); - redirects.push({ source: '/login', destination: cloudUrl, diff --git a/package.components.json b/package.components.json index 04eca82e..048673e8 100644 --- a/package.components.json +++ b/package.components.json @@ -1,6 +1,6 @@ { "name": "@umami/components", - "version": "0.111.0", + "version": "0.115.0", "description": "Umami React components.", "author": "Mike Cao ", "license": "MIT", diff --git a/package.json b/package.json index f3d02c01..e6fe20f4 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "@react-spring/web": "^10.0.1", "@svgr/cli": "^8.1.0", "@tanstack/react-query": "^5.85.5", - "@umami/react-zen": "^0.175.0", + "@umami/react-zen": "^0.176.0", "@umami/redis-client": "^0.29.0", "bcryptjs": "^3.0.2", "chalk": "^5.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f3c2182f..3f5ea941 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: ^5.85.5 version: 5.85.5(react@19.1.1) '@umami/react-zen': - specifier: ^0.175.0 - version: 0.175.0(@babel/core@7.28.3)(@types/react@19.1.12)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.1)(use-sync-external-store@1.5.0(react@19.1.1)) + specifier: ^0.176.0 + version: 0.176.0(@babel/core@7.28.3)(@types/react@19.1.12)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.1)(use-sync-external-store@1.5.0(react@19.1.1)) '@umami/redis-client': specifier: ^0.29.0 version: 0.29.0 @@ -2735,8 +2735,8 @@ packages: '@prisma/client': ^6.1.0 '@prisma/extension-read-replicas': ^0.4.1 - '@umami/react-zen@0.175.0': - resolution: {integrity: sha512-iOUCZwmr09RnqIm01wnjcSTTJ5iJdXRmFlmja2Qf42di/SOOTSBJmu1fKiYESp4dLrXXcmMeDfDqtJli0PPSRw==} + '@umami/react-zen@0.176.0': + resolution: {integrity: sha512-GP+Df68w0Kfo09ZC5WgK1YCg/IbqOz7HWPw8PLYG3shYm+feuF8ND8DHjoTle4qXY4oRJFufXCEsDbTb8FITkg==} '@umami/redis-client@0.29.0': resolution: {integrity: sha512-Jaqh++jskqDB7ny75pfC02OvKp1JTS4asGDsFrRL3qy8sxL3PAl9+/mybCJe4/6vWrXDJKqpgkSfUDJq2bFjyw==} @@ -10333,7 +10333,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@umami/react-zen@0.175.0(@babel/core@7.28.3)(@types/react@19.1.12)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.1)(use-sync-external-store@1.5.0(react@19.1.1))': + '@umami/react-zen@0.176.0(@babel/core@7.28.3)(@types/react@19.1.12)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.1)(use-sync-external-store@1.5.0(react@19.1.1))': dependencies: '@fontsource/jetbrains-mono': 5.2.6 '@internationalized/date': 3.9.0 diff --git a/src/app/(main)/SideNav.tsx b/src/app/(main)/SideNav.tsx index a28dce81..a65640e1 100644 --- a/src/app/(main)/SideNav.tsx +++ b/src/app/(main)/SideNav.tsx @@ -22,10 +22,11 @@ import { TeamsButton } from '@/components/input/TeamsButton'; import { PanelButton } from '@/components/input/PanelButton'; import { ProfileButton } from '@/components/input/ProfileButton'; import { LanguageButton } from '@/components/input/LanguageButton'; +import { Key } from 'react'; export function SideNav(props: SidebarProps) { const { formatMessage, labels } = useMessages(); - const { pathname, renderUrl, websiteId } = useNavigation(); + const { pathname, renderUrl, websiteId, router } = useNavigation(); const [isCollapsed, setIsCollapsed] = useGlobalState('sidenav-collapsed'); const hasNav = !!(websiteId || pathname.startsWith('/admin') || pathname.includes('/settings')); @@ -66,21 +67,29 @@ export function SideNav(props: SidebarProps) { }, ]; + const handleSelect = (id: Key) => { + console.log({ id }); + router.push(id === 'user' ? '/websites' : `/teams/${id}/websites`); + }; + return ( setIsCollapsed(false)}> : } + icon={ + isCollapsed && !hasNav ? ( + + ) : ( + (window.location.href = process.env.cloudUrl)} /> + ) + } style={{ maxHeight: 40 }} > {!isCollapsed && !hasNav && } - - - {links.map(({ id, path, label, icon }) => { return ( @@ -95,6 +104,9 @@ export function SideNav(props: SidebarProps) { ); })} + + + {bottomLinks.map(({ id, path, label, icon }) => { return ( @@ -108,7 +120,7 @@ export function SideNav(props: SidebarProps) { ); })} - + {!isCollapsed && !hasNav && ( diff --git a/src/app/(main)/settings/SettingsLayout.tsx b/src/app/(main)/settings/SettingsLayout.tsx index 2d9ceac0..e7a7a2f9 100644 --- a/src/app/(main)/settings/SettingsLayout.tsx +++ b/src/app/(main)/settings/SettingsLayout.tsx @@ -53,6 +53,7 @@ export function SettingsLayout({ children }: { children: ReactNode }) { title={formatMessage(labels.settings)} selectedKey={selectedKey} allowMinimize={false} + muteItems={false} /> diff --git a/src/components/common/SideMenu.tsx b/src/components/common/SideMenu.tsx index 3081a317..5135cd7a 100644 --- a/src/components/common/SideMenu.tsx +++ b/src/components/common/SideMenu.tsx @@ -12,8 +12,20 @@ import { } from '@umami/react-zen'; import Link from 'next/link'; +interface SideMenuData { + id: string; + label: string; + icon?: any; + path: string; +} + +interface SideMenuItems { + label?: string; + items: SideMenuData[]; +} + export interface SideMenuProps extends NavMenuProps { - items: { label: string; items: { id: string; label: string; icon?: any; path: string }[] }[]; + items: SideMenuItems[]; title?: string; selectedKey?: string; allowMinimize?: boolean; @@ -28,6 +40,23 @@ export function SideMenu({ children, ...props }: SideMenuProps) { + const renderItems = (items: SideMenuData[]) => { + return items?.map(({ id, label, icon, path }) => { + const isSelected = selectedKey === id; + + return ( + + + + {icon} + {label} + + + + ); + }); + }; + return ( {items?.map(({ label, items }, index) => { - return ( - - {items?.map(({ id, label, icon, path }) => { - const isSelected = selectedKey === id; - - return ( - - - - {icon} - {label} - - - - ); - })} - - ); + if (label) { + return ( + + {renderItems(items)} + + ); + } })} diff --git a/src/components/input/TeamsButton.tsx b/src/components/input/TeamsButton.tsx index 8b7977dd..5d40142e 100644 --- a/src/components/input/TeamsButton.tsx +++ b/src/components/input/TeamsButton.tsx @@ -11,25 +11,29 @@ import { Popover, Row, Box, - SidebarItem, - Pressable, + Button, + Loading, } from '@umami/react-zen'; import { useLoginQuery, useMessages, useUserTeamsQuery, useNavigation } from '@/components/hooks'; import { Chevron, User, Users } from '@/components/icons'; -export function TeamsButton({ showText = true }: { showText?: boolean }) { +export interface TeamsButtonProps { + showText?: boolean; + onAction?: (id: any) => void; +} + +export function TeamsButton({ showText = true, onAction }: TeamsButtonProps) { const { user } = useLoginQuery(); const { formatMessage, labels } = useMessages(); - const { data } = useUserTeamsQuery(user.id); + const { data, isLoading } = useUserTeamsQuery(user.id); const { teamId } = useNavigation(); - const router = useRouter(); const team = data?.data?.find(({ id }) => id === teamId); - const selectedKeys = new Set([teamId || user.id]); + const selectedKeys = new Set([teamId || 'user']); const label = teamId ? team?.name : user.username; - const handleSelect = (id: Key) => { - router.push(id === user.id ? '/websites' : `/teams/${id}/websites`); - }; + if (isLoading) { + return ; + } if (!data?.count) { return null; @@ -37,27 +41,29 @@ export function TeamsButton({ showText = true }: { showText?: boolean }) { return ( - - - : }> - {showText && ( - - - - )} - + - + diff --git a/src/components/messages.ts b/src/components/messages.ts index 30a65cfe..ebd8c765 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -359,6 +359,7 @@ export const labels = defineMessages({ invalidUrl: { id: 'label.invalid-url', defaultMessage: 'Invalid URL' }, environment: { id: 'label.environment', defaultMessage: 'Environment' }, criteria: { id: 'label.criteria', defaultMessage: 'Criteria' }, + share: { defaultMessage: 'label.share', id: 'Share' }, }); export const messages = defineMessages({ diff --git a/src/components/metrics/ChangeLabel.module.css b/src/components/metrics/ChangeLabel.module.css deleted file mode 100644 index b3bc7433..00000000 --- a/src/components/metrics/ChangeLabel.module.css +++ /dev/null @@ -1,25 +0,0 @@ -.label { - display: flex; - align-items: center; - gap: 5px; - font-size: var(--font-size); - padding: 0.1em 0.5em; - border-radius: 5px; - color: var(--base500); - align-self: flex-start; -} - -.positive { - color: var(--success-color); - background: color-mix(in srgb, var(--success-color), var(--background-color) 95%); -} - -.negative { - color: var(--danger-color); - background: color-mix(in srgb, var(--danger-color), var(--background-color) 95%); -} - -.neutral { - color: var(--font-color-muted); - background: var(--base-color-2); -} diff --git a/src/components/metrics/ChangeLabel.tsx b/src/components/metrics/ChangeLabel.tsx index 5bccdaf2..8af4a284 100644 --- a/src/components/metrics/ChangeLabel.tsx +++ b/src/components/metrics/ChangeLabel.tsx @@ -1,14 +1,26 @@ -import classNames from 'classnames'; -import { Icon, Text } from '@umami/react-zen'; -import { HTMLAttributes, ReactNode } from 'react'; +import { Icon, Text, Row, RowProps } from '@umami/react-zen'; +import { ReactNode } from 'react'; import { Arrow } from '@/components/icons'; -import styles from './ChangeLabel.module.css'; + +const STYLES = { + positive: { + color: `var(--success-color)`, + background: `color-mix(in srgb, var(--success-color), var(--background-color) 95%)`, + }, + negative: { + color: `var(--danger-color)`, + background: `color-mix(in srgb, var(--danger-color), var(--background-color) 95%)`, + }, + neutral: { + color: `var(--font-color-muted)`, + background: `var(--base-color-2)`, + }, +}; export function ChangeLabel({ value, size, reverseColors, - className, children, ...props }: { @@ -17,29 +29,24 @@ export function ChangeLabel({ title?: string; reverseColors?: boolean; showPercentage?: boolean; - className?: string; children?: ReactNode; -} & HTMLAttributes) { +} & RowProps) { const positive = value >= 0; const negative = value < 0; const neutral = value === 0 || isNaN(value); const good = reverseColors ? negative : positive; + const style = + STYLES[good && 'positive'] || STYLES[!good && 'negative'] || STYLES[neutral && 'neutral']; + return ( -
+ {!neutral && ( )} {children || value} -
+
); } diff --git a/src/components/metrics/EventsChart.module.css b/src/components/metrics/EventsChart.module.css deleted file mode 100644 index d586bead..00000000 --- a/src/components/metrics/EventsChart.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.chart { - display: flex; -} diff --git a/src/components/metrics/WorldMap.module.css b/src/components/metrics/WorldMap.module.css deleted file mode 100644 index c2528038..00000000 --- a/src/components/metrics/WorldMap.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.container { - overflow: hidden; - position: relative; -} diff --git a/src/components/metrics/WorldMap.tsx b/src/components/metrics/WorldMap.tsx index 5cc2d764..833a4a92 100644 --- a/src/components/metrics/WorldMap.tsx +++ b/src/components/metrics/WorldMap.tsx @@ -1,7 +1,6 @@ import { FloatingTooltip, Column, useTheme, ColumnProps } from '@umami/react-zen'; import { useState, useMemo } from 'react'; import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps'; -import classNames from 'classnames'; import { colord } from 'colord'; import { ISO_COUNTRIES, MAP_FILE } from '@/lib/constants'; import { @@ -12,7 +11,6 @@ import { } from '@/components/hooks'; import { formatLongNumber } from '@/lib/format'; import { percentFilter } from '@/lib/filters'; -import styles from './WorldMap.module.css'; import { getThemeColors } from '@/lib/colors'; export interface WorldMapProps extends ColumnProps { @@ -20,7 +18,7 @@ export interface WorldMapProps extends ColumnProps { data?: any[]; } -export function WorldMap({ websiteId, data, className, ...props }: WorldMapProps) { +export function WorldMap({ websiteId, data, ...props }: WorldMapProps) { const [tooltip, setTooltipPopup] = useState(); const { theme } = useTheme(); const { colors } = getThemeColors(theme); @@ -67,12 +65,7 @@ export function WorldMap({ websiteId, data, className, ...props }: WorldMapProps }; return ( - + diff --git a/src/index.ts b/src/index.ts index 5ef1fe13..f72bece0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,3 @@ -export * from '@/components/hooks'; - export * from '@/app/(main)/teams/[teamId]/TeamMemberEditButton'; export * from '@/app/(main)/teams/[teamId]/TeamMemberEditForm'; export * from '@/app/(main)/teams/[teamId]/TeamMemberRemoveButton'; @@ -37,9 +35,14 @@ export * from '@/app/(main)/websites/WebsiteAddForm'; export * from '@/app/(main)/websites/WebsitesDataTable'; export * from '@/app/(main)/websites/WebsitesHeader'; export * from '@/app/(main)/websites/WebsitesTable'; - export * from '@/app/(main)/websites/WebsiteProvider'; +export * from '@/components/charts/BarChart'; +export * from '@/components/charts/BubbleChart'; +export * from '@/components/charts/Chart'; +export * from '@/components/charts/ChartTooltip'; +export * from '@/components/charts/PieChart'; + export * from '@/components/common/ActionForm'; export * from '@/components/common/ConfirmationForm'; export * from '@/components/common/DataGrid'; @@ -52,6 +55,7 @@ export * from '@/components/common/ErrorMessage'; export * from '@/components/common/ExternalLink'; export * from '@/components/common/Favicon'; export * from '@/components/common/LinkButton'; +export * from '@/components/common/LoadingPanel'; export * from '@/components/common/PageBody'; export * from '@/components/common/PageHeader'; export * from '@/components/common/Pager'; @@ -62,3 +66,13 @@ export * from '@/components/common/TypeConfirmationForm'; export * from '@/components/input/FilterButtons'; export * from '@/components/input/TeamsButton'; +export * from '@/components/input/ProfileButton'; +export * from '@/components/input/WebsiteSelect'; + +export * from '@/components/metrics/ChangeLabel'; +export * from '@/components/metrics/ListTable'; +export * from '@/components/metrics/MetricCard'; +export * from '@/components/metrics/MetricLabel'; +export * from '@/components/metrics/MetricsBar'; + +export * from '@/components/hooks'; diff --git a/tsup.config.js b/tsup.config.js index 73a5dfea..41db7d4a 100644 --- a/tsup.config.js +++ b/tsup.config.js @@ -7,7 +7,7 @@ export default defineConfig({ dts: true, splitting: false, sourcemap: false, - clean: true, + clean: false, external: ['react', 'react-dom', 'react/jsx-runtime', '@swc/helpers'], esbuildOptions(options) { options.jsx = 'automatic';