Merge branch 'dev' into boards

# Conflicts:
#	.gitignore
#	package.json
#	pnpm-lock.yaml
#	prisma/migrations/16_boards/migration.sql
#	prisma/schema.prisma
#	src/app/(main)/MobileNav.tsx
#	src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx
#	src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx
#	src/components/common/SideMenu.tsx
#	src/lib/types.ts
This commit is contained in:
Mike Cao committed 2026-02-05 20:05:25 -08:00
commit c3e0290e65
150 files changed
+3028 -787

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
import { useContext } from 'react';
import { ShareContext } from '@/app/share/ShareProvider';
export function useShare() {
return useContext(ShareContext);
}
+2
View File
@@ -4,6 +4,7 @@
export * from './context/useBoard';
export * from './context/useLink';
export * from './context/usePixel';
export * from './context/useShare';
export * from './context/useTeam';
export * from './context/useUser';
export * from './context/useWebsite';
@@ -54,6 +55,7 @@ export * from './queries/useWebsiteSegmentsQuery';
export * from './queries/useWebsiteSessionQuery';
export * from './queries/useWebsiteSessionStatsQuery';
export * from './queries/useWebsiteSessionsQuery';
export * from './queries/useWebsiteSharesQuery';
export * from './queries/useWebsiteStatsQuery';
export * from './queries/useWebsitesQuery';
export * from './queries/useWebsiteValuesQuery';
@@ -0,0 +1,37 @@
import type { UseQueryOptions } from '@tanstack/react-query';
import { useDateParameters } from '@/components/hooks/useDateParameters';
import { useApi } from '../useApi';
import { useFilterParameters } from '../useFilterParameters';
export interface EventStatsData {
events: number;
visitors: number;
visits: number;
uniqueEvents: number;
}
type EventStatsApiResponse = {
data: EventStatsData;
};
export function useEventStatsQuery(
{ websiteId }: { websiteId: string },
options?: UseQueryOptions<EventStatsApiResponse, Error, EventStatsData>,
) {
const { get, useQuery } = useApi();
const { startAt, endAt } = useDateParameters();
const filters = useFilterParameters();
return useQuery<EventStatsApiResponse, Error, EventStatsData>({
queryKey: ['websites:events:stats', { websiteId, startAt, endAt, ...filters }],
queryFn: () =>
get(`/websites/${websiteId}/events/stats`, {
startAt,
endAt,
...filters,
}),
select: response => response.data,
enabled: !!websiteId,
...options,
});
}
@@ -10,7 +10,7 @@ export function useResultQuery<T = any>(
) {
const { websiteId, ...parameters } = params;
const { post, useQuery } = useApi();
const { startDate, endDate, timezone } = useDateParameters();
const { startDate, endDate, timezone, unit } = useDateParameters();
const filters = useFilterParameters();
return useQuery<T>({
@@ -22,6 +22,7 @@ export function useResultQuery<T = any>(
startDate,
endDate,
timezone,
unit,
...params,
...filters,
},
@@ -35,6 +36,7 @@ export function useResultQuery<T = any>(
startDate,
endDate,
timezone,
unit,
...parameters,
},
}),
@@ -1,25 +1,22 @@
import { setShareToken, useApp } from '@/store/app';
import { setShare, setShareToken, useApp } from '@/store/app';
import { useApi } from '../useApi';
const selector = (state: { shareToken: string }) => state.shareToken;
const selector = state => state.share;
export function useShareTokenQuery(slug: string): {
shareToken: any;
isLoading?: boolean;
error?: Error;
} {
const shareToken = useApp(selector);
export function useShareTokenQuery(slug: string) {
const share = useApp(selector);
const { get, useQuery } = useApi();
const { isLoading, error } = useQuery({
const query = useQuery({
queryKey: ['share', slug],
queryFn: async () => {
const data = await get(`/share/${slug}`);
setShareToken(data);
setShare(data);
setShareToken({ token: data?.token });
return data;
},
});
return { shareToken, isLoading, error };
return { share, ...query };
}
@@ -19,7 +19,7 @@ export function useWebsiteExpandedMetricsQuery(
options?: ReactQueryOptions<WebsiteExpandedMetricsData>,
) {
const { get, useQuery } = useApi();
const { startAt, endAt, unit, timezone } = useDateParameters();
const { startAt, endAt } = useDateParameters();
const filters = useFilterParameters();
return useQuery<WebsiteExpandedMetricsData>({
@@ -29,8 +29,6 @@ export function useWebsiteExpandedMetricsQuery(
websiteId,
startAt,
endAt,
unit,
timezone,
...filters,
...params,
},
@@ -39,8 +37,6 @@ export function useWebsiteExpandedMetricsQuery(
get(`/websites/${websiteId}/metrics/expanded`, {
startAt,
endAt,
unit,
timezone,
...filters,
...params,
}),
@@ -15,7 +15,7 @@ export function useWebsiteMetricsQuery(
options?: ReactQueryOptions<WebsiteMetricsData>,
) {
const { get, useQuery } = useApi();
const { startAt, endAt, unit, timezone } = useDateParameters();
const { startAt, endAt } = useDateParameters();
const filters = useFilterParameters();
return useQuery<WebsiteMetricsData>({
@@ -25,8 +25,6 @@ export function useWebsiteMetricsQuery(
websiteId,
startAt,
endAt,
unit,
timezone,
...filters,
...params,
},
@@ -35,8 +33,6 @@ export function useWebsiteMetricsQuery(
get(`/websites/${websiteId}/metrics`, {
startAt,
endAt,
unit,
timezone,
...filters,
...params,
}),
@@ -0,0 +1,20 @@
import type { ReactQueryOptions } from '@/lib/types';
import { useApi } from '../useApi';
import { useModified } from '../useModified';
import { usePagedQuery } from '../usePagedQuery';
export function useWebsiteSharesQuery(
{ websiteId }: { websiteId: string },
options?: ReactQueryOptions,
) {
const { modified } = useModified('shares');
const { get } = useApi();
return usePagedQuery({
queryKey: ['websiteShares', { websiteId, modified }],
queryFn: pageParams => {
return get(`/websites/${websiteId}/shares`, pageParams);
},
...options,
});
}
@@ -1,6 +1,5 @@
import type { UseQueryOptions } from '@tanstack/react-query';
import { useDateParameters } from '@/components/hooks/useDateParameters';
import { useDateRange } from '@/components/hooks/useDateRange';
import { useApi } from '../useApi';
import { useFilterParameters } from '../useFilterParameters';
@@ -20,21 +19,16 @@ export interface WebsiteStatsData {
}
export function useWebsiteStatsQuery(
websiteId: string,
{ websiteId, compare }: { websiteId: string; compare?: string },
options?: UseQueryOptions<WebsiteStatsData, Error, WebsiteStatsData>,
) {
const { get, useQuery } = useApi();
const { startAt, endAt, unit, timezone } = useDateParameters();
const { compare } = useDateRange();
const { startAt, endAt } = useDateParameters();
const filters = useFilterParameters();
return useQuery<WebsiteStatsData>({
queryKey: [
'websites:stats',
{ websiteId, startAt, endAt, unit, timezone, compare, ...filters },
],
queryFn: () =>
get(`/websites/${websiteId}/stats`, { startAt, endAt, unit, timezone, compare, ...filters }),
queryKey: ['websites:stats', { websiteId, compare, startAt, endAt, ...filters }],
queryFn: () => get(`/websites/${websiteId}/stats`, { compare, startAt, endAt, ...filters }),
enabled: !!websiteId,
...options,
});
@@ -12,13 +12,12 @@ export function useWeeklyTrafficQuery(websiteId: string, params?: Record<string,
return useQuery({
queryKey: [
'sessions',
{ websiteId, modified, startAt, endAt, unit, timezone, ...params, ...filters },
{ websiteId, modified, startAt, endAt, timezone, ...params, ...filters },
],
queryFn: () => {
return get(`/websites/${websiteId}/sessions/weekly`, {
startAt,
endAt,
unit,
timezone,
...params,
...filters,
+4 -3
View File
@@ -7,13 +7,13 @@ import { getItem } from '@/lib/storage';
export function useDateRange(options: { ignoreOffset?: boolean; timezone?: string } = {}) {
const {
query: { date = '', offset = 0, compare = 'prev' },
query: { date = '', unit = '', offset = 0, compare = 'prev' },
} = useNavigation();
const { locale } = useLocale();
const dateRange = useMemo(() => {
const dateRangeObject = parseDateRange(
date || getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE,
unit,
locale,
options.timezone,
);
@@ -21,12 +21,13 @@ export function useDateRange(options: { ignoreOffset?: boolean; timezone?: strin
return !options.ignoreOffset && offset
? getOffsetDateRange(dateRangeObject, +offset)
: dateRangeObject;
}, [date, offset, options]);
}, [date, unit, offset, options]);
const dateCompare = getCompareDate(compare, dateRange.startDate, dateRange.endDate);
return {
date,
unit,
offset,
compare,
isAllTime: date.endsWith(`:all`),
+134 -15
View File
@@ -1,23 +1,142 @@
import { useMessages } from './useMessages';
export type FieldGroup = 'url' | 'sources' | 'location' | 'environment' | 'utm' | 'other';
export interface Field {
name: string;
filterLabel: string;
label: string;
group: FieldGroup;
}
export function useFields() {
const { formatMessage, labels } = useMessages();
const fields = [
{ name: 'path', type: 'string', label: formatMessage(labels.path) },
{ name: 'query', type: 'string', label: formatMessage(labels.query) },
{ name: 'title', type: 'string', label: formatMessage(labels.pageTitle) },
{ name: 'referrer', type: 'string', label: formatMessage(labels.referrer) },
{ name: 'browser', type: 'string', label: formatMessage(labels.browser) },
{ name: 'os', type: 'string', label: formatMessage(labels.os) },
{ name: 'device', type: 'string', label: formatMessage(labels.device) },
{ name: 'country', type: 'string', label: formatMessage(labels.country) },
{ name: 'region', type: 'string', label: formatMessage(labels.region) },
{ name: 'city', type: 'string', label: formatMessage(labels.city) },
{ name: 'hostname', type: 'string', label: formatMessage(labels.hostname) },
{ name: 'tag', type: 'string', label: formatMessage(labels.tag) },
{ name: 'event', type: 'string', label: formatMessage(labels.event) },
const fields: Field[] = [
{
name: 'path',
filterLabel: formatMessage(labels.path),
label: formatMessage(labels.path),
group: 'url',
},
{
name: 'query',
filterLabel: formatMessage(labels.query),
label: formatMessage(labels.query),
group: 'url',
},
{
name: 'title',
filterLabel: formatMessage(labels.pageTitle),
label: formatMessage(labels.pageTitle),
group: 'url',
},
{
name: 'referrer',
filterLabel: formatMessage(labels.referrer),
label: formatMessage(labels.referrer),
group: 'sources',
},
{
name: 'country',
filterLabel: formatMessage(labels.country),
label: formatMessage(labels.country),
group: 'location',
},
{
name: 'region',
filterLabel: formatMessage(labels.region),
label: formatMessage(labels.region),
group: 'location',
},
{
name: 'city',
filterLabel: formatMessage(labels.city),
label: formatMessage(labels.city),
group: 'location',
},
{
name: 'browser',
filterLabel: formatMessage(labels.browser),
label: formatMessage(labels.browser),
group: 'environment',
},
{
name: 'os',
filterLabel: formatMessage(labels.os),
label: formatMessage(labels.os),
group: 'environment',
},
{
name: 'device',
filterLabel: formatMessage(labels.device),
label: formatMessage(labels.device),
group: 'environment',
},
{
name: 'utmSource',
filterLabel: formatMessage(labels.source),
label: formatMessage(labels.utmSource),
group: 'utm',
},
{
name: 'utmMedium',
filterLabel: formatMessage(labels.medium),
label: formatMessage(labels.utmMedium),
group: 'utm',
},
{
name: 'utmCampaign',
filterLabel: formatMessage(labels.campaign),
label: formatMessage(labels.utmCampaign),
group: 'utm',
},
{
name: 'utmContent',
filterLabel: formatMessage(labels.content),
label: formatMessage(labels.utmContent),
group: 'utm',
},
{
name: 'utmTerm',
filterLabel: formatMessage(labels.term),
label: formatMessage(labels.utmTerm),
group: 'utm',
},
{
name: 'hostname',
filterLabel: formatMessage(labels.hostname),
label: formatMessage(labels.hostname),
group: 'other',
},
{
name: 'distinctId',
filterLabel: formatMessage(labels.distinctId),
label: formatMessage(labels.distinctId),
group: 'other',
},
{
name: 'tag',
filterLabel: formatMessage(labels.tag),
label: formatMessage(labels.tag),
group: 'other',
},
{
name: 'event',
filterLabel: formatMessage(labels.event),
label: formatMessage(labels.event),
group: 'other',
},
];
return { fields };
const groupLabels: { key: FieldGroup; label: string }[] = [
{ key: 'url', label: formatMessage(labels.url) },
{ key: 'sources', label: formatMessage(labels.sources) },
{ key: 'location', label: formatMessage(labels.location) },
{ key: 'environment', label: formatMessage(labels.environment) },
{ key: 'utm', label: formatMessage(labels.utm) },
{ key: 'other', label: formatMessage(labels.other) },
];
return { fields, groupLabels };
}
@@ -18,11 +18,18 @@ export function useFilterParameters() {
event,
tag,
hostname,
distinctId,
utmSource,
utmMedium,
utmCampaign,
utmContent,
utmTerm,
page,
pageSize,
search,
segment,
cohort,
excludeBounce,
},
} = useNavigation();
@@ -42,9 +49,16 @@ export function useFilterParameters() {
event,
tag,
hostname,
distinctId,
utmSource,
utmMedium,
utmCampaign,
utmContent,
utmTerm,
search,
segment,
cohort,
excludeBounce,
};
}, [
path,
@@ -61,10 +75,17 @@ export function useFilterParameters() {
event,
tag,
hostname,
distinctId,
utmSource,
utmMedium,
utmCampaign,
utmContent,
utmTerm,
page,
pageSize,
search,
segment,
cohort,
excludeBounce,
]);
}