Pixel/links development. New validations folder. More refactoring.

This commit is contained in:
Mike Cao 2025-08-14 23:48:11 -07:00
parent 88639dfe83
commit 247e14646b
136 changed files with 1395 additions and 516 deletions

View file

@ -1,4 +1,4 @@
import { Tooltip, TooltipTrigger, Text, Focusable } from '@umami/react-zen';
import { Text } from '@umami/react-zen';
import { formatDistanceToNow } from 'date-fns';
import { useLocale, useTimezone } from '@/components/hooks';
@ -7,11 +7,8 @@ export function DateDistance({ date }: { date: Date }) {
const { dateLocale } = useLocale();
return (
<TooltipTrigger delay={0}>
<Focusable>
<Text>{formatDistanceToNow(date, { addSuffix: true, locale: dateLocale })}</Text>
</Focusable>
<Tooltip>{formatTimezoneDate(date.toISOString(), 'PPPpp')}</Tooltip>
</TooltipTrigger>
<Text title={formatTimezoneDate(date.toISOString(), 'PPPpp')}>
{formatDistanceToNow(date, { addSuffix: true, locale: dateLocale })}
</Text>
);
}

View file

@ -0,0 +1,18 @@
import Link from 'next/link';
import { Icon, Row, Text } from '@umami/react-zen';
import { ExternalLink as LinkIcon } from '@/components/icons';
export function ExternalLink({ href, children, ...props }: Icon) {
return (
<Row alignItems="center" overflow="hidden" gap>
<Text title={href} truncate>
<Link {...props} href={href} target="_blank">
{children}
</Link>
</Text>
<Icon size="sm" strokeColor="muted">
<LinkIcon />
</Icon>
</Row>
);
}

View file

@ -1,8 +1,11 @@
'use client';
// Query hooks
export * from './queries/useActiveUsersQuery';
export * from './queries/useEventDataQuery';
export * from './queries/useDeleteQuery';
export * from './queries/useEventDataEventsQuery';
export * from './queries/useEventDataPropertiesQuery';
export * from './queries/useEventDataQuery';
export * from './queries/useEventDataValuesQuery';
export * from './queries/useLinkQuery';
export * from './queries/useLinksQuery';
@ -10,41 +13,51 @@ export * from './queries/useLoginQuery';
export * from './queries/usePixelQuery';
export * from './queries/usePixelsQuery';
export * from './queries/useRealtimeQuery';
export * from './queries/useResultQuery';
export * from './queries/useReportQuery';
export * from './queries/useReportsQuery';
export * from './queries/useResultQuery';
export * from './queries/useSessionActivityQuery';
export * from './queries/useSessionDataQuery';
export * from './queries/useSessionDataPropertiesQuery';
export * from './queries/useSessionDataQuery';
export * from './queries/useSessionDataValuesQuery';
export * from './queries/useWebsiteSessionQuery';
export * from './queries/useWebsiteSessionsQuery';
export * from './queries/useWebsiteSessionsWeeklyQuery';
export * from './queries/useShareTokenQuery';
export * from './queries/useTeamMembersQuery';
export * from './queries/useTeamQuery';
export * from './queries/useTeamWebsitesQuery';
export * from './queries/useTeamsQuery';
export * from './queries/useUpdateQuery';
export * from './queries/useUserQuery';
export * from './queries/useUserTeamsQuery';
export * from './queries/useUserWebsitesQuery';
export * from './queries/useTeamWebsitesQuery';
export * from './queries/useTeamMembersQuery';
export * from './queries/useUserQuery';
export * from './queries/useUsersQuery';
export * from './queries/useWebsiteCohortQuery';
export * from './queries/useWebsiteCohortsQuery';
export * from './queries/useWebsiteEventsQuery';
export * from './queries/useWebsiteEventsSeriesQuery';
export * from './queries/useWebsiteExpandedMetricsQuery';
export * from './queries/useWebsiteMetricsQuery';
export * from './queries/useWebsitePageviewsQuery';
export * from './queries/useWebsiteQuery';
export * from './queries/useWebsiteSegmentQuery';
export * from './queries/useWebsiteSegmentsQuery';
export * from './queries/useWebsitesQuery';
export * from './queries/useWebsiteEventsQuery';
export * from './queries/useWebsiteEventsSeriesQuery';
export * from './queries/useWebsiteMetricsQuery';
export * from './queries/useWebsiteExpandedMetricsQuery';
export * from './queries/useWebsiteSessionQuery';
export * from './queries/useWebsiteSessionStatsQuery';
export * from './queries/useWebsiteSessionsQuery';
export * from './queries/useWebsiteSessionsWeeklyQuery';
export * from './queries/useWebsiteStatsQuery';
export * from './queries/useWebsiteValuesQuery';
export * from './queries/useWebsitesQuery';
// Regular hooks
export * from './useApi';
export * from './useConfig';
export * from './useCountryNames';
export * from './useDateParameters';
export * from './useDateRange';
export * from './useDocumentClick';
export * from './useEscapeKey';
export * from './useFields';
export * from './useFilterParameters';
export * from './useFilters';
export * from './useForceUpdate';
export * from './useFormat';
@ -55,6 +68,7 @@ export * from './useMessages';
export * from './useModified';
export * from './useNavigation';
export * from './usePagedQuery';
export * from './usePageParameters';
export * from './useRegionNames';
export * from './useSticky';
export * from './useTeam';

View file

@ -1,7 +1,7 @@
import { useApi } from '../useApi';
import { ReactQueryOptions } from '@/lib/types';
export function useActyiveUsersQuery(websiteId: string, options?: ReactQueryOptions<any>) {
export function useActyiveUsersQuery(websiteId: string, options?: ReactQueryOptions) {
const { get, useQuery } = useApi();
return useQuery<any>({
queryKey: ['websites:active', websiteId],

View file

@ -3,7 +3,7 @@ import { useFilterParameters } from '../useFilterParameters';
import { useDateParameters } from '../useDateParameters';
import { ReactQueryOptions } from '@/lib/types';
export function useEventDataEventsQuery(websiteId: string, options?: ReactQueryOptions<any>) {
export function useEventDataEventsQuery(websiteId: string, options?: ReactQueryOptions) {
const { get, useQuery } = useApi();
const date = useDateParameters(websiteId);
const filters = useFilterParameters();

View file

@ -3,7 +3,7 @@ import { useFilterParameters } from '../useFilterParameters';
import { useDateParameters } from '../useDateParameters';
import { ReactQueryOptions } from '@/lib/types';
export function useEventDataPropertiesQuery(websiteId: string, options?: ReactQueryOptions<any>) {
export function useEventDataPropertiesQuery(websiteId: string, options?: ReactQueryOptions) {
const { get, useQuery } = useApi();
const date = useDateParameters(websiteId);
const filters = useFilterParameters();

View file

@ -3,11 +3,7 @@ import { useFilterParameters } from '../useFilterParameters';
import { useDateParameters } from '../useDateParameters';
import { ReactQueryOptions } from '@/lib/types';
export function useEventDataQuery(
websiteId: string,
eventId: string,
options?: ReactQueryOptions<any>,
) {
export function useEventDataQuery(websiteId: string, eventId: string, options?: ReactQueryOptions) {
const { get, useQuery } = useApi();
const date = useDateParameters(websiteId);
const params = useFilterParameters();

View file

@ -7,7 +7,7 @@ export function useEventDataValuesQuery(
websiteId: string,
eventName: string,
propertyName: string,
options?: ReactQueryOptions<any>,
options?: ReactQueryOptions,
) {
const { get, useQuery } = useApi();
const date = useDateParameters(websiteId);

View file

@ -3,13 +3,15 @@ import { usePagedQuery } from '../usePagedQuery';
import { useModified } from '../useModified';
import { ReactQueryOptions } from '@/lib/types';
export function useLinksQuery({ teamId }: { teamId?: string }, options?: ReactQueryOptions<any>) {
export function useLinksQuery({ teamId }: { teamId?: string }, options?: ReactQueryOptions) {
const { modified } = useModified('links');
const { get } = useApi();
return usePagedQuery({
queryKey: ['links', { teamId, modified }],
queryFn: async () => get(teamId ? `/teams/${teamId}/links` : '/links'),
queryFn: pageParams => {
return get(teamId ? `/teams/${teamId}/links` : '/links', pageParams);
},
...options,
});
}

View file

@ -3,17 +3,15 @@ import { usePagedQuery } from '../usePagedQuery';
import { useModified } from '../useModified';
import { ReactQueryOptions } from '@/lib/types';
export function usePixelsQuery(
{ websiteId, type }: { websiteId: string; type?: string },
options?: ReactQueryOptions<any>,
) {
const { modified } = useModified(`pixels:${type}`);
export function usePixelsQuery({ teamId }: { teamId?: string }, options?: ReactQueryOptions) {
const { modified } = useModified('pixels');
const { get } = useApi();
return usePagedQuery({
queryKey: ['pixels', { websiteId, type, modified }],
queryFn: async () => get('/pixels', { websiteId, type }),
enabled: !!websiteId && !!type,
queryKey: ['pixels', { teamId, modified }],
queryFn: pageParams => {
return get(teamId ? `/teams/${teamId}/pixels` : '/pixels', pageParams);
},
...options,
});
}

View file

@ -5,7 +5,7 @@ import { ReactQueryOptions } from '@/lib/types';
export function useReportsQuery(
{ websiteId, type }: { websiteId: string; type?: string },
options?: ReactQueryOptions<any>,
options?: ReactQueryOptions,
) {
const { modified } = useModified(`reports:${type}`);
const { get } = useApi();

View file

@ -3,7 +3,7 @@ import { useFilterParameters } from '../useFilterParameters';
import { useDateParameters } from '../useDateParameters';
import { ReactQueryOptions } from '@/lib/types';
export function useSessionDataPropertiesQuery(websiteId: string, options?: ReactQueryOptions<any>) {
export function useSessionDataPropertiesQuery(websiteId: string, options?: ReactQueryOptions) {
const { get, useQuery } = useApi();
const date = useDateParameters(websiteId);
const filters = useFilterParameters();

View file

@ -6,7 +6,7 @@ import { ReactQueryOptions } from '@/lib/types';
export function useSessionDataValuesQuery(
websiteId: string,
propertyName: string,
options?: ReactQueryOptions<any>,
options?: ReactQueryOptions,
) {
const { get, useQuery } = useApi();
const date = useDateParameters(websiteId);

View file

@ -3,7 +3,7 @@ import { useModified } from '@/components/hooks';
import { keepPreviousData } from '@tanstack/react-query';
import { ReactQueryOptions } from '@/lib/types';
export function useTeamQuery(teamId: string, options?: ReactQueryOptions<any>) {
export function useTeamQuery(teamId: string, options?: ReactQueryOptions) {
const { get, useQuery } = useApi();
const { modified } = useModified(`teams:${teamId}`);

View file

@ -3,7 +3,7 @@ import { useModified } from '@/components/hooks';
import { keepPreviousData } from '@tanstack/react-query';
import { ReactQueryOptions } from '@/lib/types';
export function useUserQuery(userId: string, options?: ReactQueryOptions<any>) {
export function useUserQuery(userId: string, options?: ReactQueryOptions) {
const { get, useQuery } = useApi();
const { modified } = useModified(`user:${userId}`);

View file

@ -1,6 +1,5 @@
import { useApi } from '../useApi';
import { usePagedQuery } from '../usePagedQuery';
import { useLoginQuery } from './useLoginQuery';
import { useModified } from '../useModified';
import { ReactQueryOptions } from '@/lib/types';
@ -10,16 +9,22 @@ export function useUserWebsitesQuery(
options?: ReactQueryOptions,
) {
const { get } = useApi();
const { user } = useLoginQuery();
const { modified } = useModified(`websites`);
return usePagedQuery({
queryKey: ['websites', { userId, teamId, modified, ...params }],
queryFn: pageParams => {
return get(teamId ? `/teams/${teamId}/websites` : `/users/${userId || user.id}/websites`, {
...pageParams,
...params,
});
return get(
teamId
? `/teams/${teamId}/websites`
: userId
? `/users/${userId}/websites`
: '/me/websites',
{
...pageParams,
...params,
},
);
},
...options,
});

View file

@ -0,0 +1,21 @@
import { useApi } from '../useApi';
import { useModified } from '@/components/hooks';
import { keepPreviousData } from '@tanstack/react-query';
import { ReactQueryOptions } from '@/lib/types';
export function useWebsiteCohortQuery(
websiteId: string,
cohortId: string,
options?: ReactQueryOptions,
) {
const { get, useQuery } = useApi();
const { modified } = useModified(`cohorts`);
return useQuery({
queryKey: ['website:cohorts', { websiteId, cohortId, modified }],
queryFn: () => get(`/websites/${websiteId}/cohorts/${cohortId}`),
enabled: !!(websiteId && cohortId),
placeholderData: keepPreviousData,
...options,
});
}

View file

@ -0,0 +1,25 @@
import { useApi } from '../useApi';
import { useModified } from '@/components/hooks';
import { keepPreviousData } from '@tanstack/react-query';
import { ReactQueryOptions } from '@/lib/types';
import { useFilterParameters } from '@/components/hooks/useFilterParameters';
export function useWebsiteCohortsQuery(
websiteId: string,
params?: Record<string, string>,
options?: ReactQueryOptions,
) {
const { get, useQuery } = useApi();
const { modified } = useModified(`cohorts`);
const filters = useFilterParameters();
return useQuery({
queryKey: ['website:cohorts', { websiteId, modified, ...filters, ...params }],
queryFn: pageParams => {
return get(`/websites/${websiteId}/cohorts`, { ...pageParams, ...filters, ...params });
},
enabled: !!websiteId,
placeholderData: keepPreviousData,
...options,
});
}

View file

@ -12,7 +12,7 @@ const EVENT_TYPES = {
export function useWebsiteEventsQuery(
websiteId: string,
params?: Record<string, any>,
options?: ReactQueryOptions<any>,
options?: ReactQueryOptions,
) {
const { get } = useApi();
const date = useDateParameters(websiteId);

View file

@ -3,7 +3,7 @@ import { useFilterParameters } from '../useFilterParameters';
import { useDateParameters } from '../useDateParameters';
import { ReactQueryOptions } from '@/lib/types';
export function useWebsiteEventsSeriesQuery(websiteId: string, options?: ReactQueryOptions<any>) {
export function useWebsiteEventsSeriesQuery(websiteId: string, options?: ReactQueryOptions) {
const { get, useQuery } = useApi();
const date = useDateParameters(websiteId);
const filters = useFilterParameters();

View file

@ -3,7 +3,7 @@ import { useModified } from '@/components/hooks';
import { keepPreviousData } from '@tanstack/react-query';
import { ReactQueryOptions } from '@/lib/types';
export function useWebsiteQuery(websiteId: string, options?: ReactQueryOptions<any>) {
export function useWebsiteQuery(websiteId: string, options?: ReactQueryOptions) {
const { get, useQuery } = useApi();
const { modified } = useModified(`website:${websiteId}`);

View file

@ -6,7 +6,7 @@ import { ReactQueryOptions } from '@/lib/types';
export function useWebsiteSegmentQuery(
websiteId: string,
segmentId: string,
options?: ReactQueryOptions<any>,
options?: ReactQueryOptions,
) {
const { get, useQuery } = useApi();
const { modified } = useModified(`segments`);

View file

@ -7,7 +7,7 @@ import { useFilterParameters } from '@/components/hooks/useFilterParameters';
export function useWebsiteSegmentsQuery(
websiteId: string,
params?: Record<string, string>,
options?: ReactQueryOptions<any>,
options?: ReactQueryOptions,
) {
const { get, useQuery } = useApi();
const { modified } = useModified(`segments`);

View file

@ -1,25 +1,24 @@
import { ReactNode } from 'react';
import { Button, Icon, Modal, DialogTrigger, TooltipTrigger, Tooltip } from '@umami/react-zen';
import { Button, Icon, Modal, Text, DialogTrigger } from '@umami/react-zen';
export function ActionButton({
onClick,
icon,
tooltip,
title,
children,
}: {
onSave?: () => void;
icon?: ReactNode;
tooltip?: string;
children?: React.ReactNode;
title?: string;
children?: ReactNode;
}) {
return (
<DialogTrigger>
<TooltipTrigger delay={0}>
<Text title={title}>
<Button variant="quiet" onPress={onClick}>
<Icon>{icon}</Icon>
</Button>
<Tooltip>{tooltip}</Tooltip>
</TooltipTrigger>
</Text>
<Modal>{children}</Modal>
</DialogTrigger>
);

View file

@ -352,6 +352,7 @@ export const labels = defineMessages({
saveCohort: { id: 'label.save-cohort', defaultMessage: 'Save as cohort' },
analysis: { id: 'label.analysis', defaultMessage: 'Analysis' },
destinationUrl: { id: 'label.destination-url', defaultMessage: 'Destination URL' },
audience: { id: 'label.audience', defaultMessage: 'Audience' },
});
export const messages = defineMessages({