Lint format files. Updated rng function.

This commit is contained in:
Mike Cao
2026-06-02 22:35:07 -07:00
parent 5bf1792b38
commit 306ca4a435
144 changed files with 962 additions and 626 deletions
-1
View File
@@ -102,7 +102,6 @@
"playwright-core": "^1.60.0",
"prisma": "^7.8.0",
"prop-types": "^15.8.1",
"pure-rand": "^8.4.0",
"react": "^19.2.6",
"react-aria-components": "^1.17.0",
"react-dom": "^19.2.6",
+5 -1
View File
@@ -68,7 +68,11 @@ async function checkDatabaseVersion() {
async function applyMigration() {
if (!process.env.SKIP_DB_MIGRATION) {
const directUrl = process.env.DIRECT_DATABASE_URL || process.env.DATABASE_URL;
console.log(execSync('prisma migrate deploy', { env: { ...process.env, DATABASE_URL: directUrl } }).toString());
console.log(
execSync('prisma migrate deploy', {
env: { ...process.env, DATABASE_URL: directUrl },
}).toString(),
);
success('Database is up to date.');
}
+1 -1
View File
@@ -1,9 +1,9 @@
/* eslint-disable no-console */
import fs from 'node:fs';
import https from 'node:https';
import path from 'node:path';
import chalk from 'chalk';
import fs from 'node:fs';
const src = path.resolve(process.cwd(), 'public/intl/messages');
const dest = path.resolve(process.cwd(), 'public/intl/country');
+1 -1
View File
@@ -1,9 +1,9 @@
/* eslint-disable no-console */
import fs from 'node:fs';
import https from 'node:https';
import path from 'node:path';
import chalk from 'chalk';
import fs from 'node:fs';
const src = path.resolve(process.cwd(), 'public/intl/messages');
const dest = path.resolve(process.cwd(), 'public/intl/language');
+1 -1
View File
@@ -49,7 +49,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ slug
payload: {
pixel: pixel.id,
url: request.url,
referrer: request.headers.get("referer") || undefined,
referrer: request.headers.get('referer') || undefined,
},
};
+1 -1
View File
@@ -47,7 +47,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ slug
payload: {
link: link.id,
url: request.url,
referrer: request.headers.get("referer") || undefined,
referrer: request.headers.get('referer') || undefined,
},
};
+1 -1
View File
@@ -1,7 +1,7 @@
import { Column, Grid, Row, Text } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { WebsiteNav } from '@/app/(main)/websites/[websiteId]/WebsiteNav';
import { IconLabel } from '@/components/common/IconLabel';
import Link from '@/components/common/Link';
import { useMessages, useNavigation } from '@/components/hooks';
import { Globe, Grid2x2, LayoutDashboard, LinkIcon } from '@/components/icons';
import { MobileMenuButton } from '@/components/input/MobileMenuButton';
+15 -15
View File
@@ -1,18 +1,3 @@
import { AdminNav } from '@/app/(main)/admin/AdminNav';
import { SettingsNav } from '@/app/(main)/settings/SettingsNav';
import { WebsiteNav } from '@/app/(main)/websites/[websiteId]/WebsiteNav';
import { IconLabel } from '@/components/common/IconLabel';
import { useGlobalState, useMessages, useNavigation } from '@/components/hooks';
import {
Globe,
Grid2x2,
LayoutDashboard,
LinkIcon,
PanelLeft,
PanelsLeftBottom,
} from '@/components/icons';
import { UserButton } from '@/components/input/UserButton';
import { Logo } from '@/components/svg';
import {
Button,
type ButtonProps,
@@ -24,7 +9,22 @@ import {
Tooltip,
TooltipTrigger,
} from '@umami/react-zen';
import { AdminNav } from '@/app/(main)/admin/AdminNav';
import { SettingsNav } from '@/app/(main)/settings/SettingsNav';
import { WebsiteNav } from '@/app/(main)/websites/[websiteId]/WebsiteNav';
import { IconLabel } from '@/components/common/IconLabel';
import Link from '@/components/common/Link';
import { useGlobalState, useMessages, useNavigation } from '@/components/hooks';
import {
Globe,
Grid2x2,
LayoutDashboard,
LinkIcon,
PanelLeft,
PanelsLeftBottom,
} from '@/components/icons';
import { UserButton } from '@/components/input/UserButton';
import { Logo } from '@/components/svg';
export function SideNav(props: any) {
const { t, labels } = useMessages();
+2 -2
View File
@@ -1,9 +1,9 @@
import { Column, Focusable, Row, Tooltip, TooltipTrigger } from '@umami/react-zen';
import { IconLabel } from '@/components/common/IconLabel';
import Link from '@/components/common/Link';
import { NavMenu } from '@/components/common/NavMenu';
import { useMessages, useNavigation } from '@/components/hooks';
import { ArrowLeft, Globe, User, Users } from '@/components/icons';
import { Column, Focusable, Row, Tooltip, TooltipTrigger } from '@umami/react-zen';
import Link from '@/components/common/Link';
export function AdminNav({ onItemClick }: { onItemClick?: () => void }) {
const { t, labels } = useMessages();
@@ -1,7 +1,7 @@
import { DataColumn, DataTable, Dialog, Icon, MenuItem, Modal, Row, Text } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { useState } from 'react';
import { DateDistance } from '@/components/common/DateDistance';
import Link from '@/components/common/Link';
import { SortableLabel } from '@/components/common/SortableLabel';
import { useMessages } from '@/components/hooks';
import { Edit, Trash } from '@/components/icons';
@@ -48,7 +48,9 @@ export function AdminTeamsTable({
</DataColumn>
<DataColumn
id="created"
label={<SortableLabel label={t(labels.created)} sortKey="createdAt" defaultDirection="desc" />}
label={
<SortableLabel label={t(labels.created)} sortKey="createdAt" defaultDirection="desc" />
}
width="160px"
>
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
+4 -2
View File
@@ -1,7 +1,7 @@
import { DataColumn, DataTable, Icon, MenuItem, Modal, Row, Text } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { useState } from 'react';
import { DateDistance } from '@/components/common/DateDistance';
import Link from '@/components/common/Link';
import { SortableLabel } from '@/components/common/SortableLabel';
import { useMessages } from '@/components/hooks';
import { Edit, Trash } from '@/components/icons';
@@ -40,7 +40,9 @@ export function UsersTable({
</DataColumn>
<DataColumn
id="created"
label={<SortableLabel label={t(labels.created)} sortKey="createdAt" defaultDirection="desc" />}
label={
<SortableLabel label={t(labels.created)} sortKey="createdAt" defaultDirection="desc" />
}
>
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
</DataColumn>
@@ -1,8 +1,8 @@
import { DataColumn, DataTable, Dialog, Icon, MenuItem, Modal, Row, Text } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { useState } from 'react';
import { WebsiteDeleteForm } from '@/app/(main)/websites/[websiteId]/settings/WebsiteDeleteForm';
import { DateDistance } from '@/components/common/DateDistance';
import Link from '@/components/common/Link';
import { SortableLabel } from '@/components/common/SortableLabel';
import { useMessages } from '@/components/hooks';
import { Edit, Trash, Users } from '@/components/icons';
@@ -48,7 +48,9 @@ export function AdminWebsitesTable({ data = [], ...props }: { data: any[] }) {
</DataColumn>
<DataColumn
id="created"
label={<SortableLabel label={t(labels.created)} sortKey="createdAt" defaultDirection="desc" />}
label={
<SortableLabel label={t(labels.created)} sortKey="createdAt" defaultDirection="desc" />
}
width="180px"
>
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
+1 -7
View File
@@ -15,13 +15,7 @@ export function BoardEntityBadge({
return (
<Row padding borderRadius="full" backgroundColor="surface-base" border gap="2">
<Icon>
{type === 'pixel' ? (
<Grid2x2 />
) : type === 'link' ? (
<Link />
) : (
<Favicon domain={domain} />
)}
{type === 'pixel' ? <Grid2x2 /> : type === 'link' ? <Link /> : <Favicon domain={domain} />}
</Icon>
<Text size="sm">{name}</Text>
</Row>
+1 -1
View File
@@ -3,8 +3,8 @@ import { Loading, useToast } from '@umami/react-zen';
import { createContext, type ReactNode, useCallback, useEffect, useRef, useState } from 'react';
import { v4 as uuid } from 'uuid';
import { useApi, useMessages, useModified, useNavigation } from '@/components/hooks';
import { BOARD_TYPES, getBoardType } from '@/lib/boards';
import { useBoardQuery } from '@/components/hooks/queries/useBoardQuery';
import { BOARD_TYPES, getBoardType } from '@/lib/boards';
import type { Board, BoardParameters } from '@/lib/types';
import { getComponentDefinition } from './boardComponentRegistry';
+5 -3
View File
@@ -1,6 +1,6 @@
import { DataColumn, DataTable, type DataTableProps, Row } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { DateDistance } from '@/components/common/DateDistance';
import Link from '@/components/common/Link';
import { SortableLabel } from '@/components/common/SortableLabel';
import { useMessages, useNavigation } from '@/components/hooks';
import { BoardDeleteButton } from './BoardDeleteButton';
@@ -23,11 +23,13 @@ export function BoardsTable(props: DataTableProps) {
label={<SortableLabel label={t(labels.description)} sortKey="description" />}
/>
<DataColumn id="type" label={<SortableLabel label={t(labels.boardType)} sortKey="type" />}>
{({ type }: any) => type ? type.charAt(0).toUpperCase() + type.slice(1) : ''}
{({ type }: any) => (type ? type.charAt(0).toUpperCase() + type.slice(1) : '')}
</DataColumn>
<DataColumn
id="created"
label={<SortableLabel label={t(labels.created)} sortKey="createdAt" defaultDirection="desc" />}
label={
<SortableLabel label={t(labels.created)} sortKey="createdAt" defaultDirection="desc" />
}
width="200px"
>
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
@@ -13,7 +13,6 @@ import { useMessages } from '@/components/hooks';
import { LinkSelect } from '@/components/input/LinkSelect';
import { PixelSelect } from '@/components/input/PixelSelect';
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
import type { BoardComponentConfig } from '@/lib/types';
import {
BOARD_ENTITY_TYPES,
type BoardEntityType,
@@ -22,6 +21,7 @@ import {
isBoardComponentSupported,
isOpenBoardType,
} from '@/lib/boards';
import type { BoardComponentConfig } from '@/lib/types';
import {
type ComponentDefinition,
type ConfigField,
@@ -53,9 +53,7 @@ export function BoardComponentSelect({
const [selectedEntityType, setSelectedEntityType] = useState<BoardEntityType>(
initialEntity.entityType || boardEntityType || BOARD_ENTITY_TYPES.website,
);
const [selectedEntityId, setSelectedEntityId] = useState(
initialEntity.entityId || boardEntityId,
);
const [selectedEntityId, setSelectedEntityId] = useState(initialEntity.entityId || boardEntityId);
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
@@ -370,7 +368,11 @@ export function BoardComponentSelect({
<Text weight="bold">Preview</Text>
<Column border="left" paddingLeft="4" height="100%" style={{ minWidth: 0 }}>
{hasSelectedEntity && previewConfig && (!needsWebsite || resolvedEntityId) ? (
<BoardComponentRenderer config={previewConfig} websiteId={resolvedEntityId} entityType={resolvedEntityType} />
<BoardComponentRenderer
config={previewConfig}
websiteId={resolvedEntityId}
entityType={resolvedEntityType}
/>
) : (
<Column alignItems="center" justifyContent="center" height="100%">
<Text color="muted">
@@ -1,8 +1,3 @@
import { Panel } from '@/components/common/Panel';
import { useBoard, useMessages, useNavigation } from '@/components/hooks';
import { Pencil, Plus, X } from '@/components/icons';
import { getBoardEntity, getBoardType, getResolvedComponentEntity } from '@/lib/boards';
import type { BoardComponentConfig } from '@/lib/types';
import {
Box,
Button,
@@ -15,6 +10,11 @@ import {
TooltipTrigger,
} from '@umami/react-zen';
import { useMemo, useState } from 'react';
import { Panel } from '@/components/common/Panel';
import { useBoard, useMessages, useNavigation } from '@/components/hooks';
import { Pencil, Plus, X } from '@/components/icons';
import { getBoardEntity, getBoardType, getResolvedComponentEntity } from '@/lib/boards';
import type { BoardComponentConfig } from '@/lib/types';
import { getComponentDefinition } from '../boardComponentRegistry';
import { BoardComponentRenderer } from './BoardComponentRenderer';
import { BoardComponentSelect } from './BoardComponentSelect';
@@ -48,7 +48,9 @@ export function BoardEditColumn({
return null;
}
return <BoardComponentRenderer config={component} websiteId={entityId} entityType={entityType} />;
return (
<BoardComponentRenderer config={component} websiteId={entityId} entityType={entityType} />
);
}, [component, definition?.requiresWebsite, entityId, entityType]);
const handleSelect = (config: BoardComponentConfig) => {
@@ -95,9 +97,7 @@ export function BoardEditColumn({
>
<TooltipTrigger delay={0}>
<Button variant="outline" onPress={() => setShowSelect(true)}>
<Icon size="sm">
{hasComponent ? <Pencil /> : <Plus />}
</Icon>
<Icon size="sm">{hasComponent ? <Pencil /> : <Plus />}</Icon>
</Button>
<Tooltip>{t(hasComponent ? labels.edit : labels.selectComponent)}</Tooltip>
</TooltipTrigger>
@@ -1,5 +1,5 @@
import { Share } from '@/components/icons';
import { useMessages } from '@/components/hooks';
import { Share } from '@/components/icons';
import { DialogButton } from '@/components/input/DialogButton';
import { BoardShareDialog } from './BoardShareDialog';
@@ -7,12 +7,7 @@ export function BoardShareButton({ boardId }: { boardId: string }) {
const { t, labels } = useMessages();
return (
<DialogButton
icon={<Share />}
label={t(labels.share)}
title={null}
width="900px"
>
<DialogButton icon={<Share />} label={t(labels.share)} title={null} width="900px">
<BoardShareDialog boardId={boardId} />
</DialogButton>
);
@@ -1,9 +1,9 @@
import { Button, Column, Heading, Row, Text } from '@umami/react-zen';
import { useState } from 'react';
import { Plus } from '@/components/icons';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { IconLabel } from '@/components/common/IconLabel';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useBoardSharesQuery, useMessages } from '@/components/hooks';
import { Plus } from '@/components/icons';
import { BoardShareCreateForm } from './BoardShareCreateForm';
import { BoardSharesTable } from './BoardSharesTable';
@@ -49,9 +49,7 @@ function BoardShareDialogContent({
onCancel={() => setIsCreating(false)}
/>
)}
{hasShares && (
<Text>{t(messages.shareUrl)}</Text>
)}
{hasShares && <Text>{t(messages.shareUrl)}</Text>}
{!showCreateForm &&
(hasShares ? (
<BoardSharesTable data={shares} />
@@ -1,8 +1,8 @@
import { DataColumn, DataTable, type DataTableProps, Row } from '@umami/react-zen';
import { ShareDeleteButton } from '@/app/(main)/websites/[websiteId]/settings/ShareDeleteButton';
import { CopyButton } from '@/components/common/CopyButton';
import { ExternalLink } from '@/components/common/ExternalLink';
import { useConfig, useMessages, useMobile } from '@/components/hooks';
import { DataColumn, DataTable, type DataTableProps, Row } from '@umami/react-zen';
import { SimpleShareEditButton } from '@/components/share/SimpleShareEditButton';
export function BoardSharesTable(props: DataTableProps) {
@@ -3,8 +3,8 @@ import { IconLabel } from '@/components/common/IconLabel';
import { LinkButton } from '@/components/common/LinkButton';
import { PageHeader } from '@/components/common/PageHeader';
import { useBoard, useMessages, useNavigation } from '@/components/hooks';
import { getBoardEntity } from '@/lib/boards';
import { Edit, LayoutDashboard } from '@/components/icons';
import { getBoardEntity } from '@/lib/boards';
import { BoardEntityBadge } from '../BoardEntityBadge';
import { useBoardEntityBadgeProps } from '../useBoardEntityBadgeProps';
@@ -1,12 +1,12 @@
'use client';
import { Column } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { BoardEditForm } from '@/app/(main)/boards/BoardEditForm';
import { BoardShareDialog } from '@/app/(main)/boards/[boardId]/BoardShareDialog';
import { BoardEditForm } from '@/app/(main)/boards/BoardEditForm';
import { IconLabel } from '@/components/common/IconLabel';
import { Panel } from '@/components/common/Panel';
import Link from '@/components/common/Link';
import { PageBody } from '@/components/common/PageBody';
import { PageHeader } from '@/components/common/PageHeader';
import { Panel } from '@/components/common/Panel';
import { useBoardQuery, useMessages, useNavigation } from '@/components/hooks';
import { ArrowLeft, LayoutDashboard } from '@/components/icons';
@@ -17,12 +17,7 @@ export function BoardEditPage({ boardId }: { boardId: string }) {
return (
<PageBody>
<Column
margin="2"
width="100%"
maxWidth="800px"
style={{ marginInline: 'auto' }}
>
<Column margin="2" width="100%" maxWidth="800px" style={{ marginInline: 'auto' }}>
<>
<Column marginTop="6">
<Link href={renderUrl(`/boards/${boardId}`)}>
@@ -1,3 +1,4 @@
import type { ComponentType } from 'react';
import { TextBlock } from '@/app/(main)/boards/TextBlock';
import { LinkMetricsBar } from '@/app/(main)/links/[linkId]/LinkMetricsBar';
import { PixelMetricsBar } from '@/app/(main)/pixels/[pixelId]/PixelMetricsBar';
@@ -16,7 +17,6 @@ import { EventsChart } from '@/components/metrics/EventsChart';
import { MetricsTable } from '@/components/metrics/MetricsTable';
import { WeeklyTraffic } from '@/components/metrics/WeeklyTraffic';
import { WorldMap } from '@/components/metrics/WorldMap';
import type { ComponentType } from 'react';
export interface ConfigField {
name: string;
@@ -1,8 +1,8 @@
'use client';
import { Button, Column, Grid, Heading } from '@umami/react-zen';
import Link from '@/components/common/Link';
import Script from 'next/script';
import { WebsiteChart } from '@/app/(main)/websites/[websiteId]/WebsiteChart';
import Link from '@/components/common/Link';
import { PageBody } from '@/components/common/PageBody';
import { PageHeader } from '@/components/common/PageHeader';
import { Panel } from '@/components/common/Panel';
+1 -2
View File
@@ -1,7 +1,6 @@
import { Icon } from '@umami/react-zen';
import { LinkButton } from '@/components/common/LinkButton';
import { useNavigation } from '@/components/hooks';
import { useMessages } from '@/components/hooks';
import { useMessages, useNavigation } from '@/components/hooks';
import { Edit } from '@/components/icons';
export function LinkEditButton({ linkId }: { linkId: string }) {
+4 -2
View File
@@ -1,7 +1,7 @@
import { DataColumn, DataTable, type DataTableProps, Row } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { DateDistance } from '@/components/common/DateDistance';
import { ExternalLink } from '@/components/common/ExternalLink';
import Link from '@/components/common/Link';
import { SortableLabel } from '@/components/common/SortableLabel';
import { useMessages, useNavigation, useSlug } from '@/components/hooks';
import { LinkDeleteButton } from './LinkDeleteButton';
@@ -44,7 +44,9 @@ export function LinksTable({ showActions, ...props }: LinksTableProps) {
</DataColumn>
<DataColumn
id="created"
label={<SortableLabel label={t(labels.created)} sortKey="createdAt" defaultDirection="desc" />}
label={
<SortableLabel label={t(labels.created)} sortKey="createdAt" defaultDirection="desc" />
}
width="200px"
>
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
@@ -1,7 +1,7 @@
import { Button, Column, Heading, Row, Text } from '@umami/react-zen';
import { useState } from 'react';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { IconLabel } from '@/components/common/IconLabel';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useLinkSharesQuery, useMessages } from '@/components/hooks';
import { Plus } from '@/components/icons';
import { SimpleShareCreateForm } from '@/components/share/SimpleShareCreateForm';
@@ -49,9 +49,7 @@ function LinkShareFormContent({
onCancel={() => setIsCreating(false)}
/>
)}
{hasShares && (
<Text>{t(messages.shareUrl)}</Text>
)}
{hasShares && <Text>{t(messages.shareUrl)}</Text>}
{!showCreateForm &&
(hasShares ? (
<SimpleSharesTable data={shares} />
@@ -1,12 +1,12 @@
'use client';
import { Column } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { LinkShareForm } from '@/app/(main)/links/[linkId]/LinkShareForm';
import { LinkEditForm } from '@/app/(main)/links/LinkEditForm';
import { LinkProvider } from '@/app/(main)/links/LinkProvider';
import { LinkShareForm } from '@/app/(main)/links/[linkId]/LinkShareForm';
import { Panel } from '@/components/common/Panel';
import { IconLabel } from '@/components/common/IconLabel';
import Link from '@/components/common/Link';
import { PageHeader } from '@/components/common/PageHeader';
import { Panel } from '@/components/common/Panel';
import { useLink, useMessages, useNavigation } from '@/components/hooks';
import { ArrowLeft, Link as LinkIcon } from '@/components/icons';
+1 -2
View File
@@ -1,7 +1,6 @@
import { Icon } from '@umami/react-zen';
import { LinkButton } from '@/components/common/LinkButton';
import { useNavigation } from '@/components/hooks';
import { useMessages } from '@/components/hooks';
import { useMessages, useNavigation } from '@/components/hooks';
import { Edit } from '@/components/icons';
export function PixelEditButton({ pixelId }: { pixelId: string }) {
+4 -2
View File
@@ -1,7 +1,7 @@
import { DataColumn, DataTable, type DataTableProps, Row } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { DateDistance } from '@/components/common/DateDistance';
import { ExternalLink } from '@/components/common/ExternalLink';
import Link from '@/components/common/Link';
import { SortableLabel } from '@/components/common/SortableLabel';
import { useMessages, useNavigation, useSlug } from '@/components/hooks';
import { PixelDeleteButton } from './PixelDeleteButton';
@@ -35,7 +35,9 @@ export function PixelsTable({ showActions, ...props }: PixelsTableProps) {
</DataColumn>
<DataColumn
id="created"
label={<SortableLabel label={t(labels.created)} sortKey="createdAt" defaultDirection="desc" />}
label={
<SortableLabel label={t(labels.created)} sortKey="createdAt" defaultDirection="desc" />
}
>
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
</DataColumn>
@@ -1,7 +1,7 @@
import { Button, Column, Heading, Row, Text } from '@umami/react-zen';
import { useState } from 'react';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { IconLabel } from '@/components/common/IconLabel';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useMessages, usePixelSharesQuery } from '@/components/hooks';
import { Plus } from '@/components/icons';
import { SimpleShareCreateForm } from '@/components/share/SimpleShareCreateForm';
@@ -49,9 +49,7 @@ function PixelShareFormContent({
onCancel={() => setIsCreating(false)}
/>
)}
{hasShares && (
<Text>{t(messages.shareUrl)}</Text>
)}
{hasShares && <Text>{t(messages.shareUrl)}</Text>}
{!showCreateForm &&
(hasShares ? (
<SimpleSharesTable data={shares} />
@@ -1,12 +1,12 @@
'use client';
import { Column } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { PixelShareForm } from '@/app/(main)/pixels/[pixelId]/PixelShareForm';
import { PixelEditForm } from '@/app/(main)/pixels/PixelEditForm';
import { PixelProvider } from '@/app/(main)/pixels/PixelProvider';
import { PixelShareForm } from '@/app/(main)/pixels/[pixelId]/PixelShareForm';
import { Panel } from '@/components/common/Panel';
import { IconLabel } from '@/components/common/IconLabel';
import Link from '@/components/common/Link';
import { PageHeader } from '@/components/common/PageHeader';
import { Panel } from '@/components/common/Panel';
import { useMessages, useNavigation, usePixel } from '@/components/hooks';
import { ArrowLeft, Grid2x2 } from '@/components/icons';
+1 -1
View File
@@ -1,6 +1,6 @@
import { Column, Focusable, Row, Text, Tooltip, TooltipTrigger } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { IconLabel } from '@/components/common/IconLabel';
import Link from '@/components/common/Link';
import { useMessages, useNavigation } from '@/components/hooks';
import { ArrowLeft, Settings2, UserCircle, Users } from '@/components/icons';
@@ -4,5 +4,7 @@ import { ThemeModeSelector } from '@/components/input/ThemeModeSelector';
export function ThemeSetting() {
const { theme, setTheme } = useTheme();
return <ThemeModeSelector value={theme} onChange={value => setTheme(value as 'light' | 'dark')} />;
return (
<ThemeModeSelector value={theme} onChange={value => setTheme(value as 'light' | 'dark')} />
);
}
+1 -1
View File
@@ -1,5 +1,5 @@
import Link from '@/components/common/Link';
import { DataGrid } from '@/components/common/DataGrid';
import Link from '@/components/common/Link';
import { useLoginQuery, useNavigation, useUserTeamsQuery } from '@/components/hooks';
import { TeamsTable } from './TeamsTable';
@@ -1,7 +1,7 @@
import { DataColumn, DataTable, Row } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { TeamMemberEditButton } from '@/app/(main)/teams/[teamId]/TeamMemberEditButton';
import { TeamMemberRemoveButton } from '@/app/(main)/teams/[teamId]/TeamMemberRemoveButton';
import Link from '@/components/common/Link';
import { SortableLabel } from '@/components/common/SortableLabel';
import { useMessages } from '@/components/hooks';
import { ROLES } from '@/lib/constants';
@@ -1,6 +1,6 @@
import { Icon, Row } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { DataGrid } from '@/components/common/DataGrid';
import Link from '@/components/common/Link';
import { useLoginQuery, useNavigation, useUserWebsitesQuery } from '@/components/hooks';
import { Favicon } from '@/index';
import { WebsitesTable } from './WebsitesTable';
+3 -1
View File
@@ -25,7 +25,9 @@ export function WebsitesTable({ showActions, renderLink, ...props }: WebsitesTab
<DataColumn id="domain" label={<SortableLabel label={t(labels.domain)} sortKey="domain" />} />
<DataColumn
id="created"
label={<SortableLabel label={t(labels.created)} sortKey="createdAt" defaultDirection="desc" />}
label={
<SortableLabel label={t(labels.created)} sortKey="createdAt" defaultDirection="desc" />
}
width="200px"
>
{(row: any) => <DateDistance date={new Date(row.createdAt)} />}
@@ -6,8 +6,8 @@ import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteContro
import { Panel } from '@/components/common/Panel';
import { useDateRange, useMessages, useResultQuery } from '@/components/hooks';
import { ListCheck } from '@/components/icons';
import { DownloadButton } from '@/components/input/DownloadButton';
import { DialogButton } from '@/components/input/DialogButton';
import { DownloadButton } from '@/components/input/DownloadButton';
import { Breakdown } from './Breakdown';
export function BreakdownPage({ websiteId }: { websiteId: string }) {
@@ -1,3 +1,4 @@
import { Box, Column, Grid, Icon, ProgressBar, Row, Text } from '@umami/react-zen';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useMessages, useNavigation, useOperatorLabels, useResultQuery } from '@/components/hooks';
import { File, User } from '@/components/icons';
@@ -6,7 +7,6 @@ import { ChangeLabel } from '@/components/metrics/ChangeLabel';
import { Lightning } from '@/components/svg';
import { formatLongNumber } from '@/lib/format';
import type { FunnelResult } from '@/queries/sql/reports/getFunnel';
import { Box, Column, Grid, Icon, ProgressBar, Row, Text } from '@umami/react-zen';
import { FunnelEditForm } from './FunnelEditForm';
interface FunnelProps {
@@ -94,9 +94,7 @@ function StepRow({
<ActionSelect />
</FormField>
</Column>
<Column>
{valueField}
</Column>
<Column>{valueField}</Column>
<Button onPress={onRemove}>
<Icon size="sm">
<X />
@@ -49,7 +49,14 @@ export function Goal({ id, name, type, parameters, websiteId, startDate, endDate
</Column>
{!isSharePage && (
<Column>
<ReportEditButton id={id} name={name} type={type} title={t(labels.goal)} minWidth="400px" minHeight="300px">
<ReportEditButton
id={id}
name={name}
type={type}
title={t(labels.goal)}
minWidth="400px"
minHeight="300px"
>
{({ close }) => <GoalEditForm id={id} websiteId={websiteId} onClose={close} />}
</ReportEditButton>
</Column>
@@ -298,7 +298,12 @@ function ClickHeatmapView({
</Text>
</Row>
) : (
<Row alignItems="center" justifyContent="space-between" gap className={styles.summaryStats}>
<Row
alignItems="center"
justifyContent="space-between"
gap
className={styles.summaryStats}
>
<Text color="muted" className={styles.summaryStat}>
{viewport
? `${visible.length} positions - ${formatLongNumber(totalClicks)} clicks - viewport ${viewport.width}x${viewport.height}`
@@ -329,47 +334,49 @@ function ClickHeatmapView({
<EmptyState message="No click data for this page yet." />
) : (
<>
<div className={styles.snapshotClip}>
{showSnapshot && !snapshotReady && <CanvasLoading />}
{showSnapshot && snapshot?.imageUrl && <SnapshotImage snapshot={snapshot} onReady={handleSnapshotReady} />}
</div>
{showOverlay && (
<div className={styles.overlay}>
{visible.map((point, index) => {
const intensity = Math.min(1, point.count / maxCount);
const desiredSize = 24 + intensity * 36;
const pointWidth = Math.max(overlayPageW, point.pageX);
const pointHeight = Math.max(overlayPageH, point.pageY);
const rawCenterX = (point.pageX / Math.max(1, pointWidth)) * 100;
const rawCenterY = (point.pageY / Math.max(1, pointHeight)) * 100;
const size = desiredSize;
const centerX = Math.max(
CLICK_EDGE_PERCENT,
Math.min(100 - CLICK_EDGE_PERCENT, rawCenterX),
);
const centerY = Math.max(
CLICK_EDGE_PERCENT,
Math.min(100 - CLICK_EDGE_PERCENT, rawCenterY),
);
<div className={styles.snapshotClip}>
{showSnapshot && !snapshotReady && <CanvasLoading />}
{showSnapshot && snapshot?.imageUrl && (
<SnapshotImage snapshot={snapshot} onReady={handleSnapshotReady} />
)}
</div>
{showOverlay && (
<div className={styles.overlay}>
{visible.map((point, index) => {
const intensity = Math.min(1, point.count / maxCount);
const desiredSize = 24 + intensity * 36;
const pointWidth = Math.max(overlayPageW, point.pageX);
const pointHeight = Math.max(overlayPageH, point.pageY);
const rawCenterX = (point.pageX / Math.max(1, pointWidth)) * 100;
const rawCenterY = (point.pageY / Math.max(1, pointHeight)) * 100;
const size = desiredSize;
const centerX = Math.max(
CLICK_EDGE_PERCENT,
Math.min(100 - CLICK_EDGE_PERCENT, rawCenterX),
);
const centerY = Math.max(
CLICK_EDGE_PERCENT,
Math.min(100 - CLICK_EDGE_PERCENT, rawCenterY),
);
return (
<div
key={`${point.pageX}-${point.pageY}-${index}`}
className={styles.dot}
style={{
left: `${centerX}%`,
top: `${centerY}%`,
width: size,
height: size,
transform: 'translate(-50%, -50%)',
opacity: 0.25 + intensity * 0.55,
}}
title={`${point.count} click${point.count === 1 ? '' : 's'}`}
/>
);
})}
</div>
)}
return (
<div
key={`${point.pageX}-${point.pageY}-${index}`}
className={styles.dot}
style={{
left: `${centerX}%`,
top: `${centerY}%`,
width: size,
height: size,
transform: 'translate(-50%, -50%)',
opacity: 0.25 + intensity * 0.55,
}}
title={`${point.count} click${point.count === 1 ? '' : 's'}`}
/>
);
})}
</div>
)}
</>
)}
</div>
@@ -405,8 +412,14 @@ function ScrollHeatmapView({
useEffect(() => {
setSnapshotReady(!(showPage && hasSnapshotImage));
}, [hasSnapshotImage, showPage, snapshot?.id]);
const { buckets = [], totalSessions = 0, pageW = 0, pageH = 0, viewportW = 0, viewportH = 0 } =
scroll ?? {};
const {
buckets = [],
totalSessions = 0,
pageW = 0,
pageH = 0,
viewportW = 0,
viewportH = 0,
} = scroll ?? {};
const baseWidth = Math.max(pageW, 1);
const baseHeight = Math.max(pageH, 640);
const renderWidth = snapshot?.pageW ?? baseWidth;
@@ -451,7 +464,12 @@ function ScrollHeatmapView({
</Text>
</Row>
) : (
<Row alignItems="center" justifyContent="space-between" gap className={styles.summaryHeader}>
<Row
alignItems="center"
justifyContent="space-between"
gap
className={styles.summaryHeader}
>
<Text color="muted" className={styles.summaryStat}>
{hasScrollData
? `${formatLongNumber(totalSessions)} sessions - page ${pageW}x${pageH}${viewportH ? ` - viewport ${viewportW}x${viewportH}` : ''}`
@@ -481,33 +499,38 @@ function ScrollHeatmapView({
<EmptyState message="No scroll data for this page yet." />
) : (
<div className={styles.canvasClip}>
{showSnapshot && !snapshotReady && <CanvasLoading />}
{showSnapshot && snapshot?.imageUrl && <SnapshotImage snapshot={snapshot} onReady={handleSnapshotReady} />}
{showOverlay && (
<div className={styles.overlay}>
{bands.map(band => {
const intensity = band.ratio;
const hue = Math.round(60 - intensity * 60);
{showSnapshot && !snapshotReady && <CanvasLoading />}
{showSnapshot && snapshot?.imageUrl && (
<SnapshotImage snapshot={snapshot} onReady={handleSnapshotReady} />
)}
{showOverlay && (
<div className={styles.overlay}>
{bands.map(band => {
const intensity = band.ratio;
const hue = Math.round(60 - intensity * 60);
return (
<div
key={band.fromPct}
className={styles.scrollBand}
style={{
top: `${band.fromPct}%`,
height: `${Math.max(0, band.toPct - band.fromPct)}%`,
background: intensity > 0 ? `hsla(${hue}, 90%, 55%, ${0.12 + intensity * 0.45})` : 'none',
}}
title={`${band.toPct}% depth - ${formatLongNumber(band.reached)} sessions reached`}
>
<span className={styles.scrollBandLabel}>
{band.toPct}% depth - {Math.round(intensity * 100)}% reached
</span>
</div>
);
})}
</div>
)}
return (
<div
key={band.fromPct}
className={styles.scrollBand}
style={{
top: `${band.fromPct}%`,
height: `${Math.max(0, band.toPct - band.fromPct)}%`,
background:
intensity > 0
? `hsla(${hue}, 90%, 55%, ${0.12 + intensity * 0.45})`
: 'none',
}}
title={`${band.toPct}% depth - ${formatLongNumber(band.reached)} sessions reached`}
>
<span className={styles.scrollBandLabel}>
{band.toPct}% depth - {Math.round(intensity * 100)}% reached
</span>
</div>
);
})}
</div>
)}
</div>
)}
</div>
@@ -524,13 +547,7 @@ function ScrollHeatmapView({
);
}
function SnapshotImage({
snapshot,
onReady,
}: {
snapshot: HeatmapSnapshot;
onReady: () => void;
}) {
function SnapshotImage({ snapshot, onReady }: { snapshot: HeatmapSnapshot; onReady: () => void }) {
const [src, setSrc] = useState<string | null>(null);
useEffect(() => {
@@ -52,7 +52,13 @@ export function HeatmapsPage({ websiteId }: { websiteId: string }) {
return (
<Column gap>
<WebsiteControls websiteId={websiteId} />
<Panel minHeight="900px" allowFullscreen minWidth="0" width="100%" style={{ overflow: 'hidden' }}>
<Panel
minHeight="900px"
allowFullscreen
minWidth="0"
width="100%"
style={{ overflow: 'hidden' }}
>
<Column gap="5" minWidth="0" width="100%" paddingTop="2">
<Column gap="4" minWidth="0" width="100%">
{isPhone ? (
@@ -83,9 +89,7 @@ export function HeatmapsPage({ websiteId }: { websiteId: string }) {
<Heatmap
websiteId={websiteId}
urlPath={urlPathByMode[mode]}
onUrlPathChange={urlPath =>
setUrlPathByMode(state => ({ ...state, [mode]: urlPath }))
}
onUrlPathChange={urlPath => setUrlPathByMode(state => ({ ...state, [mode]: urlPath }))}
mode={mode}
search={search}
/>
@@ -1,6 +1,6 @@
import { Column, Grid, Icon, Row, Text } from '@umami/react-zen';
import type { ReactNode } from 'react';
import { differenceInCalendarDays } from 'date-fns';
import type { ReactNode } from 'react';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { Panel } from '@/components/common/Panel';
import { useLocale, useMessages, useResultQuery } from '@/components/hooks';
@@ -1,4 +1,15 @@
import { Column, Grid, Heading, ListItem, Row, Select, Tab, TabList, TabPanel, Tabs } from '@umami/react-zen';
import {
Column,
Grid,
Heading,
ListItem,
Row,
Select,
Tab,
TabList,
TabPanel,
Tabs,
} from '@umami/react-zen';
import { useMemo, useState } from 'react';
import { GridRow } from '@/components/common/GridRow';
import { LoadingPanel } from '@/components/common/LoadingPanel';
@@ -1,9 +1,9 @@
import type { ReactNode } from 'react';
import { useDateRange, useMessages } from '@/components/hooks';
import type { RevenueStatsData } from '@/components/hooks/queries/useRevenueStatsQuery';
import { MetricCard } from '@/components/metrics/MetricCard';
import { MetricsBar } from '@/components/metrics/MetricsBar';
import { formatLongCurrency, formatLongNumber } from '@/lib/format';
import type { RevenueStatsData } from '@/components/hooks/queries/useRevenueStatsQuery';
export interface RevenueMetricsBarProps {
data: RevenueStatsData;
@@ -1,11 +1,7 @@
import { Grid } from '@umami/react-zen';
import { useMemo } from 'react';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import {
useMessages,
useRevenueMetricsQuery,
type RevenueMetricType,
} from '@/components/hooks';
import { type RevenueMetricType, useMessages, useRevenueMetricsQuery } from '@/components/hooks';
import { ListTable } from '@/components/metrics/ListTable';
import { MetricLabel } from '@/components/metrics/MetricLabel';
import { formatLongCurrency } from '@/lib/format';
@@ -7,10 +7,7 @@ export interface RevenueSessionsDataTableProps {
currency: string;
}
export function RevenueSessionsDataTable({
websiteId,
currency,
}: RevenueSessionsDataTableProps) {
export function RevenueSessionsDataTable({ websiteId, currency }: RevenueSessionsDataTableProps) {
const { updateParams } = useNavigation();
const queryResult = useRevenueSessionsQuery(websiteId, currency);
@@ -1,6 +1,6 @@
import { Column, Focusable, Row, Text, Tooltip, TooltipTrigger } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { IconLabel } from '@/components/common/IconLabel';
import Link from '@/components/common/Link';
import { useMessages, useNavigation, useWebsiteNavItems } from '@/components/hooks';
import { ArrowLeft } from '@/components/icons';
@@ -1,8 +1,8 @@
import { DataColumn, DataTable, type DataTableProps, Row } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { CohortDeleteButton } from '@/app/(main)/websites/[websiteId]/cohorts/CohortDeleteButton';
import { CohortEditButton } from '@/app/(main)/websites/[websiteId]/cohorts/CohortEditButton';
import { DateDistance } from '@/components/common/DateDistance';
import Link from '@/components/common/Link';
import { useMessages, useNavigation } from '@/components/hooks';
import { filtersObjectToArray } from '@/lib/params';
@@ -7,7 +7,13 @@ import { Empty } from '@/components/common/Empty';
import Link from '@/components/common/Link';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { Pager } from '@/components/common/Pager';
import { useEventDataPivotQuery, useEventDataPropertiesQuery, useMessages, useMobile, useNavigation } from '@/components/hooks';
import {
useEventDataPivotQuery,
useEventDataPropertiesQuery,
useMessages,
useMobile,
useNavigation,
} from '@/components/hooks';
import type { EventPropertyFilter } from '@/lib/types';
export function EventDataPivotTable({
@@ -41,7 +47,15 @@ export function EventDataPivotTable({
const tableData = useMemo(() => {
if (!pivotQuery.data?.data) return [];
return pivotQuery.data.data.map(
(row: { eventId: string; sessionId: string; eventName: string; urlPath: string; createdAt: string; propertyKeys: string[]; propertyValues: string[] }) => {
(row: {
eventId: string;
sessionId: string;
eventName: string;
urlPath: string;
createdAt: string;
propertyKeys: string[];
propertyValues: string[];
}) => {
const flat: Record<string, any> = {
eventId: row.eventId,
sessionId: row.sessionId,
@@ -57,10 +71,7 @@ export function EventDataPivotTable({
}, [pivotQuery.data]);
const tableQuery = useMemo(
() =>
pivotQuery.data
? { ...pivotQuery.data, data: tableData }
: undefined,
() => (pivotQuery.data ? { ...pivotQuery.data, data: tableData } : undefined),
[pivotQuery.data, tableData],
);
@@ -110,13 +121,11 @@ export function EventDataPivotTable({
</DataColumn>
</DataTable>
) : (
<div style={{ width: '100%', maxWidth: '100%', overflowX: 'auto', overflowY: 'hidden' }}>
<div
style={{ width: '100%', maxWidth: '100%', overflowX: 'auto', overflowY: 'hidden' }}
>
<div style={{ width: tableMinWidth, minWidth: tableMinWidth }}>
<DataTable
data={tableQuery?.data}
style={{ width: '100%' }}
displayMode="table"
>
<DataTable data={tableQuery?.data} style={{ width: '100%' }} displayMode="table">
<DataColumn id="session" label={t(labels.session)} width="72px">
{(row: any) => (
<Link href={updateParams({ session: row.sessionId })}>
@@ -10,10 +10,10 @@ import {
Row,
Text,
} from '@umami/react-zen';
import Link from '@/components/common/Link';
import { Avatar } from '@/components/common/Avatar';
import { DateDistance } from '@/components/common/DateDistance';
import { IconLabel } from '@/components/common/IconLabel';
import Link from '@/components/common/Link';
import { TypeIcon } from '@/components/common/TypeIcon';
import { useFormat, useMessages, useNavigation } from '@/components/hooks';
import { Eye, FileText } from '@/components/icons';
@@ -9,11 +9,7 @@ export function RealtimeHeader({ data }: { data: any }) {
return (
<MetricsBar
columns={
isPhone
? 'repeat(2, minmax(0, 1fr))'
: 'repeat(auto-fit, minmax(160px, 1fr))'
}
columns={isPhone ? 'repeat(2, minmax(0, 1fr))' : 'repeat(auto-fit, minmax(160px, 1fr))'}
>
<MetricCard label={t(labels.views)} value={totals.views} />
<MetricCard label={t(labels.visitors)} value={totals.visitors} />
@@ -1,3 +1,6 @@
import { Column, Heading, Row, SearchField, Text } from '@umami/react-zen';
import { useMemo, useState } from 'react';
import { List, type RowComponentProps } from 'react-window';
import { SessionModal } from '@/app/(main)/websites/[websiteId]/sessions/SessionModal';
import { Avatar } from '@/components/common/Avatar';
import { Empty } from '@/components/common/Empty';
@@ -17,9 +20,6 @@ import { Eye, User } from '@/components/icons';
import { FilterButtons } from '@/components/input/FilterButtons';
import { Lightning } from '@/components/svg';
import { BROWSERS, OS_NAMES } from '@/lib/constants';
import { Column, Heading, Row, SearchField, Text } from '@umami/react-zen';
import { useMemo, useState } from 'react';
import { List, type RowComponentProps } from 'react-window';
const TYPE_ALL = 'all';
const TYPE_PAGEVIEW = 'pageview';
@@ -136,9 +136,7 @@ export function RealtimeLog({ data }: { data: any }) {
<Text wrap="nowrap">{getTime(row)}</Text>
</Row>
<IconLabel icon={getIcon(row)} style={{ minWidth: 0, flex: 1 }}>
<Text truncate>
{getDetail(row)}
</Text>
<Text truncate>{getDetail(row)}</Text>
</IconLabel>
</Row>
);
@@ -1,4 +1,6 @@
'use client';
import { Button, Column, Tab, TabList, TabPanel, Tabs } from '@umami/react-zen';
import { type Key, useState } from 'react';
import { SessionModal } from '@/app/(main)/websites/[websiteId]/sessions/SessionModal';
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
@@ -6,8 +8,6 @@ import { Panel } from '@/components/common/Panel';
import { useMessages, useSubscription, useWebsite } from '@/components/hooks';
import { Video } from '@/components/icons';
import { getItem, setItem } from '@/lib/storage';
import { Button, Column, Tab, TabList, TabPanel, Tabs } from '@umami/react-zen';
import { type Key, useState } from 'react';
import { ReplayModal } from './ReplayModal';
import { ReplaysDataTable } from './ReplaysDataTable';
import { SavedReplaysDataTable } from './SavedReplaysDataTable';
@@ -1,8 +1,8 @@
import { Button, DataColumn, DataTable, type DataTableProps, Icon } from '@umami/react-zen';
import { Play } from 'lucide-react';
import Link from '@/components/common/Link';
import { Avatar } from '@/components/common/Avatar';
import { DateDistance } from '@/components/common/DateDistance';
import Link from '@/components/common/Link';
import { TypeIcon } from '@/components/common/TypeIcon';
import { useFormat, useMessages, useNavigation } from '@/components/hooks';
@@ -1,7 +1,7 @@
'use client';
import { useMobile } from '@/components/hooks';
import { Column } from '@umami/react-zen';
import { useEffect, useRef, useState } from 'react';
import { useMobile } from '@/components/hooks';
import 'rrweb-player/dist/style.css';
export function ReplayPlayer({ events }: { events: any[] }) {
@@ -1,8 +1,8 @@
import { DataColumn, DataTable, type DataTableProps, Row } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { SegmentDeleteButton } from '@/app/(main)/websites/[websiteId]/segments/SegmentDeleteButton';
import { SegmentEditButton } from '@/app/(main)/websites/[websiteId]/segments/SegmentEditButton';
import { DateDistance } from '@/components/common/DateDistance';
import Link from '@/components/common/Link';
import { useMessages, useNavigation } from '@/components/hooks';
export function SegmentsTable(props: DataTableProps) {
@@ -37,7 +37,10 @@ export function SessionDataPivotTable({
return [];
}, [providedPropertyKeys]);
const tableMinWidth = useMemo(() => `${72 + 220 + 180 + propertyKeys.length * 160}px`, [propertyKeys.length]);
const tableMinWidth = useMemo(
() => `${72 + 220 + 180 + propertyKeys.length * 160}px`,
[propertyKeys.length],
);
const tableData = useMemo(() => {
if (!pivotQuery.data?.data) return [];
@@ -120,7 +123,9 @@ export function SessionDataPivotTable({
{isMobile ? (
renderTable('cards')
) : (
<div style={{ width: '100%', maxWidth: '100%', overflowX: 'auto', overflowY: 'hidden' }}>
<div
style={{ width: '100%', maxWidth: '100%', overflowX: 'auto', overflowY: 'hidden' }}
>
<div style={{ width: tableMinWidth, minWidth: tableMinWidth }}>
{renderTable('table')}
</div>
@@ -1,12 +1,12 @@
'use client';
import { Column, DataColumn, DataTable, Grid, Row, Text } from '@umami/react-zen';
import { useMemo } from 'react';
import { DistributionBarChart } from '@/components/charts/DistributionBarChart';
import { Empty } from '@/components/common/Empty';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useMessages, useMobile, useSessionDataActivityStatsQuery } from '@/components/hooks';
import { formatLongNumber } from '@/lib/format';
import type { PropertyFilter } from '@/lib/types';
import { Column, DataColumn, DataTable, Grid, Row, Text } from '@umami/react-zen';
import { useMemo } from 'react';
export function SessionPropertyChart({
websiteId,
@@ -34,7 +34,12 @@ export function SessionModal({ websiteId, ...props }: SessionModalProps) {
<Dialog variant="sheet" className="rounded-lg">
{({ close }) => (
<Column padding="10">
<SessionProfile websiteId={websiteId} sessionId={session} showReplays={!isSharePage} onClose={() => close()} />
<SessionProfile
websiteId={websiteId}
sessionId={session}
showReplays={!isSharePage}
onClose={() => close()}
/>
</Column>
)}
</Dialog>
@@ -1,7 +1,4 @@
'use client';
import { Avatar } from '@/components/common/Avatar';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useMessages, useWebsiteSessionQuery } from '@/components/hooks';
import {
Button,
Column,
@@ -14,6 +11,9 @@ import {
TextField,
} from '@umami/react-zen';
import { X } from 'lucide-react';
import { Avatar } from '@/components/common/Avatar';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useMessages, useWebsiteSessionQuery } from '@/components/hooks';
import { SessionActivity } from './SessionActivity';
import { SessionData } from './SessionData';
import { SessionInfo } from './SessionInfo';
@@ -1,15 +1,19 @@
import { Column, ComboBox, Grid, Label, ListItem, Row } from '@umami/react-zen';
import { useMemo, useState } from 'react';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useMessages, usePropertyFieldsQuery, useSessionDataPropertiesQuery } from '@/components/hooks';
import { DATA_TYPE } from '@/lib/constants';
import type { PropertyFilter } from '@/lib/types';
import { Panel } from '@/components/common/Panel';
import {
useMessages,
usePropertyFieldsQuery,
useSessionDataPropertiesQuery,
} from '@/components/hooks';
import { PropertyChart } from '@/components/property-data/PropertyChart';
import { PropertyDateChart } from '@/components/property-data/PropertyDateChart';
import { PropertyFilterBar } from '@/components/property-data/PropertyFilterBar';
import { PropertyFilterButton } from '@/components/property-data/PropertyFilterButton';
import { PropertyNumericChart } from '@/components/property-data/PropertyNumericChart';
import { Panel } from '@/components/common/Panel';
import { DATA_TYPE } from '@/lib/constants';
import type { PropertyFilter } from '@/lib/types';
import { SessionDataPivotTable } from '../session-data/SessionDataPivotTable';
import { SessionPropertyChart } from '../session-data/SessionPropertyChart';
@@ -111,7 +115,9 @@ export function SessionProperties({ websiteId }: { websiteId: string }) {
)}
</Grid>
)}
{propertyName && <PropertyFilterBar filters={propertyFilters} onChange={setPropertyFilters} />}
{propertyName && (
<PropertyFilterBar filters={propertyFilters} onChange={setPropertyFilters} />
)}
{propertyName && selectedProperty?.dataType === DATA_TYPE.number && (
<PropertyNumericChart
source="session"
@@ -1,7 +1,7 @@
import { DataColumn, DataTable, type DataTableProps } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { Avatar } from '@/components/common/Avatar';
import { DateDistance } from '@/components/common/DateDistance';
import Link from '@/components/common/Link';
import { TypeIcon } from '@/components/common/TypeIcon';
import { useFormat, useMessages } from '@/components/hooks';
@@ -17,7 +17,11 @@ export function SessionsTable({
<DataTable {...props}>
<DataColumn id="id" label={t(labels.session)} width="100px">
{(row: any) => (
<Link href={getSessionHref ? getSessionHref(row) : `/websites/${websiteId}/sessions/${row.id}`}>
<Link
href={
getSessionHref ? getSessionHref(row) : `/websites/${websiteId}/sessions/${row.id}`
}
>
<Avatar seed={row.id} size={32} />
</Link>
)}
@@ -137,21 +137,21 @@ export function ShareEditForm({
<TextField autoComplete="off" autoFocus={!isEditing} />
</FormField>
<Grid columns={{ base: 'repeat(2, 1fr)', lg: 'repeat(3, 1fr)' }} gap="3">
<FormField label={t(labels.filters)} name="allowFilter">
<Switch
isSelected={watch('allowFilter')}
onChange={value => setValue('allowFilter', value, { shouldDirty: true })}
>
{t(labels.filtersEnabled)}
</Switch>
</FormField>
<FormField label={t(labels.theme)} name="theme">
<ThemeModeSelector
value={watch('theme')}
includeSystem
onChange={value => setValue('theme', value, { shouldDirty: true })}
/>
</FormField>
<FormField label={t(labels.filters)} name="allowFilter">
<Switch
isSelected={watch('allowFilter')}
onChange={value => setValue('allowFilter', value, { shouldDirty: true })}
>
{t(labels.filtersEnabled)}
</Switch>
</FormField>
<FormField label={t(labels.theme)} name="theme">
<ThemeModeSelector
value={watch('theme')}
includeSystem
onChange={value => setValue('theme', value, { shouldDirty: true })}
/>
</FormField>
</Grid>
<ListSeparator />
<Grid columns={{ base: 'repeat(2, 1fr)', lg: 'repeat(3, 1fr)' }} gap="3">
@@ -93,14 +93,18 @@ export function WebsiteReplaySettings({ websiteId }: { websiteId: string }) {
const previous = replayEnabled;
setReplayEnabled(value);
await saveRecorderConfig(getNextConfig({ replayEnabled: value }), () => setReplayEnabled(previous));
await saveRecorderConfig(getNextConfig({ replayEnabled: value }), () =>
setReplayEnabled(previous),
);
};
const handleHeatmapToggle = async (value: boolean) => {
const previous = heatmapEnabled;
setHeatmapEnabled(value);
await saveRecorderConfig(getNextConfig({ heatmapEnabled: value }), () => setHeatmapEnabled(previous));
await saveRecorderConfig(getNextConfig({ heatmapEnabled: value }), () =>
setHeatmapEnabled(previous),
);
};
const handleSave = async () => {
@@ -1,6 +1,6 @@
import { Row } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { IconLabel } from '@/components/common/IconLabel';
import Link from '@/components/common/Link';
import { PageHeader } from '@/components/common/PageHeader';
import { useMessages, useNavigation, useWebsite } from '@/components/hooks';
import { ArrowLeft, Globe } from '@/components/icons';
@@ -34,7 +34,14 @@ export function WebsiteTrackingCode({
<Column gap>
<Label>{t(labels.trackingCode)}</Label>
<Text color="muted">{t(messages.trackingCode)}</Text>
<TextField value={code} isReadOnly allowCopy asTextArea resize="none" className="code-textarea" />
<TextField
value={code}
isReadOnly
allowCopy
asTextArea
resize="none"
className="code-textarea"
/>
</Column>
);
}
+2 -8
View File
@@ -8,10 +8,7 @@ import { anyObjectParam, filterParams, pagingParams } from '@/lib/schema';
import { canUpdateBoard, canViewBoard } from '@/permissions';
import { createShare, getSharesByEntityId } from '@/queries/prisma';
export async function GET(
request: Request,
{ params }: { params: Promise<{ boardId: string }> },
) {
export async function GET(request: Request, { params }: { params: Promise<{ boardId: string }> }) {
const schema = z.object({
...filterParams,
...pagingParams,
@@ -39,10 +36,7 @@ export async function GET(
return json(data);
}
export async function POST(
request: Request,
{ params }: { params: Promise<{ boardId: string }> },
) {
export async function POST(request: Request, { params }: { params: Promise<{ boardId: string }> }) {
const schema = z.object({
name: z.string().max(200),
parameters: anyObjectParam.optional(),
+1 -1
View File
@@ -1,7 +1,7 @@
import { z } from 'zod';
import { parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { createBoard, getBoard, updateBoard } from '@/queries/prisma';
import { z } from 'zod';
export async function GET(request: Request) {
const { auth, error } = await parseRequest(request);
+2 -8
View File
@@ -8,10 +8,7 @@ import { anyObjectParam, filterParams, pagingParams } from '@/lib/schema';
import { canUpdateLink, canViewLink } from '@/permissions';
import { createShare, getSharesByEntityId } from '@/queries/prisma';
export async function GET(
request: Request,
{ params }: { params: Promise<{ linkId: string }> },
) {
export async function GET(request: Request, { params }: { params: Promise<{ linkId: string }> }) {
const schema = z.object({
...filterParams,
...pagingParams,
@@ -39,10 +36,7 @@ export async function GET(
return json(data);
}
export async function POST(
request: Request,
{ params }: { params: Promise<{ linkId: string }> },
) {
export async function POST(request: Request, { params }: { params: Promise<{ linkId: string }> }) {
const schema = z.object({
name: z.string().max(200),
parameters: anyObjectParam.optional(),
+2 -8
View File
@@ -8,10 +8,7 @@ import { anyObjectParam, filterParams, pagingParams } from '@/lib/schema';
import { canUpdatePixel, canViewPixel } from '@/permissions';
import { createShare, getSharesByEntityId } from '@/queries/prisma';
export async function GET(
request: Request,
{ params }: { params: Promise<{ pixelId: string }> },
) {
export async function GET(request: Request, { params }: { params: Promise<{ pixelId: string }> }) {
const schema = z.object({
...filterParams,
...pagingParams,
@@ -39,10 +36,7 @@ export async function GET(
return json(data);
}
export async function POST(
request: Request,
{ params }: { params: Promise<{ pixelId: string }> },
) {
export async function POST(request: Request, { params }: { params: Promise<{ pixelId: string }> }) {
const schema = z.object({
name: z.string().max(200),
parameters: anyObjectParam.optional(),
+1 -1
View File
@@ -3,8 +3,8 @@ import { z } from 'zod';
import { REALTIME_RANGE } from '@/lib/constants';
import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response';
import { canViewWebsiteSection } from '@/permissions';
import { filterParams, timezoneParam, unitParam } from '@/lib/schema';
import { canViewWebsiteSection } from '@/permissions';
import { getRealtimeData } from '@/queries/sql';
export async function GET(
+6 -6
View File
@@ -1,11 +1,11 @@
import { isbot } from 'isbot';
import { serializeError } from 'serialize-error';
import { z } from 'zod';
import { HEATMAP_EVENT_TYPE } from '@/lib/constants';
import { secret } from '@/lib/crypto';
import { getClientInfo, hasBlockedIp } from '@/lib/detect';
import { parseToken } from '@/lib/jwt';
import { fetchAccount, fetchTeam } from '@/lib/load';
import { HEATMAP_EVENT_TYPE } from '@/lib/constants';
import { getRecorderConfig } from '@/lib/recorder';
import { parseRequest } from '@/lib/request';
import { badRequest, forbidden, json, serverError } from '@/lib/response';
@@ -185,15 +185,15 @@ export async function POST(request: Request) {
visitId,
eventType: event.type === 'click' ? HEATMAP_EVENT_TYPE.click : HEATMAP_EVENT_TYPE.scroll,
nodeId: null,
x: event.type === 'click' ? event.x ?? null : null,
y: event.type === 'click' ? event.y ?? null : null,
pageX: event.type === 'click' ? event.pageX ?? null : null,
pageY: event.type === 'click' ? event.pageY ?? null : null,
x: event.type === 'click' ? (event.x ?? null) : null,
y: event.type === 'click' ? (event.y ?? null) : null,
pageX: event.type === 'click' ? (event.pageX ?? null) : null,
pageY: event.type === 'click' ? (event.pageY ?? null) : null,
pageW: event.pageW ?? null,
viewportW: event.viewportW ?? null,
viewportH: event.viewportH ?? null,
pageH: event.pageH ?? null,
scrollPct: event.type === 'scroll' ? event.scrollPct ?? null : null,
scrollPct: event.type === 'scroll' ? (event.scrollPct ?? null) : null,
urlPath: getUrlPath(event.url),
createdAt: new Date(event.timestamp ?? fallbackMs),
replayChunkIndex: null,
@@ -1,5 +1,5 @@
import { parseRequest } from '@/lib/request';
import { getRecorderConfig } from '@/lib/recorder';
import { parseRequest } from '@/lib/request';
import { getWebsite } from '@/queries/prisma';
export async function GET(
+1 -1
View File
@@ -1,6 +1,6 @@
import type { Metadata } from 'next';
import { headers } from 'next/headers';
import { Inter } from 'next/font/google';
import { headers } from 'next/headers';
import { Suspense } from 'react';
import { getBaseUrl } from '@/lib/get-base-url';
import { Providers } from './Providers';
+3 -3
View File
@@ -1,10 +1,10 @@
'use client';
import { useShareTokenQuery } from '@/components/hooks';
import { ENTITY_TYPE } from '@/lib/constants';
import type { ShareParameters, WhiteLabel } from '@/lib/types';
import { Loading } from '@umami/react-zen';
import { usePathname, useRouter } from 'next/navigation';
import { createContext, type ReactNode, useEffect } from 'react';
import { useShareTokenQuery } from '@/components/hooks';
import { ENTITY_TYPE } from '@/lib/constants';
import type { ShareParameters, WhiteLabel } from '@/lib/types';
export interface ShareData {
shareId: string;
@@ -1,7 +1,7 @@
'use client';
import { Icon, Row, Text } from '@umami/react-zen';
import { useShare } from '@/components/hooks';
import { Logo } from '@/components/svg';
import { Icon, Row, Text } from '@umami/react-zen';
const LOGO_SIZE = { sm: 24, md: 32, lg: 40 };
const TEXT_SIZE = { sm: 'sm', md: 'base', lg: 'lg' } as const;
@@ -1,6 +1,6 @@
'use client';
import { useShare } from '@/components/hooks';
import { Column, Row } from '@umami/react-zen';
import { useShare } from '@/components/hooks';
import { ShareBranding } from './ShareBranding';
export function ShareFooter() {
@@ -1,11 +1,3 @@
import { IconLabel } from '@/components/common/IconLabel';
import { useMessages, useNavigation, useShare } from '@/components/hooks';
import { AlignEndHorizontal, Clock, Eye, PanelLeft, Sheet, Tag, User } from '@/components/icons';
import { LanguageButton } from '@/components/input/LanguageButton';
import { PreferencesButton } from '@/components/input/PreferencesButton';
import { allowShareFilter, excludeShareFilterParam, getShareTheme } from '@/lib/share';
import { Funnel, Gauge, Lightning, Magnet, Money, Network, Path, Target } from '@/components/svg';
import { buildPath } from '@/lib/url';
import {
Button,
Column,
@@ -17,7 +9,15 @@ import {
Tooltip,
TooltipTrigger,
} from '@umami/react-zen';
import { IconLabel } from '@/components/common/IconLabel';
import Link from '@/components/common/Link';
import { useMessages, useNavigation, useShare } from '@/components/hooks';
import { AlignEndHorizontal, Clock, Eye, PanelLeft, Sheet, Tag, User } from '@/components/icons';
import { LanguageButton } from '@/components/input/LanguageButton';
import { PreferencesButton } from '@/components/input/PreferencesButton';
import { Funnel, Gauge, Lightning, Magnet, Money, Network, Path, Target } from '@/components/svg';
import { allowShareFilter, excludeShareFilterParam, getShareTheme } from '@/lib/share';
import { buildPath } from '@/lib/url';
import { ShareBranding } from './ShareBranding';
export function ShareNav({
@@ -1,4 +1,7 @@
'use client';
import { Column, Grid, Row, useTheme } from '@umami/react-zen';
import { usePathname, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { BoardViewPage } from '@/app/(main)/boards/[boardId]/BoardViewPage';
import { LinkPage } from '@/app/(main)/links/[linkId]/LinkPage';
import { PixelPage } from '@/app/(main)/pixels/[pixelId]/PixelPage';
@@ -7,10 +10,10 @@ import { BreakdownPage } from '@/app/(main)/websites/[websiteId]/(reports)/break
import { FunnelsPage } from '@/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelsPage';
import { GoalsPage } from '@/app/(main)/websites/[websiteId]/(reports)/goals/GoalsPage';
import { JourneysPage } from '@/app/(main)/websites/[websiteId]/(reports)/journeys/JourneysPage';
import { PerformancePage } from '@/app/(main)/websites/[websiteId]/(reports)/performance/PerformancePage';
import { RetentionPage } from '@/app/(main)/websites/[websiteId]/(reports)/retention/RetentionPage';
import { RevenuePage } from '@/app/(main)/websites/[websiteId]/(reports)/revenue/RevenuePage';
import { UTMPage } from '@/app/(main)/websites/[websiteId]/(reports)/utm/UTMPage';
import { PerformancePage } from '@/app/(main)/websites/[websiteId]/(reports)/performance/PerformancePage';
import { ComparePage } from '@/app/(main)/websites/[websiteId]/compare/ComparePage';
import { EventsPage } from '@/app/(main)/websites/[websiteId]/events/EventsPage';
import { RealtimePage } from '@/app/(main)/websites/[websiteId]/realtime/RealtimePage';
@@ -23,9 +26,6 @@ import { useShare } from '@/components/hooks';
import { MobileMenuButton } from '@/components/input/MobileMenuButton';
import { ENTITY_TYPE } from '@/lib/constants';
import { getShareTheme } from '@/lib/share';
import { Column, Grid, Row, useTheme } from '@umami/react-zen';
import { usePathname, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { ShareFooter } from './ShareFooter';
import { ShareNav } from './ShareNav';
+2 -2
View File
@@ -1,3 +1,5 @@
import { useTheme } from '@umami/react-zen';
import { memo, useCallback, useMemo, useState } from 'react';
import { Chart, type ChartProps } from '@/components/charts/Chart';
import { ChartTooltip } from '@/components/charts/ChartTooltip';
import { useLocale } from '@/components/hooks';
@@ -5,8 +7,6 @@ import { renderNumberLabels } from '@/lib/charts';
import { getThemeColors } from '@/lib/colors';
import { DATE_FORMATS, formatDate } from '@/lib/date';
import { formatLongCurrency, formatLongNumber } from '@/lib/format';
import { useTheme } from '@umami/react-zen';
import { memo, useCallback, useMemo, useState } from 'react';
const MemoChart = memo(Chart);
+30 -23
View File
@@ -127,31 +127,38 @@ function DistributionBarChartComponent({
[chartData.labels, colors, horizontal, stacked],
);
const handleTooltip = useCallback(({ tooltip }: { tooltip: any }) => {
const { opacity, labelColors, dataPoints } = tooltip;
const point = dataPoints?.[0];
const nextTooltip = opacity
? {
title: (point?.label ?? '').toString(),
color: labelColors?.[0]?.backgroundColor,
value: `${formatLongNumber(
Number(horizontal ? point?.raw?.x ?? point?.raw ?? 0 : point?.raw?.y ?? point?.raw ?? 0),
)}${point?.dataset?.label ? ` ${point.dataset.label}` : ''}`,
const handleTooltip = useCallback(
({ tooltip }: { tooltip: any }) => {
const { opacity, labelColors, dataPoints } = tooltip;
const point = dataPoints?.[0];
const nextTooltip = opacity
? {
title: (point?.label ?? '').toString(),
color: labelColors?.[0]?.backgroundColor,
value: `${formatLongNumber(
Number(
horizontal
? (point?.raw?.x ?? point?.raw ?? 0)
: (point?.raw?.y ?? point?.raw ?? 0),
),
)}${point?.dataset?.label ? ` ${point.dataset.label}` : ''}`,
}
: null;
setTooltip(prev => {
if (
prev?.title === nextTooltip?.title &&
prev?.color === nextTooltip?.color &&
prev?.value === nextTooltip?.value
) {
return prev;
}
: null;
setTooltip(prev => {
if (
prev?.title === nextTooltip?.title &&
prev?.color === nextTooltip?.color &&
prev?.value === nextTooltip?.value
) {
return prev;
}
return nextTooltip;
});
}, [horizontal]);
return nextTooltip;
});
},
[horizontal],
);
return (
<>
+6 -6
View File
@@ -54,29 +54,29 @@
background: var(--text-muted);
}
:global([data-theme='dark']) .good {
:global([data-theme="dark"]) .good {
color: #86efac;
background: rgba(22, 163, 74, 0.15);
}
:global([data-theme='dark']) .good .dot {
:global([data-theme="dark"]) .good .dot {
background: #4ade80;
}
:global([data-theme='dark']) .warning {
:global([data-theme="dark"]) .warning {
color: #fde047;
background: rgba(202, 138, 4, 0.15);
}
:global([data-theme='dark']) .warning .dot {
:global([data-theme="dark"]) .warning .dot {
background: #facc15;
}
:global([data-theme='dark']) .danger {
:global([data-theme="dark"]) .danger {
color: #fca5a5;
background: rgba(220, 38, 38, 0.15);
}
:global([data-theme='dark']) .danger .dot {
:global([data-theme="dark"]) .danger .dot {
background: #f87171;
}
+1 -7
View File
@@ -2,13 +2,7 @@ import { Button, Icon } from '@umami/react-zen';
import { useEffect, useRef, useState } from 'react';
import { Check, Copy } from '@/components/icons';
export function CopyButton({
value,
label = 'Copy',
}: {
value: string;
label?: string;
}) {
export function CopyButton({ value, label = 'Copy' }: { value: string; label?: string }) {
const [copied, setCopied] = useState(false);
const timeoutRef = useRef<number | null>(null);
+1 -1
View File
@@ -62,7 +62,7 @@ export function DataGrid({
return stored === 'table' || stored === 'cards' ? stored : null;
});
const displayMode: DisplayMode | undefined = isMobile ? 'cards' : userDisplayMode ?? undefined;
const displayMode: DisplayMode | undefined = isMobile ? 'cards' : (userDisplayMode ?? undefined);
const handleToggleDisplayMode = () => {
const next: DisplayMode = displayMode === 'cards' ? 'table' : 'cards';
+1 -1
View File
@@ -1,6 +1,6 @@
import { Icon, Row, Text } from '@umami/react-zen';
import Link, { type LinkProps } from '@/components/common/Link';
import type { ReactNode } from 'react';
import Link, { type LinkProps } from '@/components/common/Link';
import { ExternalLink as LinkIcon } from '@/components/icons';
export function ExternalLink({
+1 -1
View File
@@ -1,6 +1,6 @@
import { Icon, Row, Text } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { type HTMLAttributes, type ReactNode, useState } from 'react';
import Link from '@/components/common/Link';
import { useMessages, useNavigation } from '@/components/hooks';
import { ExternalLink } from '@/components/icons';
+1 -1
View File
@@ -1,6 +1,6 @@
import { Button, type ButtonProps } from '@umami/react-zen';
import Link from '@/components/common/Link';
import type { ReactNode } from 'react';
import Link from '@/components/common/Link';
import { useLocale } from '@/components/hooks';
export interface LinkButtonProps extends ButtonProps {
+1 -1
View File
@@ -1,6 +1,6 @@
import { Column, Heading, Row, Text } from '@umami/react-zen';
import Link from '@/components/common/Link';
import { IconLabel } from '@/components/common/IconLabel';
import Link from '@/components/common/Link';
interface NavMenuData {
id: string;
+1 -7
View File
@@ -11,13 +11,7 @@ export interface PagerProps {
className?: string;
}
export function Pager({
page,
pageSize,
count,
isCapped,
onPageChange,
}: PagerProps) {
export function Pager({ page, pageSize, count, isCapped, onPageChange }: PagerProps) {
const { t, labels } = useMessages();
const maxPage = pageSize && count ? Math.ceil(+count / +pageSize) : 0;
const lastPage = page === maxPage;
+3 -7
View File
@@ -1,9 +1,9 @@
'use client';
import type { ReactNode } from 'react';
import { Icon } from '@umami/react-zen';
import { ChevronDown, ChevronUp } from '@/components/icons';
import type { ReactNode } from 'react';
import { useNavigation } from '@/components/hooks';
import { ChevronDown, ChevronUp } from '@/components/icons';
type SortDirection = 'asc' | 'desc';
@@ -13,11 +13,7 @@ export interface SortableLabelProps {
defaultDirection?: SortDirection;
}
export function SortableLabel({
label,
sortKey,
defaultDirection = 'asc',
}: SortableLabelProps) {
export function SortableLabel({ label, sortKey, defaultDirection = 'asc' }: SortableLabelProps) {
const { router, query, updateParams } = useNavigation();
const isActive = query.orderBy === sortKey;
const isDescending = query.sortDescending === 'true';
+5 -5
View File
@@ -41,16 +41,16 @@ export * from './queries/useReplaysQuery';
export * from './queries/useReportQuery';
export * from './queries/useReportsQuery';
export * from './queries/useResultQuery';
export * from './queries/useRevenueSessionsQuery';
export * from './queries/useRevenueMetricsQuery';
export * from './queries/useRevenueChartQuery';
export * from './queries/useRevenueMetricsQuery';
export * from './queries/useRevenueSessionsQuery';
export * from './queries/useRevenueStatsQuery';
export * from './queries/useSavedReplaysQuery';
export * from './queries/useSessionActivityQuery';
export * from './queries/useSessionDataPropertiesQuery';
export * from './queries/useSessionDataPivotQuery';
export * from './queries/useSessionDataQuery';
export * from './queries/useSessionDataActivityStatsQuery';
export * from './queries/useSessionDataPivotQuery';
export * from './queries/useSessionDataPropertiesQuery';
export * from './queries/useSessionDataQuery';
export * from './queries/useSessionDataValuesQuery';
export * from './queries/useSessionReplaysQuery';
export * from './queries/useShareTokenQuery';
@@ -3,10 +3,7 @@ import { useApi } from '../useApi';
import { useModified } from '../useModified';
import { usePagedQuery } from '../usePagedQuery';
export function useLinkSharesQuery(
{ linkId }: { linkId: string },
options?: ReactQueryOptions,
) {
export function useLinkSharesQuery({ linkId }: { linkId: string }, options?: ReactQueryOptions) {
const { modified } = useModified('shares');
const { get } = useApi();
@@ -3,10 +3,7 @@ import { useApi } from '../useApi';
import { useModified } from '../useModified';
import { usePagedQuery } from '../usePagedQuery';
export function usePixelSharesQuery(
{ pixelId }: { pixelId: string },
options?: ReactQueryOptions,
) {
export function usePixelSharesQuery({ pixelId }: { pixelId: string }, options?: ReactQueryOptions) {
const { modified } = useModified('shares');
const { get } = useApi();
@@ -20,7 +20,17 @@ export function usePropertyArraySeriesQuery(
return useQuery<EventDataSeriesPoint[]>({
queryKey: [
`websites:${source}-data:array-series`,
{ websiteId, propertyName, eventName, propertyFilters, startAt, endAt, unit, timezone, ...params },
{
websiteId,
propertyName,
eventName,
propertyFilters,
startAt,
endAt,
unit,
timezone,
...params,
},
],
queryFn: () =>
get(
@@ -21,7 +21,18 @@ export function usePropertyNumericSeriesQuery(
return useQuery<any>({
queryKey: [
`websites:${source}-data:numeric-series`,
{ websiteId, propertyName, metric, eventName, propertyFilters, startAt, endAt, unit, timezone, ...params },
{
websiteId,
propertyName,
metric,
eventName,
propertyFilters,
startAt,
endAt,
unit,
timezone,
...params,
},
],
queryFn: () =>
get(
@@ -20,7 +20,17 @@ export function usePropertySeriesQuery(
return useQuery<EventDataSeriesPoint[]>({
queryKey: [
`websites:${source}-data:property-series`,
{ websiteId, propertyName, eventName, propertyFilters, startAt, endAt, unit, timezone, ...params },
{
websiteId,
propertyName,
eventName,
propertyFilters,
startAt,
endAt,
unit,
timezone,
...params,
},
],
queryFn: () =>
get(
@@ -21,7 +21,18 @@ export function usePropertyValuesQuery(
return useQuery<any>({
queryKey: [
`websites:${source}-data:values`,
{ websiteId, propertyName, dataType, eventName, propertyFilters, startAt, endAt, unit, timezone, ...params },
{
websiteId,
propertyName,
dataType,
eventName,
propertyFilters,
startAt,
endAt,
unit,
timezone,
...params,
},
],
queryFn: () =>
get(

Some files were not shown because too many files have changed in this diff Show More