Merge branch 'dev' into boards

This commit is contained in:
Mike Cao 2026-01-14 12:53:25 -08:00
commit 0fbd8a448d
32 changed files with 52 additions and 43 deletions

View file

@ -1,5 +1,5 @@
import { ConfirmationForm } from '@/components/common/ConfirmationForm'; import { ConfirmationForm } from '@/components/common/ConfirmationForm';
import { useDeleteQuery, useMessages } from '@/components/hooks'; import { useDeleteQuery, useMessages, useModified } from '@/components/hooks';
import { Trash } from '@/components/icons'; import { Trash } from '@/components/icons';
import { DialogButton } from '@/components/input/DialogButton'; import { DialogButton } from '@/components/input/DialogButton';
import { messages } from '@/components/messages'; import { messages } from '@/components/messages';
@ -15,7 +15,8 @@ export function LinkDeleteButton({
onSave?: () => void; onSave?: () => void;
}) { }) {
const { formatMessage, labels, getErrorMessage, FormattedMessage } = useMessages(); const { formatMessage, labels, getErrorMessage, FormattedMessage } = useMessages();
const { mutateAsync, isPending, error, touch } = useDeleteQuery(`/links/${linkId}`); const { mutateAsync, isPending, error } = useDeleteQuery(`/links/${linkId}`);
const { touch } = useModified();
const handleConfirm = async (close: () => void) => { const handleConfirm = async (close: () => void) => {
await mutateAsync(null, { await mutateAsync(null, {

View file

@ -50,6 +50,7 @@ export function LinkEditForm({
onSuccess: async () => { onSuccess: async () => {
toast(formatMessage(messages.saved)); toast(formatMessage(messages.saved));
touch('links'); touch('links');
touch(`link:${linkId}`);
onSave?.(); onSave?.();
onClose?.(); onClose?.();
}, },

View file

@ -48,6 +48,7 @@ export function PixelEditForm({
onSuccess: async () => { onSuccess: async () => {
toast(formatMessage(messages.saved)); toast(formatMessage(messages.saved));
touch('pixels'); touch('pixels');
touch(`pixel:${pixelId}`);
onSave?.(); onSave?.();
onClose?.(); onClose?.();
}, },

View file

@ -17,8 +17,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); const parameters = await setWebsiteDate(websiteId, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); const filters = await getQueryFilters(body.filters, websiteId);
const data = await getAttribution(websiteId, parameters as AttributionParameters, filters); const data = await getAttribution(websiteId, parameters as AttributionParameters, filters);

View file

@ -17,8 +17,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); const parameters = await setWebsiteDate(websiteId, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); const filters = await getQueryFilters(body.filters, websiteId);
const data = await getBreakdown(websiteId, parameters as BreakdownParameters, filters); const data = await getBreakdown(websiteId, parameters as BreakdownParameters, filters);

View file

@ -17,8 +17,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); const parameters = await setWebsiteDate(websiteId, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); const filters = await getQueryFilters(body.filters, websiteId);
const data = await getFunnel(websiteId, parameters as FunnelParameters, filters); const data = await getFunnel(websiteId, parameters as FunnelParameters, filters);

View file

@ -17,8 +17,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); const parameters = await setWebsiteDate(websiteId, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); const filters = await getQueryFilters(body.filters, websiteId);
const data = await getGoal(websiteId, parameters as GoalParameters, filters); const data = await getGoal(websiteId, parameters as GoalParameters, filters);

View file

@ -17,8 +17,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); const filters = await getQueryFilters(body.filters, websiteId);
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); const parameters = await setWebsiteDate(websiteId, body.parameters);
const data = await getRetention(websiteId, parameters as RetentionParameters, filters); const data = await getRetention(websiteId, parameters as RetentionParameters, filters);

View file

@ -17,8 +17,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); const parameters = await setWebsiteDate(websiteId, body.parameters);
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); const filters = await getQueryFilters(body.filters, websiteId);
const data = await getRevenue(websiteId, parameters as RevenuParameters, filters); const data = await getRevenue(websiteId, parameters as RevenuParameters, filters);

View file

@ -18,8 +18,8 @@ export async function POST(request: Request) {
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(body.filters, websiteId, auth.user?.id); const filters = await getQueryFilters(body.filters, websiteId);
const parameters = await setWebsiteDate(websiteId, auth.user.id, body.parameters); const parameters = await setWebsiteDate(websiteId, body.parameters);
const data = { const data = {
utm_source: [], utm_source: [],

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const data = await getEventDataEvents(websiteId, { const data = await getEventDataEvents(websiteId, {
...filters, ...filters,

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const data = await getEventDataFields(websiteId, filters); const data = await getEventDataFields(websiteId, filters);

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const data = await getEventDataProperties(websiteId, filters); const data = await getEventDataProperties(websiteId, filters);

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const data = await getEventDataStats(websiteId, filters); const data = await getEventDataStats(websiteId, filters);

View file

@ -30,7 +30,7 @@ export async function GET(
} }
const { propertyName } = query; const { propertyName } = query;
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const data = await getEventDataValues(websiteId, { const data = await getEventDataValues(websiteId, {
...filters, ...filters,

View file

@ -29,7 +29,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const data = await getWebsiteEvents(websiteId, filters); const data = await getWebsiteEvents(websiteId, filters);

View file

@ -29,7 +29,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const data = await getEventStats(websiteId, filters); const data = await getEventStats(websiteId, filters);

View file

@ -28,7 +28,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const [events, pages, referrers, browsers, os, devices, countries] = await Promise.all([ const [events, pages, referrers, browsers, os, devices, countries] = await Promise.all([
getEventMetrics(websiteId, { type: 'event' }, filters), getEventMetrics(websiteId, { type: 'event' }, filters),

View file

@ -37,7 +37,7 @@ export async function GET(
} }
const { type, limit, offset, search } = query; const { type, limit, offset, search } = query;
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
if (search) { if (search) {
filters[type] = `c.${search}`; filters[type] = `c.${search}`;

View file

@ -37,7 +37,7 @@ export async function GET(
} }
const { type, limit, offset, search } = query; const { type, limit, offset, search } = query;
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
if (search) { if (search) {
filters[type] = `c.${search}`; filters[type] = `c.${search}`;

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const [pageviews, sessions] = await Promise.all([ const [pageviews, sessions] = await Promise.all([
getPageviewStats(websiteId, filters), getPageviewStats(websiteId, filters),

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const data = await getSessionDataProperties(websiteId, filters); const data = await getSessionDataProperties(websiteId, filters);

View file

@ -29,7 +29,7 @@ export async function GET(
} }
const { propertyName } = query; const { propertyName } = query;
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const data = await getSessionDataValues(websiteId, { const data = await getSessionDataValues(websiteId, {
...filters, ...filters,

View file

@ -25,7 +25,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const data = await getSessionActivity(websiteId, sessionId, filters); const data = await getSessionActivity(websiteId, sessionId, filters);

View file

@ -28,7 +28,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const data = await getWebsiteSessions(websiteId, filters); const data = await getWebsiteSessions(websiteId, filters);

View file

@ -27,7 +27,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const metrics = await getWebsiteSessionStats(websiteId, filters); const metrics = await getWebsiteSessionStats(websiteId, filters);

View file

@ -28,7 +28,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const data = await getWeeklyTraffic(websiteId, filters); const data = await getWeeklyTraffic(websiteId, filters);

View file

@ -27,11 +27,13 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
const data = await getWebsiteStats(websiteId, filters); const data = await getWebsiteStats(websiteId, filters);
const { startDate, endDate } = getCompareDate('prev', filters.startDate, filters.endDate); const compare = filters.compare ?? 'prev';
const { startDate, endDate } = getCompareDate(compare, filters.startDate, filters.endDate);
const comparison = await getWebsiteStats(websiteId, { const comparison = await getWebsiteStats(websiteId, {
...filters, ...filters,

View file

@ -42,7 +42,7 @@ export async function GET(
value: segment.name, value: segment.name,
})); }));
} else { } else {
const filters = await getQueryFilters(query, websiteId, auth.user?.id); const filters = await getQueryFilters(query, websiteId);
values = await getValues(websiteId, FILTER_COLUMNS[type], filters); values = await getValues(websiteId, FILTER_COLUMNS[type], filters);
} }

View file

@ -1,5 +1,6 @@
import type { UseQueryOptions } from '@tanstack/react-query'; import type { UseQueryOptions } from '@tanstack/react-query';
import { useDateParameters } from '@/components/hooks/useDateParameters'; import { useDateParameters } from '@/components/hooks/useDateParameters';
import { useDateRange } from '@/components/hooks/useDateRange';
import { useApi } from '../useApi'; import { useApi } from '../useApi';
import { useFilterParameters } from '../useFilterParameters'; import { useFilterParameters } from '../useFilterParameters';
@ -24,12 +25,16 @@ export function useWebsiteStatsQuery(
) { ) {
const { get, useQuery } = useApi(); const { get, useQuery } = useApi();
const { startAt, endAt, unit, timezone } = useDateParameters(); const { startAt, endAt, unit, timezone } = useDateParameters();
const { compare } = useDateRange();
const filters = useFilterParameters(); const filters = useFilterParameters();
return useQuery<WebsiteStatsData>({ return useQuery<WebsiteStatsData>({
queryKey: ['websites:stats', { websiteId, startAt, endAt, unit, timezone, ...filters }], queryKey: [
'websites:stats',
{ websiteId, startAt, endAt, unit, timezone, compare, ...filters },
],
queryFn: () => queryFn: () =>
get(`/websites/${websiteId}/stats`, { startAt, endAt, unit, timezone, ...filters }), get(`/websites/${websiteId}/stats`, { startAt, endAt, unit, timezone, compare, ...filters }),
enabled: !!websiteId, enabled: !!websiteId,
...options, ...options,
}); });

View file

@ -81,12 +81,12 @@ export function getRequestFilters(query: Record<string, any>) {
return result; return result;
} }
export async function setWebsiteDate(websiteId: string, userId: string, data: Record<string, any>) { export async function setWebsiteDate(websiteId: string, data: Record<string, any>) {
const website = await fetchWebsite(websiteId); const website = await fetchWebsite(websiteId);
const cloudMode = !!process.env.CLOUD_MODE; const cloudMode = !!process.env.CLOUD_MODE;
if (cloudMode && website && !website.teamId) { if (cloudMode && website && !website.teamId) {
const account = await fetchAccount(userId); const account = await fetchAccount(website.userId);
if (!account?.hasSubscription) { if (!account?.hasSubscription) {
data.startDate = maxDate(data.startDate, startOfMonth(subMonths(new Date(), 6))); data.startDate = maxDate(data.startDate, startOfMonth(subMonths(new Date(), 6)));
@ -103,13 +103,12 @@ export async function setWebsiteDate(websiteId: string, userId: string, data: Re
export async function getQueryFilters( export async function getQueryFilters(
params: Record<string, any>, params: Record<string, any>,
websiteId?: string, websiteId?: string,
userId?: string,
): Promise<QueryFilters> { ): Promise<QueryFilters> {
const dateRange = getRequestDateRange(params); const dateRange = getRequestDateRange(params);
const filters = getRequestFilters(params); const filters = getRequestFilters(params);
if (websiteId) { if (websiteId) {
await setWebsiteDate(websiteId, userId, dateRange); await setWebsiteDate(websiteId, dateRange);
if (params.segment) { if (params.segment) {
const segmentParams = (await getWebsiteSegment(websiteId, params.segment)) const segmentParams = (await getWebsiteSegment(websiteId, params.segment))

View file

@ -20,7 +20,7 @@ export const dateRangeParams = {
endDate: z.coerce.date().optional(), endDate: z.coerce.date().optional(),
timezone: timezoneParam.optional(), timezone: timezoneParam.optional(),
unit: unitParam.optional(), unit: unitParam.optional(),
compare: z.string().optional(), compare: z.enum(['prev', 'yoy']).optional(),
}; };
export const filterParams = { export const filterParams = {