diff --git a/next.config.ts b/next.config.ts
index e52df6ab..f2c07aff 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -199,18 +199,6 @@ export default {
typescript: {
ignoreBuildErrors: true,
},
- functions: {
- 'app/api/**/*.js': {
- maxDuration: 30,
- },
- },
- outputFileTracing: {
- include: [
- 'src/generated/prisma/**/*',
- 'node_modules/@prisma/client/**/*',
- 'node_modules/.prisma/client/**/*',
- ],
- },
async headers() {
return headers;
},
diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx
index f3975c3a..ecff5023 100644
--- a/src/app/(main)/App.tsx
+++ b/src/app/(main)/App.tsx
@@ -4,7 +4,7 @@ import Script from 'next/script';
import { usePathname } from 'next/navigation';
import { UpdateNotice } from './UpdateNotice';
import { SideNav } from '@/app/(main)/SideNav';
-import { MenuBar } from '@/app/(main)/MenuBar';
+import { TopNav } from '@/app/(main)/TopNav';
import { useLoginQuery, useConfig } from '@/components/hooks';
export function App({ children }) {
@@ -35,7 +35,7 @@ export function App({ children }) {
-
+
{};
+ const { teamId, websiteId, pathname } = useNavigation();
+ const isSettings = pathname.includes('/settings');
return (
- {websiteId && (
+ {websiteId && !isSettings && (
<>
-
+
>
)}
diff --git a/src/app/(main)/admin/users/UserDeleteForm.tsx b/src/app/(main)/admin/users/UserDeleteForm.tsx
index 59b12721..620da1d1 100644
--- a/src/app/(main)/admin/users/UserDeleteForm.tsx
+++ b/src/app/(main)/admin/users/UserDeleteForm.tsx
@@ -21,6 +21,7 @@ export function UserDeleteForm({
mutate(null, {
onSuccess: async () => {
touch('users');
+ touch(`users:${userId}`);
onSave?.();
onClose?.();
},
@@ -35,9 +36,7 @@ export function UserDeleteForm({
confirmLabel={formatMessage(labels.delete)}
isDanger
>
-
- {formatMessage(messages.confirmDelete, { target: {username} })}
-
+ {formatMessage(messages.confirmDelete, { target: username })}
);
}
diff --git a/src/app/(main)/admin/websites/AdminWebsitesTable.tsx b/src/app/(main)/admin/websites/AdminWebsitesTable.tsx
index 99105bc3..7b970e7f 100644
--- a/src/app/(main)/admin/websites/AdminWebsitesTable.tsx
+++ b/src/app/(main)/admin/websites/AdminWebsitesTable.tsx
@@ -1,15 +1,16 @@
import { useState } from 'react';
import Link from 'next/link';
-import { Row, Text, Icon, DataTable, DataColumn, MenuItem, Modal } from '@umami/react-zen';
+import { Row, Text, Icon, DataTable, DataColumn, MenuItem, Modal, Dialog } from '@umami/react-zen';
import { Trash, Users } from '@/components/icons';
import { useMessages } from '@/components/hooks';
import { Edit } from '@/components/icons';
import { MenuButton } from '@/components/input/MenuButton';
import { DateDistance } from '@/components/common/DateDistance';
+import { WebsiteDeleteForm } from '@/app/(main)/settings/websites/[websiteId]/WebsiteDeleteForm';
export function AdminWebsitesTable({ data = [] }: { data: any[] }) {
const { formatMessage, labels } = useMessages();
- const [deleteUser, setDeleteUser] = useState(null);
+ const [deleteWebsite, setDeleteWebsite] = useState(null);
return (
<>
@@ -64,7 +65,7 @@ export function AdminWebsitesTable({ data = [] }: { data: any[] }) {
- {children}
+ {children}
diff --git a/src/app/(main)/settings/teams/[teamId]/TeamDetails.tsx b/src/app/(main)/settings/teams/[teamId]/TeamDetails.tsx
index e4d7e514..071c56a8 100644
--- a/src/app/(main)/settings/teams/[teamId]/TeamDetails.tsx
+++ b/src/app/(main)/settings/teams/[teamId]/TeamDetails.tsx
@@ -1,7 +1,7 @@
import { useContext, useState } from 'react';
import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
-import { useLoginQuery, useMessages } from '@/components/hooks';
+import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks';
import { SectionHeader } from '@/components/common/SectionHeader';
import { ROLES } from '@/lib/constants';
import { Users } from '@/components/icons';
@@ -15,7 +15,10 @@ export function TeamDetails({ teamId }: { teamId: string }) {
const team = useContext(TeamContext);
const { formatMessage, labels } = useMessages();
const { user } = useLoginQuery();
- const [tab, setTab] = useState('details');
+ const { query, pathname } = useNavigation();
+ const [tab, setTab] = useState(query?.tab || 'details');
+
+ const isAdmin = pathname.includes('/admin');
const isTeamOwner =
!!team?.teamUser?.find(({ userId, role }) => role === ROLES.teamOwner && userId === user.id) &&
@@ -32,7 +35,7 @@ export function TeamDetails({ teamId }: { teamId: string }) {
return (
}>
- {!isTeamOwner && }
+ {!isTeamOwner && !isAdmin && }
setTab(value)}>
diff --git a/src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx b/src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx
index f84f58a9..c4cd45a8 100644
--- a/src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx
+++ b/src/app/(main)/settings/teams/[teamId]/TeamMembersTable.tsx
@@ -33,6 +33,10 @@ export function TeamMembersTable({
{allowEdit && (
{(row: any) => {
+ if (row?.role === ROLES.teamOwner) {
+ return null;
+ }
+
return (
diff --git a/src/app/(main)/settings/websites/WebsitesTable.tsx b/src/app/(main)/settings/websites/WebsitesTable.tsx
index 2d285c75..a7948781 100644
--- a/src/app/(main)/settings/websites/WebsitesTable.tsx
+++ b/src/app/(main)/settings/websites/WebsitesTable.tsx
@@ -57,7 +57,7 @@ export function WebsitesTable({
)}
{allowEdit && (
-
diff --git a/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx b/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx
index 9c4dd08c..75a8e97e 100644
--- a/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx
+++ b/src/app/(main)/websites/[websiteId]/WebsiteHeader.tsx
@@ -5,12 +5,11 @@ import { Share, Edit } from '@/components/icons';
import { Favicon } from '@/components/common/Favicon';
import { ActiveUsers } from '@/components/metrics/ActiveUsers';
import { WebsiteShareForm } from '@/app/(main)/settings/websites/[websiteId]/WebsiteShareForm';
-import { useMessages, useNavigation } from '@/components/hooks';
+import { useMessages } from '@/components/hooks';
import { LinkButton } from '@/components/common/LinkButton';
export function WebsiteHeader() {
const website = useWebsite();
- const { renderUrl } = useNavigation();
return (
} showBorder={false}>
@@ -18,7 +17,7 @@ export function WebsiteHeader() {
-
+
@@ -42,7 +41,7 @@ const ShareButton = ({ websiteId, shareId }) => {
Share
-
);
diff --git a/src/components/hooks/queries/useTeamsQuery.ts b/src/components/hooks/queries/useTeamsQuery.ts
index 1e78db84..6a0ecc34 100644
--- a/src/components/hooks/queries/useTeamsQuery.ts
+++ b/src/components/hooks/queries/useTeamsQuery.ts
@@ -11,8 +11,8 @@ export function useTeamsQuery(params?: Record, options?: ReactQuery
queryKey: ['teams:admin', { modified, ...params }],
queryFn: pageParams => {
return get(`/admin/teams`, {
- ...params,
...pageParams,
+ ...params,
});
},
...options,
diff --git a/src/components/hooks/queries/useUserWebsitesQuery.ts b/src/components/hooks/queries/useUserWebsitesQuery.ts
index 5f37d67d..16c28518 100644
--- a/src/components/hooks/queries/useUserWebsitesQuery.ts
+++ b/src/components/hooks/queries/useUserWebsitesQuery.ts
@@ -17,8 +17,8 @@ export function useUserWebsitesQuery(
queryKey: ['websites', { userId, teamId, modified, ...params }],
queryFn: pageParams => {
return get(teamId ? `/teams/${teamId}/websites` : `/users/${userId || user.id}/websites`, {
- ...params,
...pageParams,
+ ...params,
});
},
...options,
diff --git a/src/components/input/WebsiteSelect.tsx b/src/components/input/WebsiteSelect.tsx
index 3a8d5519..6e32b0e1 100644
--- a/src/components/input/WebsiteSelect.tsx
+++ b/src/components/input/WebsiteSelect.tsx
@@ -1,28 +1,24 @@
import { useState } from 'react';
import { Select, SelectProps, ListItem } from '@umami/react-zen';
-import { useUserWebsitesQuery, useMessages } from '@/components/hooks';
+import { useUserWebsitesQuery, useWebsiteQuery, useNavigation } from '@/components/hooks';
export function WebsiteSelect({
websiteId,
teamId,
variant,
- onSelect,
...props
}: {
websiteId?: string;
teamId?: string;
variant?: 'primary' | 'outline' | 'quiet' | 'danger' | 'zero';
- onSelect?: (key: any) => void;
} & SelectProps) {
- const { formatMessage, labels } = useMessages();
+ const { router, renderUrl } = useNavigation();
const [search, setSearch] = useState('');
- const [selectedId, setSelectedId] = useState(websiteId);
-
+ const { data: website } = useWebsiteQuery(websiteId);
const { data, isLoading } = useUserWebsitesQuery({ teamId }, { search, pageSize: 5 });
const handleSelect = (value: any) => {
- setSelectedId(value);
- onSelect?.(value);
+ router.push(renderUrl(`/websites/${value}`));
};
const handleSearch = (value: string) => {
@@ -33,14 +29,14 @@ export function WebsiteSelect({
diff --git a/src/lib/request.ts b/src/lib/request.ts
index b0df42a3..3d0048f1 100644
--- a/src/lib/request.ts
+++ b/src/lib/request.ts
@@ -66,7 +66,7 @@ export function getRequestDateRange(query: Record) {
};
}
-export async function getRequestFilters(query: Record, websiteId?: string) {
+export function getRequestFilters(query: Record) {
const result: Record = {};
for (const key of Object.keys(FILTER_COLUMNS)) {
@@ -76,20 +76,17 @@ export async function getRequestFilters(query: Record, websiteId?:
}
}
+ return result;
+}
+
+export async function getRequestSegments(websiteId: string, query: Record) {
for (const key of Object.keys(FILTER_GROUPS)) {
const value = query[key];
+
if (value !== undefined) {
- const segment = await getWebsiteSegment(websiteId, key, value);
- if (key === 'segment') {
- // merge filters into result
- Object.assign(result, segment.parameters);
- } else {
- result[key] = segment.parameters;
- }
+ return getWebsiteSegment(websiteId, key, value);
}
}
-
- return result;
}
export async function setWebsiteDate(websiteId: string, data: Record) {
@@ -102,13 +99,18 @@ export async function setWebsiteDate(websiteId: string, data: Record): QueryFilters {
- const dateRange = getRequestDateRange(params);
+export async function getQueryFilters(
+ params: Record,
+ websiteId?: string,
+): Promise {
+ const dateRange = await setWebsiteDate(websiteId, getRequestDateRange(params));
const filters = getRequestFilters(params);
+ const segments = await getRequestSegments(websiteId, params);
return {
...dateRange,
...filters,
+ ...segments,
page: params?.page,
pageSize: params?.page ? params?.pageSize || DEFAULT_PAGE_SIZE : undefined,
orderBy: params?.orderBy,
diff --git a/src/lib/types.ts b/src/lib/types.ts
index e1263826..29d566a5 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -71,6 +71,9 @@ export interface FilterParams {
search?: string;
tag?: string;
eventType?: number;
+ segment?: string;
+ cohort?: string;
+ compare?: string;
}
export interface SortParams {
diff --git a/src/queries/sql/events/getEventMetrics.ts b/src/queries/sql/events/getEventMetrics.ts
index 0e078ff1..06c141ba 100644
--- a/src/queries/sql/events/getEventMetrics.ts
+++ b/src/queries/sql/events/getEventMetrics.ts
@@ -85,7 +85,7 @@ async function clickhouseQuery(
from (
select arrayJoin(event_name) as event_name,
created_at
- from website_event_stats_hourly website_event
+ from website_event_stats_hourly as website_event
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
and event_type = {eventType:UInt32}
diff --git a/src/queries/sql/pageviews/getPageviewStats.ts b/src/queries/sql/pageviews/getPageviewStats.ts
index 7ef51137..704dea1e 100644
--- a/src/queries/sql/pageviews/getPageviewStats.ts
+++ b/src/queries/sql/pageviews/getPageviewStats.ts
@@ -81,7 +81,7 @@ async function clickhouseQuery(
select
${getDateSQL('website_event.created_at', unit, timezone)} as t,
sum(views) as y
- from website_event_stats_hourly website_event
+ from website_event_stats_hourly as website_event
${cohortQuery}
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
diff --git a/src/queries/sql/sessions/getSessionMetrics.ts b/src/queries/sql/sessions/getSessionMetrics.ts
index 285eaa7f..7e2393cf 100644
--- a/src/queries/sql/sessions/getSessionMetrics.ts
+++ b/src/queries/sql/sessions/getSessionMetrics.ts
@@ -103,7 +103,7 @@ async function clickhouseQuery(
${column} x,
uniq(session_id) y
${includeCountry ? ', country' : ''}
- from website_event_stats_hourly website_event
+ from website_event_stats_hourly as website_event
${cohortQuery}
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
diff --git a/src/queries/sql/sessions/getSessionStats.ts b/src/queries/sql/sessions/getSessionStats.ts
index 9911a235..258de290 100644
--- a/src/queries/sql/sessions/getSessionStats.ts
+++ b/src/queries/sql/sessions/getSessionStats.ts
@@ -81,7 +81,7 @@ async function clickhouseQuery(
select
${getDateSQL('website_event.created_at', unit, timezone)} as t,
uniq(session_id) as y
- from website_event_stats_hourly website_event
+ from website_event_stats_hourly as website_event
where website_id = {websiteId:UUID}
and created_at between {startDate:DateTime64} and {endDate:DateTime64}
and event_type = {eventType:UInt32}
diff --git a/src/queries/sql/sessions/getWebsiteSessions.ts b/src/queries/sql/sessions/getWebsiteSessions.ts
index 57dba14e..19b6cf93 100644
--- a/src/queries/sql/sessions/getWebsiteSessions.ts
+++ b/src/queries/sql/sessions/getWebsiteSessions.ts
@@ -94,7 +94,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) {
uniq(visit_id) as visits,
sumIf(views, event_type = 1) as views,
lastAt as createdAt
- from website_event_stats_hourly website_event
+ from website_event_stats_hourly as website_event
${cohortQuery}
where website_id = {websiteId:UUID}
${dateQuery}