From d0e1912fafdf097f48081e4eca6876031f077834 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 30 Nov 2023 23:40:58 -0800 Subject: [PATCH 01/28] Updated CSP rules. --- next.config.js | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/next.config.js b/next.config.js index eaaf8fe72..c73790b8f 100644 --- a/next.config.js +++ b/next.config.js @@ -9,38 +9,21 @@ const contentSecurityPolicy = [ `script-src 'self' 'unsafe-eval' 'unsafe-inline'`, `style-src 'self' 'unsafe-inline'`, `connect-src 'self' api.umami.is`, - `frame-src *`, + `frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS || ''}`, ]; -const cspHeader = (values = []) => ({ - key: 'Content-Security-Policy', - value: values - .join(';') - .replace(/\s{2,}/g, ' ') - .trim(), -}); - const headers = [ { key: 'X-DNS-Prefetch-Control', value: 'on', }, { - key: 'X-Frame-Options', - value: 'SAMEORIGIN', + key: 'Content-Security-Policy', + value: contentSecurityPolicy + .join(';') + .replace(/\s{2,}/g, ' ') + .trim(), }, - cspHeader(contentSecurityPolicy), -]; - -const shareHeaders = [ - { - key: 'X-DNS-Prefetch-Control', - value: 'on', - }, - cspHeader([ - ...contentSecurityPolicy, - `frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS || ''}`, - ]), ]; if (process.env.FORCE_SSL) { @@ -142,10 +125,6 @@ const config = { source: '/:path*', headers, }, - { - source: '/share/:path*', - headers: shareHeaders, - }, ]; }, async rewrites() { From b314cc88f567c34f33ce202844384897f727953b Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Fri, 1 Dec 2023 12:59:51 -0800 Subject: [PATCH 02/28] update @umami/prisma-client to 0.8.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ba85fb3e7..4d244cdc4 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "@prisma/extension-read-replicas": "^0.3.0", "@react-spring/web": "^9.7.3", "@tanstack/react-query": "^4.33.0", - "@umami/prisma-client": "^0.7.0", + "@umami/prisma-client": "^0.8.0", "@umami/redis-client": "^0.18.0", "chalk": "^4.1.1", "chart.js": "^4.2.1", diff --git a/yarn.lock b/yarn.lock index 1e405135d..2a8de5f1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2601,10 +2601,10 @@ "@typescript-eslint/types" "6.8.0" eslint-visitor-keys "^3.4.1" -"@umami/prisma-client@^0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.7.0.tgz#f9de0dfc861c9ba6379c0789e012d4effa65f1ef" - integrity sha512-70Azr4aAYMU6c+Lx69bjumNnKWgOFoq2PuYmp+T2kfCDhMyMLXTLDVD5ArDrwJMl1gWsgvpnumxCirYy+6KhGg== +"@umami/prisma-client@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.8.0.tgz#9f866c813b15b7ab0e7632506316bf1e5d2e74cc" + integrity sha512-ix3/75CO3eVlf1Rg0cUIjoHDFJV7nxx5sSh1NnvbjyGn8EsTpZ7fVYF874w8+ENQsaKFIMftUSGGiwvgxaZN3g== dependencies: chalk "^4.1.2" debug "^4.3.4" From e068ac0dd98eb5874c6bddf4842c2bcfc1714b5f Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 1 Dec 2023 16:02:50 -0800 Subject: [PATCH 03/28] Fixed insights report. --- package.json | 2 +- src/app/(main)/reports/insights/InsightsTable.js | 4 ++-- src/components/hooks/useFormat.ts | 10 +++++----- src/pages/api/reports/insights.ts | 2 +- src/queries/analytics/reports/getInsights.ts | 5 +++-- yarn.lock | 8 ++++---- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index ba85fb3e7..addfebdf3 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "npm-run-all": "^4.1.5", "prisma": "5.6.0", "react": "^18.2.0", - "react-basics": "^0.109.0", + "react-basics": "^0.110.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.4", diff --git a/src/app/(main)/reports/insights/InsightsTable.js b/src/app/(main)/reports/insights/InsightsTable.js index 05d380427..4194fee80 100644 --- a/src/app/(main)/reports/insights/InsightsTable.js +++ b/src/app/(main)/reports/insights/InsightsTable.js @@ -37,10 +37,10 @@ export function InsightsTable() { width="100px" alignment="end" > - {row => row.visitors.toLocaleString()} + {row => row?.visitors?.toLocaleString()} - {row => row.views.toLocaleString()} + {row => row?.views?.toLocaleString()} ); diff --git a/src/components/hooks/useFormat.ts b/src/components/hooks/useFormat.ts index c1160162d..f804eb728 100644 --- a/src/components/hooks/useFormat.ts +++ b/src/components/hooks/useFormat.ts @@ -9,23 +9,23 @@ export function useFormat() { const { locale } = useLocale(); const countryNames = useCountryNames(locale); - const formatBrowser = (value: string) => { + const formatBrowser = (value: string): string => { return BROWSERS[value] || value; }; - const formatCountry = (value: string) => { + const formatCountry = (value: string): string => { return countryNames[value] || value; }; - const formatRegion = (value: string) => { + const formatRegion = (value: string): string => { return regions[value] ? regions[value] : value; }; - const formatDevice = (value: string) => { + const formatDevice = (value: string): string => { return formatMessage(labels[value] || labels.unknown); }; - const formatValue = (value: string, type: string) => { + const formatValue = (value: string, type: string): string => { switch (type) { case 'browser': return formatBrowser(value); diff --git a/src/pages/api/reports/insights.ts b/src/pages/api/reports/insights.ts index 4344422af..c70d218eb 100644 --- a/src/pages/api/reports/insights.ts +++ b/src/pages/api/reports/insights.ts @@ -55,7 +55,7 @@ const schema = { }), }; -function convertFilters(filters) { +function convertFilters(filters: any[]) { return filters.reduce((obj, { name, ...value }) => { obj[name] = value; diff --git a/src/queries/analytics/reports/getInsights.ts b/src/queries/analytics/reports/getInsights.ts index a5b1b7731..282ed7557 100644 --- a/src/queries/analytics/reports/getInsights.ts +++ b/src/queries/analytics/reports/getInsights.ts @@ -86,8 +86,9 @@ async function clickhouseQuery( ).then(a => { return Object.values(a).map(a => { return { - x: a.x, - y: Number(a.y), + ...a, + views: Number(a.views), + visitors: Number(a.visitors), }; }); }); diff --git a/yarn.lock b/yarn.lock index 1e405135d..6584c9ece 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7476,10 +7476,10 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-basics@^0.109.0: - version "0.109.0" - resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.109.0.tgz#9c1f41ebf6abbcf67f7dd11a16a7fb5e3aedd38d" - integrity sha512-n955CwqIeQ/sTMxxvbtYpWtBWS07Rg39zrNKeUYN/JoCIq0YbbZiZDALAhh1Out+qsIe62NoOFA7JtHzk1EkHQ== +react-basics@^0.110.0: + version "0.110.0" + resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.110.0.tgz#7b7689edcb96b973528abc91b964345edc6e000a" + integrity sha512-3XQx5hR0zTuEAxDDHyaqS2GfXOowb0Lri8ay65kjGUd7RmvdpnIr62MsrAwo62rtTwtzRzsJXHJCR5CKQ/D3rQ== dependencies: "@react-spring/web" "^9.7.3" classnames "^2.3.1" From b578162cb6399b0ed461071246475aec83fed534 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 1 Dec 2023 20:27:59 -0800 Subject: [PATCH 04/28] Refactored useQuery functions. --- src/app/(main)/console/TestConsole.js | 5 +++- src/app/(main)/dashboard/Dashboard.js | 7 ++--- src/app/(main)/dashboard/DashboardEdit.js | 5 +++- src/app/(main)/reports/ReportsDataTable.tsx | 8 +++--- .../(main)/reports/[id]/FilterSelectForm.js | 10 +++---- src/app/(main)/reports/[id]/ReportDetails.js | 5 +++- .../reports/event-data/EventDataParameters.js | 10 +++---- .../(main)/settings/teams/TeamsDataTable.js | 11 +++++--- .../(main)/settings/teams/[id]/TeamMembers.js | 10 +++---- .../settings/teams/[id]/TeamSettings.js | 10 +++---- .../settings/teams/[id]/TeamWebsiteAddForm.js | 5 +++- .../settings/teams/[id]/TeamWebsites.js | 10 +++---- src/app/(main)/settings/users/UserWebsites.js | 8 +++--- .../(main)/settings/users/UsersDataTable.js | 7 +++-- .../settings/users/[id]/UserSettings.js | 10 +++---- .../settings/websites/WebsiteSettings.js | 10 +++++-- .../settings/websites/WebsitesDataTable.tsx | 10 +++---- src/app/(main)/websites/[id]/WebsiteChart.js | 8 +++--- .../(main)/websites/[id]/WebsiteMetricsBar.js | 8 +++--- .../[id]/event-data/EventDataMetricsBar.js | 8 +++--- .../[id]/event-data/WebsiteEventData.js | 10 +++---- .../(main)/websites/[id]/realtime/Realtime.js | 16 +++++------ .../websites/[id]/realtime/RealtimeHome.js | 5 +++- src/components/common/DataTable.tsx | 26 +++++++----------- src/components/common/FilterLink.tsx | 2 +- src/components/hooks/useFilterQuery.ts | 27 ++++++++++--------- src/components/hooks/useReports.ts | 8 +++--- src/components/hooks/useShareToken.ts | 11 +++++--- src/components/hooks/useWebsite.ts | 4 ++- src/components/input/WebsiteSelect.js | 5 +++- src/components/metrics/ActiveUsers.js | 14 +++++----- src/components/metrics/EventsChart.js | 25 +++++++++-------- src/components/metrics/MetricsTable.js | 12 +++++---- 33 files changed, 179 insertions(+), 151 deletions(-) diff --git a/src/app/(main)/console/TestConsole.js b/src/app/(main)/console/TestConsole.js index b88bfd77d..7aae09b2a 100644 --- a/src/app/(main)/console/TestConsole.js +++ b/src/app/(main)/console/TestConsole.js @@ -14,7 +14,10 @@ import styles from './TestConsole.module.css'; export function TestConsole({ websiteId }) { const { get, useQuery } = useApi(); - const { data, isLoading, error } = useQuery(['websites:me'], () => get('/me/websites')); + const { data, isLoading, error } = useQuery({ + queryKey: ['websites:me'], + queryFn: () => get('/me/websites'), + }); const { router } = useNavigation(); function handleChange(value) { diff --git a/src/app/(main)/dashboard/Dashboard.js b/src/app/(main)/dashboard/Dashboard.js index 5fb65f238..6f0659bbe 100644 --- a/src/app/(main)/dashboard/Dashboard.js +++ b/src/app/(main)/dashboard/Dashboard.js @@ -20,9 +20,10 @@ export function Dashboard() { const { get, useQuery } = useApi(); const { page, handlePageChange } = useApiFilter(); const pageSize = 10; - const { data: result, isLoading } = useQuery(['websites', page, pageSize], () => - get('/websites', { includeTeams: 1, page, pageSize }), - ); + const { data: result, isLoading } = useQuery({ + queryKey: ['websites', page, pageSize], + queryFn: () => get('/websites', { includeTeams: 1, page, pageSize }), + }); const { data, count } = result || {}; const hasData = data && data?.length !== 0; diff --git a/src/app/(main)/dashboard/DashboardEdit.js b/src/app/(main)/dashboard/DashboardEdit.js index 3af338670..0a8734e8d 100644 --- a/src/app/(main)/dashboard/DashboardEdit.js +++ b/src/app/(main)/dashboard/DashboardEdit.js @@ -17,7 +17,10 @@ export function DashboardEdit() { const { formatMessage, labels } = useMessages(); const [order, setOrder] = useState(websiteOrder || []); const { get, useQuery } = useApi(); - const { data: result } = useQuery(['websites'], () => get('/websites', { includeTeams: 1 })); + const { data: result } = useQuery({ + queryKey: ['websites'], + queryFn: () => get('/websites', { includeTeams: 1 }), + }); const { data: websites } = result || {}; const ordered = useMemo(() => { diff --git a/src/app/(main)/reports/ReportsDataTable.tsx b/src/app/(main)/reports/ReportsDataTable.tsx index 0ca853dc7..eeef203b4 100644 --- a/src/app/(main)/reports/ReportsDataTable.tsx +++ b/src/app/(main)/reports/ReportsDataTable.tsx @@ -8,9 +8,11 @@ import useCache from 'store/cache'; export default function ReportsDataTable({ websiteId }: { websiteId?: string }) { const { get } = useApi(); const modified = useCache(state => (state as any)?.reports); - const queryResult = useFilterQuery(['reports', { websiteId, modified }], params => - get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params), - ); + const queryResult = useFilterQuery({ + queryKey: ['reports', { websiteId, modified }], + queryFn: (params: any) => + get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params), + }); return ( diff --git a/src/app/(main)/reports/[id]/FilterSelectForm.js b/src/app/(main)/reports/[id]/FilterSelectForm.js index 9ad4cd932..8457b7ff6 100644 --- a/src/app/(main)/reports/[id]/FilterSelectForm.js +++ b/src/app/(main)/reports/[id]/FilterSelectForm.js @@ -8,16 +8,16 @@ import { useApi } from 'components/hooks'; function useValues(websiteId, type) { const now = Date.now(); const { get, useQuery } = useApi(); - const { data, error, isLoading } = useQuery( - ['websites:values', websiteId, type], - () => + const { data, error, isLoading } = useQuery({ + queryKey: ['websites:values', websiteId, type], + queryFn: () => get(`/websites/${websiteId}/values`, { type, startAt: +subDays(now, 90), endAt: now, }), - { enabled: !!(websiteId && type) }, - ); + enabled: !!(websiteId && type), + }); return { data, error, isLoading }; } diff --git a/src/app/(main)/reports/[id]/ReportDetails.js b/src/app/(main)/reports/[id]/ReportDetails.js index 8605ffb33..df91719a3 100644 --- a/src/app/(main)/reports/[id]/ReportDetails.js +++ b/src/app/(main)/reports/[id]/ReportDetails.js @@ -14,7 +14,10 @@ const reports = { export default function ReportDetails({ reportId }) { const { get, useQuery } = useApi(); - const { data: report } = useQuery(['reports', reportId], () => get(`/reports/${reportId}`)); + const { data: report } = useQuery({ + queryKey: ['reports', reportId], + queryFn: () => get(`/reports/${reportId}`), + }); if (!report) { return null; diff --git a/src/app/(main)/reports/event-data/EventDataParameters.js b/src/app/(main)/reports/event-data/EventDataParameters.js index 6b9a03445..ac90fc2a8 100644 --- a/src/app/(main)/reports/event-data/EventDataParameters.js +++ b/src/app/(main)/reports/event-data/EventDataParameters.js @@ -12,16 +12,16 @@ import styles from './EventDataParameters.module.css'; function useFields(websiteId, startDate, endDate) { const { get, useQuery } = useApi(); - const { data, error, isLoading } = useQuery( - ['fields', websiteId, startDate, endDate], - () => + const { data, error, isLoading } = useQuery({ + queryKey: ['fields', websiteId, startDate, endDate], + queryFn: () => get('/reports/event-data', { websiteId, startAt: +startDate, endAt: +endDate, }), - { enabled: !!(websiteId && startDate && endDate) }, - ); + enabled: !!(websiteId && startDate && endDate), + }); return { data, error, isLoading }; } diff --git a/src/app/(main)/settings/teams/TeamsDataTable.js b/src/app/(main)/settings/teams/TeamsDataTable.js index 164838f96..a8c9e21d1 100644 --- a/src/app/(main)/settings/teams/TeamsDataTable.js +++ b/src/app/(main)/settings/teams/TeamsDataTable.js @@ -8,10 +8,13 @@ import useCache from 'store/cache'; export function TeamsDataTable() { const { get } = useApi(); const modified = useCache(state => state?.teams); - const queryResult = useFilterQuery(['teams', { modified }], params => { - return get(`/teams`, { - ...params, - }); + const queryResult = useFilterQuery({ + queryKey: ['teams', { modified }], + queryFn: params => { + return get(`/teams`, { + ...params, + }); + }, }); return ( diff --git a/src/app/(main)/settings/teams/[id]/TeamMembers.js b/src/app/(main)/settings/teams/[id]/TeamMembers.js index fb31b6fab..1b0c0d180 100644 --- a/src/app/(main)/settings/teams/[id]/TeamMembers.js +++ b/src/app/(main)/settings/teams/[id]/TeamMembers.js @@ -7,15 +7,15 @@ import useCache from 'store/cache'; export function TeamMembers({ teamId, readOnly }) { const { get } = useApi(); const modified = useCache(state => state?.['team:members']); - const queryResult = useFilterQuery( - ['team:members', { teamId, modified }], - params => { + const queryResult = useFilterQuery({ + queryKey: ['team:members', { teamId, modified }], + queryFn: params => { return get(`/teams/${teamId}/users`, { ...params, }); }, - { enabled: !!teamId }, - ); + enabled: !!teamId, + }); return ( <> diff --git a/src/app/(main)/settings/teams/[id]/TeamSettings.js b/src/app/(main)/settings/teams/[id]/TeamSettings.js index 8ec0ad856..d7065f97f 100644 --- a/src/app/(main)/settings/teams/[id]/TeamSettings.js +++ b/src/app/(main)/settings/teams/[id]/TeamSettings.js @@ -17,15 +17,15 @@ export function TeamSettings({ teamId }) { const [tab, setTab] = useState('details'); const { get, useQuery } = useApi(); const { showToast } = useToasts(); - const { data, isLoading } = useQuery( - ['team', teamId], - () => { + const { data, isLoading } = useQuery({ + queryKey: ['team', teamId], + queryFn: () => { if (teamId) { return get(`/teams/${teamId}`); } }, - { cacheTime: 0 }, - ); + cacheTime: 0, + }); const canEdit = data?.teamUser?.find( ({ userId, role }) => role === ROLES.teamOwner && userId === user.id, ); diff --git a/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js b/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js index c83ec3d08..004ae54f1 100644 --- a/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js +++ b/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js @@ -12,7 +12,10 @@ export function TeamWebsiteAddForm({ teamId, onSave, onClose }) { const { formatMessage, labels } = useMessages(); const { get, post, useQuery, useMutation } = useApi(); const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data)); - const { data: websites, isLoading } = useQuery(['websites'], () => get('/websites')); + const { data: websites, isLoading } = useQuery({ + queryKey: ['websites'], + queryFn: () => get('/websites'), + }); const [selected, setSelected] = useState([]); const hasData = websites && websites.data.length > 0; diff --git a/src/app/(main)/settings/teams/[id]/TeamWebsites.js b/src/app/(main)/settings/teams/[id]/TeamWebsites.js index 9e76ffab4..af167b890 100644 --- a/src/app/(main)/settings/teams/[id]/TeamWebsites.js +++ b/src/app/(main)/settings/teams/[id]/TeamWebsites.js @@ -13,15 +13,15 @@ export function TeamWebsites({ teamId }) { const { user } = useUser(); const { get } = useApi(); const modified = useCache(state => state?.['team:websites']); - const queryResult = useFilterQuery( - ['team:websites', { teamId, modified }], - params => { + const queryResult = useFilterQuery({ + queryKey: ['team:websites', { teamId, modified }], + queryFn: params => { return get(`/teams/${teamId}/websites`, { ...params, }); }, - { enabled: !!user }, - ); + enabled: !!user, + }); const handleChange = () => { queryResult.refetch(); diff --git a/src/app/(main)/settings/users/UserWebsites.js b/src/app/(main)/settings/users/UserWebsites.js index 18b5f1a71..cd53c512e 100644 --- a/src/app/(main)/settings/users/UserWebsites.js +++ b/src/app/(main)/settings/users/UserWebsites.js @@ -7,15 +7,15 @@ export function UserWebsites({ userId }) { const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } = useApiFilter(); const { get, useQuery } = useApi(); - const { data, isLoading, error } = useQuery( - ['user:websites', userId, filter, page, pageSize], - () => + const { data, isLoading, error } = useQuery({ + queryKey: ['user:websites', userId, filter, page, pageSize], + queryFn: () => get(`/users/${userId}/websites`, { filter, page, pageSize, }), - ); + }); const hasData = data && data.length !== 0; return ( diff --git a/src/app/(main)/settings/users/UsersDataTable.js b/src/app/(main)/settings/users/UsersDataTable.js index 154e37ad0..91125309b 100644 --- a/src/app/(main)/settings/users/UsersDataTable.js +++ b/src/app/(main)/settings/users/UsersDataTable.js @@ -9,10 +9,9 @@ import useCache from 'store/cache'; export function UsersDataTable() { const { get } = useApi(); const modified = useCache(state => state?.users); - const queryResult = useFilterQuery(['users', { modified }], params => { - return get(`/users`, { - ...params, - }); + const queryResult = useFilterQuery({ + queryKey: ['users', { modified }], + queryFn: params => get(`/users`, params), }); return ( diff --git a/src/app/(main)/settings/users/[id]/UserSettings.js b/src/app/(main)/settings/users/[id]/UserSettings.js index ea340ab79..f635bb054 100644 --- a/src/app/(main)/settings/users/[id]/UserSettings.js +++ b/src/app/(main)/settings/users/[id]/UserSettings.js @@ -14,15 +14,15 @@ export function UserSettings({ userId }) { const [tab, setTab] = useState('details'); const { get, useQuery } = useApi(); const { showToast } = useToasts(); - const { data, isLoading } = useQuery( - ['user', userId], - () => { + const { data, isLoading } = useQuery({ + queryKey: ['user', userId], + queryFn: () => { if (userId) { return get(`/users/${userId}`); } }, - { cacheTime: 0 }, - ); + cacheTime: 0, + }); const handleSave = data => { showToast({ message: formatMessage(messages.saved), variant: 'success' }); diff --git a/src/app/(main)/settings/websites/WebsiteSettings.js b/src/app/(main)/settings/websites/WebsiteSettings.js index 82b380482..79ba08fc0 100644 --- a/src/app/(main)/settings/websites/WebsiteSettings.js +++ b/src/app/(main)/settings/websites/WebsiteSettings.js @@ -1,6 +1,6 @@ 'use client'; import { useEffect, useState } from 'react'; -import { Item, Tabs, useToasts, Button, Text, Icon, Icons } from 'react-basics'; +import { Item, Tabs, useToasts, Button, Text, Icon, Icons, Loading } from 'react-basics'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; import PageHeader from 'components/layout/PageHeader'; @@ -16,7 +16,9 @@ export function WebsiteSettings({ websiteId, openExternal = false, analyticsUrl const { formatMessage, labels, messages } = useMessages(); const { get, useQuery } = useApi(); const { showToast } = useToasts(); - const { data } = useQuery(['website', websiteId], () => get(`/websites/${websiteId}`), { + const { data, isLoading } = useQuery({ + queryKey: ['website', websiteId], + queryFn: () => get(`/websites/${websiteId}`), enabled: !!websiteId, cacheTime: 0, }); @@ -46,6 +48,10 @@ export function WebsiteSettings({ websiteId, openExternal = false, analyticsUrl } }, [data]); + if (isLoading || !values) { + return ; + } + return ( <> diff --git a/src/app/(main)/settings/websites/WebsitesDataTable.tsx b/src/app/(main)/settings/websites/WebsitesDataTable.tsx index fc6dd0c0e..4d55e0474 100644 --- a/src/app/(main)/settings/websites/WebsitesDataTable.tsx +++ b/src/app/(main)/settings/websites/WebsitesDataTable.tsx @@ -21,17 +21,17 @@ function useWebsites(userId: string, { includeTeams, onlyTeams }) { const { get } = useApi(); const modified = useCache((state: any) => state?.websites); - return useFilterQuery( - ['websites', { includeTeams, onlyTeams, modified }], - (params: any) => { + return useFilterQuery({ + queryKey: ['websites', { includeTeams, onlyTeams, modified }], + queryFn: (params: any) => { return get(`/users/${userId}/websites`, { includeTeams, onlyTeams, ...params, }); }, - { enabled: !!userId }, - ); + enabled: !!userId, + }); } export function WebsitesDataTable({ diff --git a/src/app/(main)/websites/[id]/WebsiteChart.js b/src/app/(main)/websites/[id]/WebsiteChart.js index d05ff4220..15a7525f1 100644 --- a/src/app/(main)/websites/[id]/WebsiteChart.js +++ b/src/app/(main)/websites/[id]/WebsiteChart.js @@ -12,12 +12,12 @@ export function WebsiteChart({ websiteId }) { } = useNavigation(); const { get, useQuery } = useApi(); - const { data, isLoading } = useQuery( - [ + const { data, isLoading } = useQuery({ + queryKey: [ 'websites:pageviews', { websiteId, modified, url, referrer, os, browser, device, country, region, city, title }, ], - () => + queryFn: () => get(`/websites/${websiteId}/pageviews`, { startAt: +startDate, endAt: +endDate, @@ -33,7 +33,7 @@ export function WebsiteChart({ websiteId }) { city, title, }), - ); + }); const chartData = useMemo(() => { if (data) { diff --git a/src/app/(main)/websites/[id]/WebsiteMetricsBar.js b/src/app/(main)/websites/[id]/WebsiteMetricsBar.js index 0dd6a4e2f..d840c7b68 100644 --- a/src/app/(main)/websites/[id]/WebsiteMetricsBar.js +++ b/src/app/(main)/websites/[id]/WebsiteMetricsBar.js @@ -17,12 +17,12 @@ export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) { query: { url, referrer, title, os, browser, device, country, region, city }, } = useNavigation(); - const { data, error, isLoading, isFetched } = useQuery( - [ + const { data, error, isLoading, isFetched } = useQuery({ + queryKey: [ 'websites:stats', { websiteId, modified, url, referrer, title, os, browser, device, country, region, city }, ], - () => + queryFn: () => get(`/websites/${websiteId}/stats`, { startAt: +startDate, endAt: +endDate, @@ -36,7 +36,7 @@ export function WebsiteMetricsBar({ websiteId, showFilter = true, sticky }) { region, city, }), - ); + }); const { pageviews, uniques, bounces, totaltime } = data || {}; const num = Math.min(data && uniques.value, data && bounces.value); diff --git a/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js b/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js index 5be191854..b02e166c1 100644 --- a/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js +++ b/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js @@ -11,15 +11,15 @@ export function EventDataMetricsBar({ websiteId }) { const [dateRange] = useDateRange(websiteId); const { startDate, endDate, modified } = dateRange; - const { data, error, isLoading, isFetched } = useQuery( - ['event-data:stats', { websiteId, startDate, endDate, modified }], - () => + const { data, error, isLoading, isFetched } = useQuery({ + queryKey: ['event-data:stats', { websiteId, startDate, endDate, modified }], + queryFn: () => get(`/event-data/stats`, { websiteId, startAt: +startDate, endAt: +endDate, }), - ); + }); return (
diff --git a/src/app/(main)/websites/[id]/event-data/WebsiteEventData.js b/src/app/(main)/websites/[id]/event-data/WebsiteEventData.js index b5982e321..b67ee95e4 100644 --- a/src/app/(main)/websites/[id]/event-data/WebsiteEventData.js +++ b/src/app/(main)/websites/[id]/event-data/WebsiteEventData.js @@ -10,17 +10,17 @@ function useData(websiteId, event) { const [dateRange] = useDateRange(websiteId); const { startDate, endDate } = dateRange; const { get, useQuery } = useApi(); - const { data, error, isLoading } = useQuery( - ['event-data:events', { websiteId, startDate, endDate, event }], - () => + const { data, error, isLoading } = useQuery({ + queryKey: ['event-data:events', { websiteId, startDate, endDate, event }], + queryFn: () => get('/event-data/events', { websiteId, startAt: +startDate, endAt: +endDate, event, }), - { enabled: !!(websiteId && startDate && endDate) }, - ); + enabled: !!(websiteId && startDate && endDate), + }); return { data, error, isLoading }; } diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.js b/src/app/(main)/websites/[id]/realtime/Realtime.js index 737bcd1ba..37df458ca 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.js +++ b/src/app/(main)/websites/[id]/realtime/Realtime.js @@ -28,15 +28,13 @@ export function Realtime({ websiteId }) { const [currentData, setCurrentData] = useState(); const { get, useQuery } = useApi(); const { data: website } = useWebsite(websiteId); - const { data, isLoading, error } = useQuery( - ['realtime', websiteId], - () => get(`/realtime/${websiteId}`, { startAt: currentData?.timestamp || 0 }), - { - enabled: !!(websiteId && website), - refetchInterval: REALTIME_INTERVAL, - cache: false, - }, - ); + const { data, isLoading, error } = useQuery({ + queryKey: ['realtime', websiteId], + queryFn: () => get(`/realtime/${websiteId}`, { startAt: currentData?.timestamp || 0 }), + enabled: !!(websiteId && website), + refetchInterval: REALTIME_INTERVAL, + cache: false, + }); useEffect(() => { if (data) { diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeHome.js b/src/app/(main)/websites/[id]/realtime/RealtimeHome.js index dbaeb541a..154ac707f 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeHome.js +++ b/src/app/(main)/websites/[id]/realtime/RealtimeHome.js @@ -10,7 +10,10 @@ export function RealtimeHome() { const { formatMessage, labels, messages } = useMessages(); const { get, useQuery } = useApi(); const router = useRouter(); - const { data, isLoading, error } = useQuery(['websites:me'], () => get('/me/websites')); + const { data, isLoading, error } = useQuery({ + queryKey: ['websites:me'], + queryFn: () => get('/me/websites'), + }); useEffect(() => { if (data?.length) { diff --git a/src/components/common/DataTable.tsx b/src/components/common/DataTable.tsx index a3c63c0ab..621a44fe4 100644 --- a/src/components/common/DataTable.tsx +++ b/src/components/common/DataTable.tsx @@ -1,29 +1,16 @@ -import { ReactNode, Dispatch, SetStateAction } from 'react'; +import { ReactNode } from 'react'; import classNames from 'classnames'; import { Banner, Loading, SearchField } from 'react-basics'; import { useMessages } from 'components/hooks'; import Empty from 'components/common/Empty'; import Pager from 'components/common/Pager'; import styles from './DataTable.module.css'; +import { FilterQueryResult } from 'components/hooks/useFilterQuery'; const DEFAULT_SEARCH_DELAY = 600; export interface DataTableProps { - queryResult: { - result: { - page: number; - pageSize: number; - count: number; - data: any[]; - }; - params: { - query: string; - page: number; - }; - setParams: Dispatch>; - isLoading: boolean; - error: unknown; - }; + queryResult: FilterQueryResult; searchDelay?: number; allowSearch?: boolean; allowPaging?: boolean; @@ -38,7 +25,12 @@ export function DataTable({ children, }: DataTableProps) { const { formatMessage, labels, messages } = useMessages(); - const { result, error, isLoading, params, setParams } = queryResult || {}; + const { + result, + params, + setParams, + query: { error, isLoading }, + } = queryResult || {}; const { page, pageSize, count, data } = result || {}; const { query } = params || {}; const hasData = Boolean(!isLoading && data?.length); diff --git a/src/components/common/FilterLink.tsx b/src/components/common/FilterLink.tsx index a90302270..bc0a4d48d 100644 --- a/src/components/common/FilterLink.tsx +++ b/src/components/common/FilterLink.tsx @@ -13,7 +13,7 @@ export interface FilterLinkProps { label?: string; externalUrl?: string; className?: string; - children: ReactNode; + children?: ReactNode; } export function FilterLink({ diff --git a/src/components/hooks/useFilterQuery.ts b/src/components/hooks/useFilterQuery.ts index 37c28b7e6..2c5207416 100644 --- a/src/components/hooks/useFilterQuery.ts +++ b/src/components/hooks/useFilterQuery.ts @@ -1,24 +1,25 @@ -import { useState } from 'react'; +import { useState, Dispatch, SetStateAction } from 'react'; import { useApi } from 'components/hooks/useApi'; -import { UseQueryOptions } from '@tanstack/react-query'; +import { SearchFilter, FilterResult } from 'lib/types'; -export function useFilterQuery(key: any[], fn, options?: UseQueryOptions) { - const [params, setParams] = useState({ +export interface FilterQueryResult { + result: FilterResult; + query: any; + params: SearchFilter; + setParams: Dispatch>; +} + +export function useFilterQuery(props = {}): FilterQueryResult { + const [params, setParams] = useState({ query: '', page: 1, }); const { useQuery } = useApi(); - - const { data, ...other } = useQuery([...key, params], fn.bind(null, params), options); + const { data, ...query } = useQuery>({ ...props }); return { - result: data as { - page: number; - pageSize: number; - count: number; - data: any[]; - }, - ...other, + result: data, + query, params, setParams, }; diff --git a/src/components/hooks/useReports.ts b/src/components/hooks/useReports.ts index d9292aeb4..0fad2db15 100644 --- a/src/components/hooks/useReports.ts +++ b/src/components/hooks/useReports.ts @@ -8,10 +8,10 @@ export function useReports() { const { mutate } = useMutation(reportId => del(`/reports/${reportId}`)); const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } = useApiFilter(); - const { data, error, isLoading } = useQuery( - ['reports', { modified, filter, page, pageSize }], - () => get(`/reports`, { filter, page, pageSize }), - ); + const { data, error, isLoading } = useQuery({ + queryKey: ['reports', { modified, filter, page, pageSize }], + queryFn: () => get(`/reports`, { filter, page, pageSize }), + }); const deleteReport = id => { mutate(id, { diff --git a/src/components/hooks/useShareToken.ts b/src/components/hooks/useShareToken.ts index 088f643eb..41ae7faf5 100644 --- a/src/components/hooks/useShareToken.ts +++ b/src/components/hooks/useShareToken.ts @@ -6,12 +6,15 @@ const selector = (state: { shareToken: string }) => state.shareToken; export function useShareToken(shareId: string) { const shareToken = useStore(selector); const { get, useQuery } = useApi(); - const { isLoading, error } = useQuery(['share', shareId], async () => { - const data = await get(`/share/${shareId}`); + const { isLoading, error } = useQuery({ + queryKey: ['share', shareId], + queryFn: async () => { + const data = await get(`/share/${shareId}`); - setShareToken(data); + setShareToken(data); - return data; + return data; + }, }); return { shareToken, isLoading, error }; diff --git a/src/components/hooks/useWebsite.ts b/src/components/hooks/useWebsite.ts index 7b68335ac..d18e96ba6 100644 --- a/src/components/hooks/useWebsite.ts +++ b/src/components/hooks/useWebsite.ts @@ -2,7 +2,9 @@ import useApi from './useApi'; export function useWebsite(websiteId: string) { const { get, useQuery } = useApi(); - return useQuery(['websites', websiteId], () => get(`/websites/${websiteId}`), { + return useQuery({ + queryKey: ['websites', websiteId], + queryFn: () => get(`/websites/${websiteId}`), enabled: !!websiteId, }); } diff --git a/src/components/input/WebsiteSelect.js b/src/components/input/WebsiteSelect.js index 078389d31..233342159 100644 --- a/src/components/input/WebsiteSelect.js +++ b/src/components/input/WebsiteSelect.js @@ -6,7 +6,10 @@ import styles from './WebsiteSelect.module.css'; export function WebsiteSelect({ websiteId, onSelect }) { const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); - const { data } = useQuery(['websites:me'], () => get('/me/websites', { pageSize: 100 })); + const { data } = useQuery({ + queryKey: ['websites:me'], + queryFn: () => get('/me/websites', { pageSize: 100 }), + }); const renderValue = value => { return data?.data?.find(({ id }) => id === value)?.name; diff --git a/src/components/metrics/ActiveUsers.js b/src/components/metrics/ActiveUsers.js index 3074d0df9..cd3bfb994 100644 --- a/src/components/metrics/ActiveUsers.js +++ b/src/components/metrics/ActiveUsers.js @@ -7,14 +7,12 @@ import styles from './ActiveUsers.module.css'; export function ActiveUsers({ websiteId, value, refetchInterval = 60000 }) { const { formatMessage, messages } = useMessages(); const { get, useQuery } = useApi(); - const { data } = useQuery( - ['websites:active', websiteId], - () => get(`/websites/${websiteId}/active`), - { - refetchInterval, - enabled: !!websiteId, - }, - ); + const { data } = useQuery({ + queryKey: ['websites:active', websiteId], + queryFn: () => get(`/websites/${websiteId}/active`), + refetchInterval, + enabled: !!websiteId, + }); const count = useMemo(() => { if (websiteId) { diff --git a/src/components/metrics/EventsChart.js b/src/components/metrics/EventsChart.js index f2cf48d11..610a3616e 100644 --- a/src/components/metrics/EventsChart.js +++ b/src/components/metrics/EventsChart.js @@ -16,17 +16,20 @@ export function EventsChart({ websiteId, className, token }) { query: { url, event }, } = useNavigation(); - const { data, isLoading } = useQuery(['events', websiteId, modified, event], () => - get(`/websites/${websiteId}/events`, { - startAt: +startDate, - endAt: +endDate, - unit, - timezone, - url, - event, - token, - }), - ); + const { data, isLoading } = useQuery({ + queryKey: ['events', websiteId, modified, event], + queryFn: () => + get(`/websites/${websiteId}/events`, { + startAt: +startDate, + endAt: +endDate, + unit, + timezone, + url, + event, + token, + }), + enabled: !!websiteId, + }); const datasets = useMemo(() => { if (!data) return []; diff --git a/src/components/metrics/MetricsTable.js b/src/components/metrics/MetricsTable.js index 1d64b714b..a251756f7 100644 --- a/src/components/metrics/MetricsTable.js +++ b/src/components/metrics/MetricsTable.js @@ -35,8 +35,8 @@ export function MetricsTable({ const { get, useQuery } = useApi(); const { dir } = useLocale(); - const { data, isLoading, isFetched, error } = useQuery( - [ + const { data, isLoading, isFetched, error } = useQuery({ + queryKey: [ 'websites:metrics', { websiteId, @@ -53,11 +53,13 @@ export function MetricsTable({ city, }, ], - () => { + queryFn: () => { const filters = { url, title, referrer, os, browser, device, country, region, city }; filters[type] = undefined; + onDataLoad?.(); + return get(`/websites/${websiteId}/metrics`, { type, startAt: +startDate, @@ -65,8 +67,8 @@ export function MetricsTable({ ...filters, }); }, - { onSuccess: onDataLoad, retryDelay: delay || DEFAULT_ANIMATION_DURATION }, - ); + retryDelay: delay || DEFAULT_ANIMATION_DURATION, + }); const filteredData = useMemo(() => { if (data) { From 7c42f0da8290fce03d00c7a86660406497c044da Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 3 Dec 2023 03:07:03 -0800 Subject: [PATCH 05/28] Typescript refactor. --- package.json | 10 +-- src/app/(main)/{NavBar.js => NavBar.tsx} | 0 .../{UpdateNotice.js => UpdateNotice.tsx} | 0 .../{TestConsole.js => TestConsole.tsx} | 32 +++++----- .../dashboard/{Dashboard.js => Dashboard.tsx} | 28 ++++++--- .../{DashboardEdit.js => DashboardEdit.tsx} | 6 +- ...sButton.js => DashboardSettingsButton.tsx} | 0 ...DeleteButton.js => ReportDeleteButton.tsx} | 14 ++++- src/app/(main)/reports/ReportsDataTable.tsx | 12 +--- .../{ReportsHeader.js => ReportsHeader.tsx} | 0 .../{ReportsTable.js => ReportsTable.tsx} | 2 +- .../{BaseParameters.js => BaseParameters.tsx} | 13 +++- .../{FieldAddForm.js => FieldAddForm.tsx} | 18 ++++-- ...ggregateForm.js => FieldAggregateForm.tsx} | 12 +++- ...FieldFilterForm.js => FieldFilterForm.tsx} | 19 ++++-- ...FieldSelectForm.js => FieldSelectForm.tsx} | 17 +++++- ...lterSelectForm.js => FilterSelectForm.tsx} | 20 ++++-- .../{ParameterList.js => ParameterList.tsx} | 9 ++- .../[id]/{PopupForm.js => PopupForm.tsx} | 11 +++- .../reports/[id]/{Report.js => Report.tsx} | 16 +++-- .../[id]/{ReportBody.js => ReportBody.tsx} | 0 .../{ReportDetails.js => ReportDetails.tsx} | 2 +- .../{ReportHeader.js => ReportHeader.tsx} | 12 ++-- .../[id]/{ReportMenu.js => ReportMenu.tsx} | 0 ...ReportTemplates.js => ReportTemplates.tsx} | 0 ...aParameters.js => EventDataParameters.tsx} | 17 +++--- ...EventDataReport.js => EventDataReport.tsx} | 2 +- .../{EventDataTable.js => EventDataTable.tsx} | 0 .../{FunnelChart.js => FunnelChart.tsx} | 61 +++++++++++-------- ...nnelParameters.js => FunnelParameters.tsx} | 16 ++--- .../{FunnelReport.js => FunnelReport.tsx} | 0 .../{FunnelTable.js => FunnelTable.tsx} | 0 .../funnel/{UrlAddForm.js => UrlAddForm.tsx} | 7 ++- ...tsParameters.js => InsightsParameters.tsx} | 13 ++-- .../{InsightsReport.js => InsightsReport.tsx} | 2 +- .../{InsightsTable.js => InsightsTable.tsx} | 2 +- ...nParameters.js => RetentionParameters.tsx} | 7 +-- ...RetentionReport.js => RetentionReport.tsx} | 2 +- .../{RetentionTable.js => RetentionTable.tsx} | 2 +- ...teRangeSetting.js => DateRangeSetting.tsx} | 0 ...LanguageSetting.js => LanguageSetting.tsx} | 0 ...angeButton.js => PasswordChangeButton.tsx} | 0 ...sswordEditForm.js => PasswordEditForm.tsx} | 6 +- .../{ProfileHeader.js => ProfileHeader.tsx} | 0 ...ProfileSettings.js => ProfileSettings.tsx} | 0 .../{ThemeSetting.js => ThemeSetting.tsx} | 0 ...TimezoneSetting.js => TimezoneSetting.tsx} | 0 .../teams/{TeamAddForm.js => TeamAddForm.tsx} | 14 ++--- ...amDeleteButton.js => TeamDeleteButton.tsx} | 12 +++- .../{TeamDeleteForm.js => TeamDeleteForm.tsx} | 18 +++++- .../{TeamJoinForm.js => TeamJoinForm.tsx} | 4 +- ...TeamLeaveButton.js => TeamLeaveButton.tsx} | 10 ++- .../{TeamLeaveForm.js => TeamLeaveForm.tsx} | 35 +++++++---- .../{TeamsAddButton.js => TeamsAddButton.tsx} | 4 +- .../{TeamsDataTable.js => TeamsDataTable.tsx} | 4 +- .../teams/{TeamsHeader.js => TeamsHeader.tsx} | 0 ...TeamsJoinButton.js => TeamsJoinButton.tsx} | 0 .../teams/{TeamsTable.js => TeamsTable.tsx} | 2 +- .../teams/{WebsiteTags.js => WebsiteTags.tsx} | 10 ++- .../{TeamEditForm.js => TeamEditForm.tsx} | 0 ...veButton.js => TeamMemberRemoveButton.tsx} | 31 ++++++---- .../[id]/{TeamMembers.js => TeamMembers.tsx} | 2 +- ...amMembersTable.js => TeamMembersTable.tsx} | 10 ++- .../{TeamSettings.js => TeamSettings.tsx} | 6 +- ...bsiteAddForm.js => TeamWebsiteAddForm.tsx} | 16 ++++- ...eButton.js => TeamWebsiteRemoveButton.tsx} | 0 .../{TeamWebsites.js => TeamWebsites.tsx} | 8 ++- ...WebsitesTable.js => TeamWebsitesTable.tsx} | 12 +++- .../settings/teams/[id]/{page.js => page.tsx} | 0 .../{UserAddButton.js => UserAddButton.tsx} | 2 +- .../users/{UserAddForm.js => UserAddForm.tsx} | 6 +- ...erDeleteButton.js => UserDeleteButton.tsx} | 10 ++- .../{UserDeleteForm.js => UserDeleteForm.tsx} | 11 ++-- .../{UserEditForm.js => UserEditForm.tsx} | 26 ++++++-- src/app/(main)/settings/users/UserWebsites.js | 36 ----------- .../(main)/settings/users/UserWebsites.tsx | 26 ++++++++ .../{UsersDataTable.js => UsersDataTable.tsx} | 4 +- .../users/{UsersHeader.js => UsersHeader.tsx} | 2 +- .../users/{UsersTable.js => UsersTable.tsx} | 2 +- .../settings/users/[id]/UserSettings.js | 2 +- .../settings/users/[id]/{page.js => page.tsx} | 0 ...bsiteAddButton.js => WebsiteAddButton.tsx} | 2 +- .../settings/websites/WebsiteAddForm.tsx | 8 ++- .../settings/websites/WebsiteSettings.js | 2 +- .../{WebsitesHeader.js => WebsitesHeader.tsx} | 0 .../{WebsitesTable.js => WebsitesTable.tsx} | 12 +++- .../[id]/{ShareUrl.js => ShareUrl.tsx} | 10 +-- .../{TrackingCode.js => TrackingCode.tsx} | 8 ++- .../[id]/{WebsiteData.js => WebsiteData.tsx} | 8 ++- ...iteDeleteForm.js => WebsiteDeleteForm.tsx} | 14 ++++- ...WebsiteEditForm.js => WebsiteEditForm.tsx} | 14 ++++- ...bsiteResetForm.js => WebsiteResetForm.tsx} | 16 ++++- .../websites/[id]/{page.js => page.tsx} | 0 .../{WebsitesBrowse.js => WebsitesBrowse.tsx} | 2 +- .../{WebsiteChart.js => WebsiteChart.tsx} | 4 +- ...bsiteChartList.js => WebsiteChartList.tsx} | 10 ++- .../{WebsiteDetails.js => WebsiteDetails.tsx} | 7 +-- ...ilterButton.js => WebsiteFilterButton.tsx} | 12 +++- .../{WebsiteHeader.js => WebsiteHeader.tsx} | 11 +++- ...WebsiteMenuView.js => WebsiteMenuView.tsx} | 8 ++- ...iteMetricsBar.js => WebsiteMetricsBar.tsx} | 12 +++- ...bsiteTableView.js => WebsiteTableView.tsx} | 2 +- ...aMetricsBar.js => EventDataMetricsBar.tsx} | 2 +- .../{EventDataTable.js => EventDataTable.tsx} | 0 ...aValueTable.js => EventDataValueTable.tsx} | 2 +- .../[id]/event-data/{page.js => page.tsx} | 0 src/app/login/{LoginForm.js => LoginForm.tsx} | 9 +-- src/app/logout/{Logout.js => Logout.tsx} | 0 .../share/[...id]/{Footer.js => Footer.tsx} | 0 .../share/[...id]/{Header.js => Header.tsx} | 0 src/app/share/[...id]/{Share.js => Share.tsx} | 0 src/components/common/DataTable.tsx | 6 +- src/components/common/EmptyPlaceholder.tsx | 4 +- src/components/common/FilterButtons.tsx | 2 +- src/components/declarations.d.ts | 1 + src/components/hooks/useApiFilter.ts | 28 --------- src/components/hooks/useDateRange.ts | 2 +- src/components/hooks/useFilterQuery.ts | 19 ++++-- src/components/hooks/useNavigation.ts | 7 ++- src/components/hooks/useReports.ts | 30 ++++----- src/components/hooks/useShareToken.ts | 6 +- .../input/{DateFilter.js => DateFilter.tsx} | 25 +++++--- .../{LanguageButton.js => LanguageButton.tsx} | 6 +- .../{LogoutButton.js => LogoutButton.tsx} | 6 +- .../input/{MonthSelect.js => MonthSelect.tsx} | 10 +-- .../{ProfileButton.js => ProfileButton.tsx} | 0 .../{RefreshButton.js => RefreshButton.tsx} | 8 ++- .../{SettingsButton.js => SettingsButton.tsx} | 0 .../input/{ThemeButton.js => ThemeButton.tsx} | 0 ...iteDateFilter.js => WebsiteDateFilter.tsx} | 2 +- .../{WebsiteSelect.js => WebsiteSelect.tsx} | 8 ++- src/components/layout/Grid.js | 18 ------ src/components/layout/Grid.tsx | 34 +++++++++++ .../layout/{NavGroup.js => NavGroup.tsx} | 10 ++- .../layout/{SideNav.js => SideNav.tsx} | 11 +++- src/components/{messages.js => messages.ts} | 0 .../{ActiveUsers.js => ActiveUsers.tsx} | 12 +++- .../metrics/{BarChart.js => BarChart.tsx} | 31 +++++++--- .../{BrowsersTable.js => BrowsersTable.tsx} | 5 +- .../{CitiesTable.js => CitiesTable.tsx} | 7 +-- .../{CountriesTable.js => CountriesTable.tsx} | 19 ++++-- .../{DatePickerForm.js => DatePickerForm.tsx} | 2 +- .../{DevicesTable.js => DevicesTable.tsx} | 5 +- .../{EventsChart.js => EventsChart.tsx} | 9 ++- .../{EventsTable.js => EventsTable.tsx} | 7 +-- .../metrics/{FilterTags.js => FilterTags.tsx} | 4 +- .../{LanguagesTable.js => LanguagesTable.tsx} | 8 ++- .../metrics/{Legend.js => Legend.tsx} | 0 .../metrics/{ListTable.js => ListTable.tsx} | 17 +++++- .../metrics/{MetricCard.js => MetricCard.tsx} | 16 ++++- .../metrics/{MetricsBar.js => MetricsBar.tsx} | 10 ++- .../{MetricsTable.js => MetricsTable.tsx} | 39 +++++++----- .../metrics/{OSTable.js => OSTable.tsx} | 6 +- .../metrics/{PagesTable.js => PagesTable.tsx} | 9 ++- .../{PageviewsChart.js => PageviewsChart.tsx} | 16 +++-- ...etersTable.js => QueryParametersTable.tsx} | 8 ++- .../{RealtimeChart.js => RealtimeChart.tsx} | 20 +++--- .../{ReferrersTable.js => ReferrersTable.tsx} | 5 +- .../{RegionsTable.js => RegionsTable.tsx} | 7 +-- .../{ScreenTable.js => ScreenTable.tsx} | 5 +- .../metrics/{WorldMap.js => WorldMap.tsx} | 8 ++- src/lib/{charts.js => charts.tsx} | 12 ++-- src/lib/{crypto.js => crypto.ts} | 4 +- src/lib/{db.js => db.ts} | 4 +- src/lib/{filters.js => filters.ts} | 10 +-- src/lib/{format.js => format.ts} | 20 +++--- src/lib/{lang.js => lang.ts} | 4 +- src/lib/types.ts | 2 +- src/store/{app.js => app.ts} | 0 src/store/{cache.js => cache.ts} | 0 src/store/{dashboard.js => dashboard.ts} | 0 src/store/{version.js => version.ts} | 0 yarn.lock | 53 ++++++++-------- 173 files changed, 968 insertions(+), 549 deletions(-) rename src/app/(main)/{NavBar.js => NavBar.tsx} (100%) rename src/app/(main)/{UpdateNotice.js => UpdateNotice.tsx} (100%) rename src/app/(main)/console/{TestConsole.js => TestConsole.tsx} (85%) rename src/app/(main)/dashboard/{Dashboard.js => Dashboard.tsx} (80%) rename src/app/(main)/dashboard/{DashboardEdit.js => DashboardEdit.tsx} (95%) rename src/app/(main)/dashboard/{DashboardSettingsButton.js => DashboardSettingsButton.tsx} (100%) rename src/app/(main)/reports/{ReportDeleteButton.js => ReportDeleteButton.tsx} (81%) rename src/app/(main)/reports/{ReportsHeader.js => ReportsHeader.tsx} (100%) rename src/app/(main)/reports/{ReportsTable.js => ReportsTable.tsx} (94%) rename src/app/(main)/reports/[id]/{BaseParameters.js => BaseParameters.tsx} (83%) rename src/app/(main)/reports/[id]/{FieldAddForm.js => FieldAddForm.tsx} (76%) rename src/app/(main)/reports/[id]/{FieldAggregateForm.js => FieldAggregateForm.tsx} (86%) rename src/app/(main)/reports/[id]/{FieldFilterForm.js => FieldFilterForm.tsx} (86%) rename src/app/(main)/reports/[id]/{FieldSelectForm.js => FieldSelectForm.tsx} (65%) rename src/app/(main)/reports/[id]/{FilterSelectForm.js => FilterSelectForm.tsx} (67%) rename src/app/(main)/reports/[id]/{ParameterList.js => ParameterList.tsx} (82%) rename src/app/(main)/reports/[id]/{PopupForm.js => PopupForm.tsx} (59%) rename src/app/(main)/reports/[id]/{Report.js => Report.tsx} (56%) rename src/app/(main)/reports/[id]/{ReportBody.js => ReportBody.tsx} (100%) rename src/app/(main)/reports/[id]/{ReportDetails.js => ReportDetails.tsx} (90%) rename src/app/(main)/reports/[id]/{ReportHeader.js => ReportHeader.tsx} (92%) rename src/app/(main)/reports/[id]/{ReportMenu.js => ReportMenu.tsx} (100%) rename src/app/(main)/reports/create/{ReportTemplates.js => ReportTemplates.tsx} (100%) rename src/app/(main)/reports/event-data/{EventDataParameters.js => EventDataParameters.tsx} (91%) rename src/app/(main)/reports/event-data/{EventDataReport.js => EventDataReport.tsx} (89%) rename src/app/(main)/reports/event-data/{EventDataTable.js => EventDataTable.tsx} (100%) rename src/app/(main)/reports/funnel/{FunnelChart.js => FunnelChart.tsx} (52%) rename src/app/(main)/reports/funnel/{FunnelParameters.js => FunnelParameters.tsx} (84%) rename src/app/(main)/reports/funnel/{FunnelReport.js => FunnelReport.tsx} (100%) rename src/app/(main)/reports/funnel/{FunnelTable.js => FunnelTable.tsx} (100%) rename src/app/(main)/reports/funnel/{UrlAddForm.js => UrlAddForm.tsx} (86%) rename src/app/(main)/reports/insights/{InsightsParameters.js => InsightsParameters.tsx} (93%) rename src/app/(main)/reports/insights/{InsightsReport.js => InsightsReport.tsx} (90%) rename src/app/(main)/reports/insights/{InsightsTable.js => InsightsTable.tsx} (96%) rename src/app/(main)/reports/retention/{RetentionParameters.js => RetentionParameters.tsx} (87%) rename src/app/(main)/reports/retention/{RetentionReport.js => RetentionReport.tsx} (92%) rename src/app/(main)/reports/retention/{RetentionTable.js => RetentionTable.tsx} (96%) rename src/app/(main)/settings/profile/{DateRangeSetting.js => DateRangeSetting.tsx} (100%) rename src/app/(main)/settings/profile/{LanguageSetting.js => LanguageSetting.tsx} (100%) rename src/app/(main)/settings/profile/{PasswordChangeButton.js => PasswordChangeButton.tsx} (100%) rename src/app/(main)/settings/profile/{PasswordEditForm.js => PasswordEditForm.tsx} (91%) rename src/app/(main)/settings/profile/{ProfileHeader.js => ProfileHeader.tsx} (100%) rename src/app/(main)/settings/profile/{ProfileSettings.js => ProfileSettings.tsx} (100%) rename src/app/(main)/settings/profile/{ThemeSetting.js => ThemeSetting.tsx} (100%) rename src/app/(main)/settings/profile/{TimezoneSetting.js => TimezoneSetting.tsx} (100%) rename src/app/(main)/settings/teams/{TeamAddForm.js => TeamAddForm.tsx} (72%) rename src/app/(main)/settings/teams/{TeamDeleteButton.js => TeamDeleteButton.tsx} (79%) rename src/app/(main)/settings/teams/{TeamDeleteForm.js => TeamDeleteForm.tsx} (72%) rename src/app/(main)/settings/teams/{TeamJoinForm.js => TeamJoinForm.tsx} (85%) rename src/app/(main)/settings/teams/{TeamLeaveButton.js => TeamLeaveButton.tsx} (87%) rename src/app/(main)/settings/teams/{TeamLeaveForm.js => TeamLeaveForm.tsx} (60%) rename src/app/(main)/settings/teams/{TeamsAddButton.js => TeamsAddButton.tsx} (79%) rename src/app/(main)/settings/teams/{TeamsDataTable.js => TeamsDataTable.tsx} (88%) rename src/app/(main)/settings/teams/{TeamsHeader.js => TeamsHeader.tsx} (100%) rename src/app/(main)/settings/teams/{TeamsJoinButton.js => TeamsJoinButton.tsx} (100%) rename src/app/(main)/settings/teams/{TeamsTable.js => TeamsTable.tsx} (96%) rename src/app/(main)/settings/teams/{WebsiteTags.js => WebsiteTags.tsx} (83%) rename src/app/(main)/settings/teams/[id]/{TeamEditForm.js => TeamEditForm.tsx} (100%) rename src/app/(main)/settings/teams/[id]/{TeamMemberRemoveButton.js => TeamMemberRemoveButton.tsx} (59%) rename src/app/(main)/settings/teams/[id]/{TeamMembers.js => TeamMembers.tsx} (89%) rename src/app/(main)/settings/teams/[id]/{TeamMembersTable.js => TeamMembersTable.tsx} (90%) rename src/app/(main)/settings/teams/[id]/{TeamSettings.js => TeamSettings.tsx} (91%) rename src/app/(main)/settings/teams/[id]/{TeamWebsiteAddForm.js => TeamWebsiteAddForm.tsx} (86%) rename src/app/(main)/settings/teams/[id]/{TeamWebsiteRemoveButton.js => TeamWebsiteRemoveButton.tsx} (100%) rename src/app/(main)/settings/teams/[id]/{TeamWebsites.js => TeamWebsites.tsx} (86%) rename src/app/(main)/settings/teams/[id]/{TeamWebsitesTable.js => TeamWebsitesTable.tsx} (85%) rename src/app/(main)/settings/teams/[id]/{page.js => page.tsx} (100%) rename src/app/(main)/settings/users/{UserAddButton.js => UserAddButton.tsx} (92%) rename src/app/(main)/settings/users/{UserAddForm.js => UserAddForm.tsx} (92%) rename src/app/(main)/settings/users/{UserDeleteButton.js => UserDeleteButton.tsx} (85%) rename src/app/(main)/settings/users/{UserDeleteForm.js => UserDeleteForm.tsx} (72%) rename src/app/(main)/settings/users/{UserEditForm.js => UserEditForm.tsx} (82%) delete mode 100644 src/app/(main)/settings/users/UserWebsites.js create mode 100644 src/app/(main)/settings/users/UserWebsites.tsx rename src/app/(main)/settings/users/{UsersDataTable.js => UsersDataTable.tsx} (82%) rename src/app/(main)/settings/users/{UsersHeader.js => UsersHeader.tsx} (85%) rename src/app/(main)/settings/users/{UsersTable.js => UsersTable.tsx} (96%) rename src/app/(main)/settings/users/[id]/{page.js => page.tsx} (100%) rename src/app/(main)/settings/websites/{WebsiteAddButton.js => WebsiteAddButton.tsx} (92%) rename src/app/(main)/settings/websites/{WebsitesHeader.js => WebsitesHeader.tsx} (100%) rename src/app/(main)/settings/websites/{WebsitesTable.js => WebsitesTable.tsx} (90%) rename src/app/(main)/settings/websites/[id]/{ShareUrl.js => ShareUrl.tsx} (91%) rename src/app/(main)/settings/websites/[id]/{TrackingCode.js => TrackingCode.tsx} (87%) rename src/app/(main)/settings/websites/[id]/{WebsiteData.js => WebsiteData.tsx} (92%) rename src/app/(main)/settings/websites/[id]/{WebsiteDeleteForm.js => WebsiteDeleteForm.tsx} (82%) rename src/app/(main)/settings/websites/[id]/{WebsiteEditForm.js => WebsiteEditForm.tsx} (85%) rename src/app/(main)/settings/websites/[id]/{WebsiteResetForm.js => WebsiteResetForm.tsx} (78%) rename src/app/(main)/settings/websites/[id]/{page.js => page.tsx} (100%) rename src/app/(main)/websites/{WebsitesBrowse.js => WebsitesBrowse.tsx} (91%) rename src/app/(main)/websites/[id]/{WebsiteChart.js => WebsiteChart.tsx} (90%) rename src/app/(main)/websites/[id]/{WebsiteChartList.js => WebsiteChartList.tsx} (91%) rename src/app/(main)/websites/[id]/{WebsiteDetails.js => WebsiteDetails.tsx} (87%) rename src/app/(main)/websites/[id]/{WebsiteFilterButton.js => WebsiteFilterButton.tsx} (91%) rename src/app/(main)/websites/[id]/{WebsiteHeader.js => WebsiteHeader.tsx} (92%) rename src/app/(main)/websites/[id]/{WebsiteMenuView.js => WebsiteMenuView.tsx} (97%) rename src/app/(main)/websites/[id]/{WebsiteMetricsBar.js => WebsiteMetricsBar.tsx} (93%) rename src/app/(main)/websites/[id]/{WebsiteTableView.js => WebsiteTableView.tsx} (94%) rename src/app/(main)/websites/[id]/event-data/{EventDataMetricsBar.js => EventDataMetricsBar.tsx} (94%) rename src/app/(main)/websites/[id]/event-data/{EventDataTable.js => EventDataTable.tsx} (100%) rename src/app/(main)/websites/[id]/event-data/{EventDataValueTable.js => EventDataValueTable.tsx} (94%) rename src/app/(main)/websites/[id]/event-data/{page.js => page.tsx} (100%) rename src/app/login/{LoginForm.js => LoginForm.tsx} (90%) rename src/app/logout/{Logout.js => Logout.tsx} (100%) rename src/app/share/[...id]/{Footer.js => Footer.tsx} (100%) rename src/app/share/[...id]/{Header.js => Header.tsx} (100%) rename src/app/share/[...id]/{Share.js => Share.tsx} (100%) delete mode 100644 src/components/hooks/useApiFilter.ts rename src/components/input/{DateFilter.js => DateFilter.tsx} (89%) rename src/components/input/{LanguageButton.js => LanguageButton.tsx} (88%) rename src/components/input/{LogoutButton.js => LogoutButton.tsx} (80%) rename src/components/input/{MonthSelect.js => MonthSelect.tsx} (87%) rename src/components/input/{ProfileButton.js => ProfileButton.tsx} (100%) rename src/components/input/{RefreshButton.js => RefreshButton.tsx} (87%) rename src/components/input/{SettingsButton.js => SettingsButton.tsx} (100%) rename src/components/input/{ThemeButton.js => ThemeButton.tsx} (100%) rename src/components/input/{WebsiteDateFilter.js => WebsiteDateFilter.tsx} (95%) rename src/components/input/{WebsiteSelect.js => WebsiteSelect.tsx} (88%) delete mode 100644 src/components/layout/Grid.js create mode 100644 src/components/layout/Grid.tsx rename src/components/layout/{NavGroup.js => NavGroup.tsx} (90%) rename src/components/layout/{SideNav.js => SideNav.tsx} (82%) rename src/components/{messages.js => messages.ts} (100%) rename src/components/metrics/{ActiveUsers.js => ActiveUsers.tsx} (85%) rename src/components/metrics/{BarChart.js => BarChart.tsx} (83%) rename src/components/metrics/{BrowsersTable.js => BrowsersTable.tsx} (85%) rename src/components/metrics/{CitiesTable.js => CitiesTable.tsx} (85%) rename src/components/metrics/{CountriesTable.js => CountriesTable.tsx} (73%) rename src/components/metrics/{DatePickerForm.js => DatePickerForm.tsx} (96%) rename src/components/metrics/{DevicesTable.js => DevicesTable.tsx} (86%) rename src/components/metrics/{EventsChart.js => EventsChart.tsx} (92%) rename src/components/metrics/{EventsTable.js => EventsTable.tsx} (69%) rename src/components/metrics/{FilterTags.js => FilterTags.tsx} (92%) rename src/components/metrics/{LanguagesTable.js => LanguagesTable.tsx} (80%) rename src/components/metrics/{Legend.js => Legend.tsx} (100%) rename src/components/metrics/{ListTable.js => ListTable.tsx} (87%) rename src/components/metrics/{MetricCard.js => MetricCard.tsx} (77%) rename src/components/metrics/{MetricsBar.js => MetricsBar.tsx} (79%) rename src/components/metrics/{MetricsTable.js => MetricsTable.tsx} (80%) rename src/components/metrics/{OSTable.js => OSTable.tsx} (80%) rename src/components/metrics/{PagesTable.js => PagesTable.tsx} (83%) rename src/components/metrics/{PageviewsChart.js => PageviewsChart.tsx} (74%) rename src/components/metrics/{QueryParametersTable.js => QueryParametersTable.tsx} (88%) rename src/components/metrics/{RealtimeChart.js => RealtimeChart.tsx} (81%) rename src/components/metrics/{ReferrersTable.js => ReferrersTable.tsx} (83%) rename src/components/metrics/{RegionsTable.js => RegionsTable.tsx} (86%) rename src/components/metrics/{ScreenTable.js => ScreenTable.tsx} (70%) rename src/components/metrics/{WorldMap.js => WorldMap.tsx} (91%) rename src/lib/{charts.js => charts.tsx} (77%) rename src/lib/{crypto.js => crypto.ts} (85%) rename src/lib/{db.js => db.ts} (91%) rename src/lib/{filters.js => filters.ts} (86%) rename src/lib/{format.js => format.ts} (74%) rename src/lib/{lang.js => lang.ts} (96%) rename src/store/{app.js => app.ts} (100%) rename src/store/{cache.js => cache.ts} (100%) rename src/store/{dashboard.js => dashboard.ts} (100%) rename src/store/{version.js => version.ts} (100%) diff --git a/package.json b/package.json index feec3db80..a158696cf 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@prisma/client": "5.6.0", "@prisma/extension-read-replicas": "^0.3.0", "@react-spring/web": "^9.7.3", - "@tanstack/react-query": "^4.33.0", + "@tanstack/react-query": "^5.12.2", "@umami/prisma-client": "^0.8.0", "@umami/redis-client": "^0.18.0", "chalk": "^4.1.1", @@ -94,12 +94,12 @@ "maxmind": "^4.3.6", "moment-timezone": "^0.5.35", "next": "13.5.6", - "next-basics": "^0.37.0", + "next-basics": "^0.39.0", "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", "prisma": "5.6.0", "react": "^18.2.0", - "react-basics": "^0.110.0", + "react-basics": "^0.114.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.4", @@ -127,8 +127,8 @@ "@svgr/rollup": "^8.1.0", "@svgr/webpack": "^8.1.0", "@types/node": "^20.9.0", - "@types/react": "^18.2.37", - "@types/react-dom": "^18.2.15", + "@types/react": "^18.2.41", + "@types/react-dom": "^18.2.17", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", "cross-env": "^7.0.3", diff --git a/src/app/(main)/NavBar.js b/src/app/(main)/NavBar.tsx similarity index 100% rename from src/app/(main)/NavBar.js rename to src/app/(main)/NavBar.tsx diff --git a/src/app/(main)/UpdateNotice.js b/src/app/(main)/UpdateNotice.tsx similarity index 100% rename from src/app/(main)/UpdateNotice.js rename to src/app/(main)/UpdateNotice.tsx diff --git a/src/app/(main)/console/TestConsole.js b/src/app/(main)/console/TestConsole.tsx similarity index 85% rename from src/app/(main)/console/TestConsole.js rename to src/app/(main)/console/TestConsole.tsx index 7aae09b2a..0bb807ff5 100644 --- a/src/app/(main)/console/TestConsole.js +++ b/src/app/(main)/console/TestConsole.tsx @@ -1,18 +1,18 @@ 'use client'; +import { Button } from 'react-basics'; +import Head from 'next/head'; +import Link from 'next/link'; +import Script from 'next/script'; import WebsiteSelect from 'components/input/WebsiteSelect'; import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import EventsChart from 'components/metrics/EventsChart'; -import WebsiteChart from '../../(main)/websites/[id]/WebsiteChart'; +import WebsiteChart from 'app/(main)/websites/[id]/WebsiteChart'; import useApi from 'components/hooks/useApi'; -import Head from 'next/head'; -import Link from 'next/link'; import useNavigation from 'components/hooks/useNavigation'; -import Script from 'next/script'; -import { Button } from 'react-basics'; import styles from './TestConsole.module.css'; -export function TestConsole({ websiteId }) { +export function TestConsole({ websiteId }: { websiteId: string }) { const { get, useQuery } = useApi(); const { data, isLoading, error } = useQuery({ queryKey: ['websites:me'], @@ -20,14 +20,14 @@ export function TestConsole({ websiteId }) { }); const { router } = useNavigation(); - function handleChange(value) { + function handleChange(value: string) { router.push(`/console/${value}`); } function handleClick() { - window.umami.track({ url: '/page-view', referrer: 'https://www.google.com' }); - window.umami.track('track-event-no-data'); - window.umami.track('track-event-with-data', { + window['umami'].track({ url: '/page-view', referrer: 'https://www.google.com' }); + window['umami'].track('track-event-no-data'); + window['umami'].track('track-event-with-data', { test: 'test-data', boolean: true, booleanError: 'true', @@ -47,7 +47,7 @@ export function TestConsole({ websiteId }) { } function handleIdentifyClick() { - window.umami.identify({ + window['umami'].identify({ userId: 123, name: 'brian', number: Math.random() * 100, @@ -74,7 +74,7 @@ export function TestConsole({ websiteId }) { const website = data?.data.find(({ id }) => websiteId === id); return ( - + {website ? `${website.name} | Umami Console` : 'Umami Console'} @@ -116,7 +116,7 @@ export function TestConsole({ websiteId }) {
Click events
-

@@ -125,18 +125,18 @@ export function TestConsole({ websiteId }) { data-umami-event="button-click" data-umami-event-name="bob" data-umami-event-id="123" - variant="action" + variant="primary" > Send event with data

Javascript events
-

-

diff --git a/src/app/(main)/dashboard/Dashboard.js b/src/app/(main)/dashboard/Dashboard.tsx similarity index 80% rename from src/app/(main)/dashboard/Dashboard.js rename to src/app/(main)/dashboard/Dashboard.tsx index 6f0659bbe..1afc0da2e 100644 --- a/src/app/(main)/dashboard/Dashboard.js +++ b/src/app/(main)/dashboard/Dashboard.tsx @@ -11,23 +11,31 @@ import useApi from 'components/hooks/useApi'; import useDashboard from 'store/dashboard'; import useMessages from 'components/hooks/useMessages'; import useLocale from 'components/hooks/useLocale'; -import useApiFilter from 'components/hooks/useApiFilter'; +import useFilterQuery from 'components/hooks/useFilterQuery'; export function Dashboard() { const { formatMessage, labels, messages } = useMessages(); const { showCharts, editing } = useDashboard(); const { dir } = useLocale(); - const { get, useQuery } = useApi(); - const { page, handlePageChange } = useApiFilter(); + const { get } = useApi(); const pageSize = 10; - const { data: result, isLoading } = useQuery({ - queryKey: ['websites', page, pageSize], - queryFn: () => get('/websites', { includeTeams: 1, page, pageSize }), - }); - const { data, count } = result || {}; - const hasData = data && data?.length !== 0; - if (isLoading) { + const { query, params, setParams, result } = useFilterQuery({ + queryKey: ['dashboard:websites'], + queryFn: (params: any) => { + return get(`/websites`, { ...params, includeTeams: true, pageSize }); + }, + }); + + const handlePageChange = (page: number) => { + setParams({ ...params, page }); + }; + + const { data, count } = result || {}; + const hasData = !!(data as any)?.length; + const { page } = params; + + if (query.isLoading) { return ; } diff --git a/src/app/(main)/dashboard/DashboardEdit.js b/src/app/(main)/dashboard/DashboardEdit.tsx similarity index 95% rename from src/app/(main)/dashboard/DashboardEdit.js rename to src/app/(main)/dashboard/DashboardEdit.tsx index 0a8734e8d..356380388 100644 --- a/src/app/(main)/dashboard/DashboardEdit.js +++ b/src/app/(main)/dashboard/DashboardEdit.tsx @@ -60,13 +60,13 @@ export function DashboardEdit() { return ( <>
- - -
diff --git a/src/app/(main)/dashboard/DashboardSettingsButton.js b/src/app/(main)/dashboard/DashboardSettingsButton.tsx similarity index 100% rename from src/app/(main)/dashboard/DashboardSettingsButton.js rename to src/app/(main)/dashboard/DashboardSettingsButton.tsx diff --git a/src/app/(main)/reports/ReportDeleteButton.js b/src/app/(main)/reports/ReportDeleteButton.tsx similarity index 81% rename from src/app/(main)/reports/ReportDeleteButton.js rename to src/app/(main)/reports/ReportDeleteButton.tsx index 35809a985..d3f1bb4f3 100644 --- a/src/app/(main)/reports/ReportDeleteButton.js +++ b/src/app/(main)/reports/ReportDeleteButton.tsx @@ -3,13 +3,21 @@ import ConfirmDeleteForm from 'components/common/ConfirmDeleteForm'; import { useApi, useMessages } from 'components/hooks'; import { setValue } from 'store/cache'; -export function ReportDeleteButton({ reportId, reportName, onDelete }) { +export function ReportDeleteButton({ + reportId, + reportName, + onDelete, +}: { + reportId: string; + reportName: string; + onDelete?: () => void; +}) { const { formatMessage, labels } = useMessages(); const { del, useMutation } = useApi(); const { mutate } = useMutation(reportId => del(`/reports/${reportId}`)); - const handleConfirm = close => { - mutate(reportId, { + const handleConfirm = (close: () => void) => { + mutate(reportId as any, { onSuccess: () => { setValue('reports', Date.now()); onDelete?.(); diff --git a/src/app/(main)/reports/ReportsDataTable.tsx b/src/app/(main)/reports/ReportsDataTable.tsx index eeef203b4..3ede47839 100644 --- a/src/app/(main)/reports/ReportsDataTable.tsx +++ b/src/app/(main)/reports/ReportsDataTable.tsx @@ -1,18 +1,10 @@ 'use client'; -import { useApi } from 'components/hooks'; +import { useReports } from 'components/hooks'; import ReportsTable from './ReportsTable'; -import useFilterQuery from 'components/hooks/useFilterQuery'; import DataTable from 'components/common/DataTable'; -import useCache from 'store/cache'; export default function ReportsDataTable({ websiteId }: { websiteId?: string }) { - const { get } = useApi(); - const modified = useCache(state => (state as any)?.reports); - const queryResult = useFilterQuery({ - queryKey: ['reports', { websiteId, modified }], - queryFn: (params: any) => - get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params), - }); + const queryResult = useReports(websiteId); return ( diff --git a/src/app/(main)/reports/ReportsHeader.js b/src/app/(main)/reports/ReportsHeader.tsx similarity index 100% rename from src/app/(main)/reports/ReportsHeader.js rename to src/app/(main)/reports/ReportsHeader.tsx diff --git a/src/app/(main)/reports/ReportsTable.js b/src/app/(main)/reports/ReportsTable.tsx similarity index 94% rename from src/app/(main)/reports/ReportsTable.js rename to src/app/(main)/reports/ReportsTable.tsx index 6b2a79320..882110eec 100644 --- a/src/app/(main)/reports/ReportsTable.js +++ b/src/app/(main)/reports/ReportsTable.tsx @@ -5,7 +5,7 @@ import useUser from 'components/hooks/useUser'; import { REPORT_TYPES } from 'lib/constants'; import ReportDeleteButton from './ReportDeleteButton'; -export function ReportsTable({ data = [], showDomain }) { +export function ReportsTable({ data = [], showDomain }: { data: any[]; showDomain?: boolean }) { const { formatMessage, labels } = useMessages(); const { user } = useUser(); const breakpoint = useBreakpoint(); diff --git a/src/app/(main)/reports/[id]/BaseParameters.js b/src/app/(main)/reports/[id]/BaseParameters.tsx similarity index 83% rename from src/app/(main)/reports/[id]/BaseParameters.js rename to src/app/(main)/reports/[id]/BaseParameters.tsx index a54ef4f36..9b6be5d8c 100644 --- a/src/app/(main)/reports/[id]/BaseParameters.js +++ b/src/app/(main)/reports/[id]/BaseParameters.tsx @@ -6,12 +6,19 @@ import WebsiteSelect from 'components/input/WebsiteSelect'; import { useMessages } from 'components/hooks'; import { ReportContext } from './Report'; +export interface BaseParametersProps { + showWebsiteSelect?: boolean; + allowWebsiteSelect?: boolean; + showDateSelect?: boolean; + allowDateSelect?: boolean; +} + export function BaseParameters({ showWebsiteSelect = true, allowWebsiteSelect = true, showDateSelect = true, allowDateSelect = true, -}) { +}: BaseParametersProps) { const { report, updateReport } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); @@ -19,11 +26,11 @@ export function BaseParameters({ const { websiteId, dateRange } = parameters || {}; const { value, startDate, endDate } = dateRange || {}; - const handleWebsiteSelect = websiteId => { + const handleWebsiteSelect = (websiteId: string) => { updateReport({ websiteId, parameters: { websiteId } }); }; - const handleDateChange = value => { + const handleDateChange = (value: string) => { updateReport({ parameters: { dateRange: { ...parseDateRange(value) } } }); }; diff --git a/src/app/(main)/reports/[id]/FieldAddForm.js b/src/app/(main)/reports/[id]/FieldAddForm.tsx similarity index 76% rename from src/app/(main)/reports/[id]/FieldAddForm.js rename to src/app/(main)/reports/[id]/FieldAddForm.tsx index 6923bceb6..9db472d8d 100644 --- a/src/app/(main)/reports/[id]/FieldAddForm.js +++ b/src/app/(main)/reports/[id]/FieldAddForm.tsx @@ -7,10 +7,20 @@ import FieldAggregateForm from './FieldAggregateForm'; import FieldFilterForm from './FieldFilterForm'; import styles from './FieldAddForm.module.css'; -export function FieldAddForm({ fields = [], group, onAdd, onClose }) { - const [selected, setSelected] = useState(); +export function FieldAddForm({ + fields = [], + group, + onAdd, + onClose, +}: { + fields?: any[]; + group: string; + onAdd: (group: string, value: string) => void; + onClose: () => void; +}) { + const [selected, setSelected] = useState<{ name: string; type: string; value: string }>(); - const handleSelect = value => { + const handleSelect = (value: any) => { const { type } = value; if (group === REPORT_PARAMETERS.groups || type === 'array' || type === 'boolean') { @@ -22,7 +32,7 @@ export function FieldAddForm({ fields = [], group, onAdd, onClose }) { setSelected(value); }; - const handleSave = value => { + const handleSave = (value: any) => { onAdd(group, value); onClose(); }; diff --git a/src/app/(main)/reports/[id]/FieldAggregateForm.js b/src/app/(main)/reports/[id]/FieldAggregateForm.tsx similarity index 86% rename from src/app/(main)/reports/[id]/FieldAggregateForm.js rename to src/app/(main)/reports/[id]/FieldAggregateForm.tsx index 34b679808..6b5bf6360 100644 --- a/src/app/(main)/reports/[id]/FieldAggregateForm.js +++ b/src/app/(main)/reports/[id]/FieldAggregateForm.tsx @@ -1,7 +1,15 @@ import { Form, FormRow, Menu, Item } from 'react-basics'; import { useMessages } from 'components/hooks'; -export default function FieldAggregateForm({ name, type, onSelect }) { +export default function FieldAggregateForm({ + name, + type, + onSelect, +}: { + name: string; + type: string; + onSelect: (key: any) => void; +}) { const { formatMessage, labels } = useMessages(); const options = { @@ -27,7 +35,7 @@ export default function FieldAggregateForm({ name, type, onSelect }) { const items = options[type]; - const handleSelect = value => { + const handleSelect = (value: any) => { onSelect({ name, type, value }); }; diff --git a/src/app/(main)/reports/[id]/FieldFilterForm.js b/src/app/(main)/reports/[id]/FieldFilterForm.tsx similarity index 86% rename from src/app/(main)/reports/[id]/FieldFilterForm.js rename to src/app/(main)/reports/[id]/FieldFilterForm.tsx index ea80f82a1..4af7febfb 100644 --- a/src/app/(main)/reports/[id]/FieldFilterForm.js +++ b/src/app/(main)/reports/[id]/FieldFilterForm.tsx @@ -3,6 +3,15 @@ import { Form, FormRow, Item, Flexbox, Dropdown, Button } from 'react-basics'; import { useMessages, useFilters, useFormat, useLocale } from 'components/hooks'; import styles from './FieldFilterForm.module.css'; +export interface FieldFilterFormProps { + name: string; + label?: string; + type: string; + values?: any[]; + onSelect?: (key: any) => void; + allowFilterSelect?: boolean; +} + export default function FieldFilterForm({ name, label, @@ -10,7 +19,7 @@ export default function FieldFilterForm({ values, onSelect, allowFilterSelect = true, -}) { +}: FieldFilterFormProps) { const { formatMessage, labels } = useMessages(); const [filter, setFilter] = useState('eq'); const [value, setValue] = useState(); @@ -21,7 +30,7 @@ export default function FieldFilterForm({ const formattedValues = useMemo(() => { const formatted = {}; - const format = val => { + const format = (val: string) => { formatted[val] = formatValue(val, name); return formatted[val]; }; @@ -56,7 +65,7 @@ export default function FieldFilterForm({ items={filters} value={filter} renderValue={renderFilterValue} - onChange={setFilter} + onChange={(key: any) => setFilter(key)} > {({ value, label }) => { return {label}; @@ -69,12 +78,12 @@ export default function FieldFilterForm({ items={values} value={value} renderValue={renderValue} - onChange={setValue} + onChange={(key: any) => setValue(key)} style={{ minWidth: '250px', }} > - {value => { + {(value: string) => { return {formattedValues[value]}; }} diff --git a/src/app/(main)/reports/[id]/FieldSelectForm.js b/src/app/(main)/reports/[id]/FieldSelectForm.tsx similarity index 65% rename from src/app/(main)/reports/[id]/FieldSelectForm.js rename to src/app/(main)/reports/[id]/FieldSelectForm.tsx index e7661511d..dfd402cf0 100644 --- a/src/app/(main)/reports/[id]/FieldSelectForm.js +++ b/src/app/(main)/reports/[id]/FieldSelectForm.tsx @@ -1,15 +1,26 @@ import { Menu, Item, Form, FormRow } from 'react-basics'; import { useMessages } from 'components/hooks'; import styles from './FieldSelectForm.module.css'; +import { Key } from 'react'; -export default function FieldSelectForm({ items, onSelect, showType = true }) { +export interface FieldSelectFormProps { + fields?: any[]; + onSelect?: (key: any) => void; + showType?: boolean; +} + +export default function FieldSelectForm({ + fields = [], + onSelect, + showType = true, +}: FieldSelectFormProps) { const { formatMessage, labels } = useMessages(); return (
- onSelect(items[key])}> - {items.map(({ name, label, type }, index) => { + onSelect(fields[key as any])}> + {fields.map(({ name, label, type }: any, index: Key) => { return (
{label || name}
diff --git a/src/app/(main)/reports/[id]/FilterSelectForm.js b/src/app/(main)/reports/[id]/FilterSelectForm.tsx similarity index 67% rename from src/app/(main)/reports/[id]/FilterSelectForm.js rename to src/app/(main)/reports/[id]/FilterSelectForm.tsx index 8457b7ff6..4f9b92642 100644 --- a/src/app/(main)/reports/[id]/FilterSelectForm.js +++ b/src/app/(main)/reports/[id]/FilterSelectForm.tsx @@ -5,7 +5,7 @@ import FieldSelectForm from './FieldSelectForm'; import FieldFilterForm from './FieldFilterForm'; import { useApi } from 'components/hooks'; -function useValues(websiteId, type) { +function useValues(websiteId: string, type: string) { const now = Date.now(); const { get, useQuery } = useApi(); const { data, error, isLoading } = useQuery({ @@ -22,12 +22,24 @@ function useValues(websiteId, type) { return { data, error, isLoading }; } -export default function FilterSelectForm({ websiteId, items, onSelect, allowFilterSelect }) { - const [field, setField] = useState(); +export interface FilterSelectFormProps { + websiteId: string; + items: any[]; + onSelect?: (key: any) => void; + allowFilterSelect?: boolean; +} + +export default function FilterSelectForm({ + websiteId, + items, + onSelect, + allowFilterSelect, +}: FilterSelectFormProps) { + const [field, setField] = useState<{ name: string; label: string; type: string }>(); const { data, isLoading } = useValues(websiteId, field?.name); if (!field) { - return ; + return ; } if (isLoading) { diff --git a/src/app/(main)/reports/[id]/ParameterList.js b/src/app/(main)/reports/[id]/ParameterList.tsx similarity index 82% rename from src/app/(main)/reports/[id]/ParameterList.js rename to src/app/(main)/reports/[id]/ParameterList.tsx index bf77dd9d2..eb1a646a4 100644 --- a/src/app/(main)/reports/[id]/ParameterList.js +++ b/src/app/(main)/reports/[id]/ParameterList.tsx @@ -1,10 +1,17 @@ +import { ReactNode } from 'react'; import { Icon, TooltipPopup } from 'react-basics'; import Icons from 'components/icons'; import Empty from 'components/common/Empty'; import { useMessages } from 'components/hooks'; import styles from './ParameterList.module.css'; -export function ParameterList({ items = [], children, onRemove }) { +export interface ParameterListProps { + items: any[]; + children?: ReactNode | ((item: any) => ReactNode); + onRemove: (index: number, e: any) => void; +} + +export function ParameterList({ items = [], children, onRemove }: ParameterListProps) { const { formatMessage, labels } = useMessages(); return ( diff --git a/src/app/(main)/reports/[id]/PopupForm.js b/src/app/(main)/reports/[id]/PopupForm.tsx similarity index 59% rename from src/app/(main)/reports/[id]/PopupForm.js rename to src/app/(main)/reports/[id]/PopupForm.tsx index 6b99b00a9..f26661991 100644 --- a/src/app/(main)/reports/[id]/PopupForm.js +++ b/src/app/(main)/reports/[id]/PopupForm.tsx @@ -1,7 +1,16 @@ +import { CSSProperties, ReactNode } from 'react'; import classNames from 'classnames'; import styles from './PopupForm.module.css'; -export function PopupForm({ className, style, children }) { +export function PopupForm({ + className, + style, + children, +}: { + className?: string; + style?: CSSProperties; + children: ReactNode; +}) { return (
-
- {children} -
+
{children}
); } diff --git a/src/app/(main)/reports/[id]/ReportBody.js b/src/app/(main)/reports/[id]/ReportBody.tsx similarity index 100% rename from src/app/(main)/reports/[id]/ReportBody.js rename to src/app/(main)/reports/[id]/ReportBody.tsx diff --git a/src/app/(main)/reports/[id]/ReportDetails.js b/src/app/(main)/reports/[id]/ReportDetails.tsx similarity index 90% rename from src/app/(main)/reports/[id]/ReportDetails.js rename to src/app/(main)/reports/[id]/ReportDetails.tsx index df91719a3..e4d4688a0 100644 --- a/src/app/(main)/reports/[id]/ReportDetails.js +++ b/src/app/(main)/reports/[id]/ReportDetails.tsx @@ -12,7 +12,7 @@ const reports = { retention: RetentionReport, }; -export default function ReportDetails({ reportId }) { +export default function ReportDetails({ reportId }: { reportId: string }) { const { get, useQuery } = useApi(); const { data: report } = useQuery({ queryKey: ['reports', reportId], diff --git a/src/app/(main)/reports/[id]/ReportHeader.js b/src/app/(main)/reports/[id]/ReportHeader.tsx similarity index 92% rename from src/app/(main)/reports/[id]/ReportHeader.js rename to src/app/(main)/reports/[id]/ReportHeader.tsx index ed3b97364..6d226344f 100644 --- a/src/app/(main)/reports/[id]/ReportHeader.js +++ b/src/app/(main)/reports/[id]/ReportHeader.tsx @@ -12,8 +12,10 @@ export function ReportHeader({ icon }) { const { showToast } = useToasts(); const { post, useMutation } = useApi(); const router = useRouter(); - const { mutate: create, isLoading: isCreating } = useMutation(data => post(`/reports`, data)); - const { mutate: update, isLoading: isUpdating } = useMutation(data => + const { mutate: create, isLoading: isCreating } = useMutation((data: any) => + post(`/reports`, data), + ); + const { mutate: update, isLoading: isUpdating } = useMutation((data: any) => post(`/reports/${data.id}`, data), ); @@ -26,7 +28,7 @@ export function ReportHeader({ icon }) { create(report, { onSuccess: async ({ id }) => { showToast({ message: formatMessage(messages.saved), variant: 'success' }); - router.push(`/reports/${id}`, null, { shallow: true }); + router.push(`/reports/${id}`); }, }); } else { @@ -38,11 +40,11 @@ export function ReportHeader({ icon }) { } }; - const handleNameChange = name => { + const handleNameChange = (name: string) => { updateReport({ name: name || defaultName }); }; - const handleDescriptionChange = description => { + const handleDescriptionChange = (description: string) => { updateReport({ description }); }; diff --git a/src/app/(main)/reports/[id]/ReportMenu.js b/src/app/(main)/reports/[id]/ReportMenu.tsx similarity index 100% rename from src/app/(main)/reports/[id]/ReportMenu.js rename to src/app/(main)/reports/[id]/ReportMenu.tsx diff --git a/src/app/(main)/reports/create/ReportTemplates.js b/src/app/(main)/reports/create/ReportTemplates.tsx similarity index 100% rename from src/app/(main)/reports/create/ReportTemplates.js rename to src/app/(main)/reports/create/ReportTemplates.tsx diff --git a/src/app/(main)/reports/event-data/EventDataParameters.js b/src/app/(main)/reports/event-data/EventDataParameters.tsx similarity index 91% rename from src/app/(main)/reports/event-data/EventDataParameters.js rename to src/app/(main)/reports/event-data/EventDataParameters.tsx index ac90fc2a8..7a39131ba 100644 --- a/src/app/(main)/reports/event-data/EventDataParameters.js +++ b/src/app/(main)/reports/event-data/EventDataParameters.tsx @@ -1,4 +1,4 @@ -import { useContext, useRef } from 'react'; +import { useContext } from 'react'; import { Form, FormRow, FormButtons, SubmitButton, PopupTrigger, Icon, Popup } from 'react-basics'; import Empty from 'components/common/Empty'; import Icons from 'components/icons'; @@ -29,7 +29,6 @@ function useFields(websiteId, startDate, endDate) { export function EventDataParameters() { const { report, runReport, updateReport, isRunning } = useContext(ReportContext); const { formatMessage, labels, messages } = useMessages(); - const ref = useRef(null); const { parameters } = report || {}; const { websiteId, dateRange, fields, filters, groups } = parameters || {}; const { startDate, endDate } = dateRange || {}; @@ -53,28 +52,28 @@ export function EventDataParameters() { runReport(values); }; - const handleAdd = (group, value) => { + const handleAdd = (group: string, value: any) => { const data = parameterData[group]; - if (!data.find(({ name }) => name === value.name)) { + if (!data.find(({ name }) => name === value?.name)) { updateReport({ parameters: { [group]: data.concat(value) } }); } }; - const handleRemove = (group, index) => { + const handleRemove = (group: string, index: number) => { const data = [...parameterData[group]]; data.splice(index, 1); updateReport({ parameters: { [group]: data } }); }; - const AddButton = ({ group }) => { + const AddButton = ({ group, onAdd }) => { return ( - {close => { + {(close: () => void) => { return ( ({ @@ -82,7 +81,7 @@ export function EventDataParameters() { type: DATA_TYPES[eventDataType], }))} group={group} - onAdd={handleAdd} + onAdd={onAdd} onClose={close} /> ); @@ -93,7 +92,7 @@ export function EventDataParameters() { }; return ( - + {!hasData && } {parametersSelected && diff --git a/src/app/(main)/reports/event-data/EventDataReport.js b/src/app/(main)/reports/event-data/EventDataReport.tsx similarity index 89% rename from src/app/(main)/reports/event-data/EventDataReport.js rename to src/app/(main)/reports/event-data/EventDataReport.tsx index e91cb4a2b..8b4dc99cd 100644 --- a/src/app/(main)/reports/event-data/EventDataReport.js +++ b/src/app/(main)/reports/event-data/EventDataReport.tsx @@ -11,7 +11,7 @@ const defaultParameters = { parameters: { fields: [], filters: [] }, }; -export default function EventDataReport({ reportId }) { +export default function EventDataReport({ reportId }: { reportId: string }) { return ( } /> diff --git a/src/app/(main)/reports/event-data/EventDataTable.js b/src/app/(main)/reports/event-data/EventDataTable.tsx similarity index 100% rename from src/app/(main)/reports/event-data/EventDataTable.js rename to src/app/(main)/reports/event-data/EventDataTable.tsx diff --git a/src/app/(main)/reports/funnel/FunnelChart.js b/src/app/(main)/reports/funnel/FunnelChart.tsx similarity index 52% rename from src/app/(main)/reports/funnel/FunnelChart.js rename to src/app/(main)/reports/funnel/FunnelChart.tsx index 7461afbcd..923e8d56b 100644 --- a/src/app/(main)/reports/funnel/FunnelChart.js +++ b/src/app/(main)/reports/funnel/FunnelChart.tsx @@ -1,13 +1,18 @@ -import { useCallback, useContext, useMemo } from 'react'; +import { JSX, useCallback, useContext, useMemo } from 'react'; import { Loading, StatusLight } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; import useTheme from 'components/hooks/useTheme'; import BarChart from 'components/metrics/BarChart'; import { formatLongNumber } from 'lib/format'; -import styles from './FunnelChart.module.css'; import { ReportContext } from '../[id]/Report'; +import styles from './FunnelChart.module.css'; -export function FunnelChart({ className, loading }) { +export interface FunnelChartProps { + className?: string; + isLoading?: boolean; +} + +export function FunnelChart({ className, isLoading }: FunnelChartProps) { const { report } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); const { colors } = useTheme(); @@ -15,33 +20,39 @@ export function FunnelChart({ className, loading }) { const { parameters, data } = report || {}; const renderXLabel = useCallback( - (label, index) => { + (label: string, index: number) => { return parameters.urls[index]; }, [parameters], ); - const renderTooltipPopup = useCallback((setTooltipPopup, model) => { - const { opacity, labelColors, dataPoints } = model.tooltip; + const renderTooltipPopup = useCallback( + ( + setTooltipPopup: (arg0: JSX.Element) => void, + model: { tooltip: { opacity: any; labelColors: any; dataPoints: any } }, + ) => { + const { opacity, labelColors, dataPoints } = model.tooltip; - if (!dataPoints?.length || !opacity) { - setTooltipPopup(null); - return; - } + if (!dataPoints?.length || !opacity) { + setTooltipPopup(null); + return; + } - setTooltipPopup( - <> -
- {formatLongNumber(dataPoints[0].raw.y)} {formatMessage(labels.visitors)} -
-
- - {formatLongNumber(dataPoints[0].raw.z)}% {formatMessage(labels.dropoff)} - -
- , - ); - }, []); + setTooltipPopup( + <> +
+ {formatLongNumber(dataPoints[0].raw.y)} {formatMessage(labels.visitors)} +
+
+ + {formatLongNumber(dataPoints[0].raw.z)}% {formatMessage(labels.dropoff)} + +
+ , + ); + }, + [], + ); const datasets = useMemo(() => { return [ @@ -54,7 +65,7 @@ export function FunnelChart({ className, loading }) { ]; }, [data, colors, formatMessage, labels]); - if (loading) { + if (isLoading) { return ; } @@ -63,7 +74,7 @@ export function FunnelChart({ className, loading }) { className={className} datasets={datasets} unit="day" - loading={loading} + isLoading={isLoading} renderXLabel={renderXLabel} renderTooltipPopup={renderTooltipPopup} XAxisType="category" diff --git a/src/app/(main)/reports/funnel/FunnelParameters.js b/src/app/(main)/reports/funnel/FunnelParameters.tsx similarity index 84% rename from src/app/(main)/reports/funnel/FunnelParameters.js rename to src/app/(main)/reports/funnel/FunnelParameters.tsx index 135b5db84..0bb454156 100644 --- a/src/app/(main)/reports/funnel/FunnelParameters.js +++ b/src/app/(main)/reports/funnel/FunnelParameters.tsx @@ -1,4 +1,4 @@ -import { useContext, useRef } from 'react'; +import { useContext } from 'react'; import { useMessages } from 'components/hooks'; import { Icon, @@ -21,13 +21,12 @@ import PopupForm from '../[id]/PopupForm'; export function FunnelParameters() { const { report, runReport, updateReport, isRunning } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); - const ref = useRef(null); const { parameters } = report || {}; const { websiteId, dateRange, urls } = parameters || {}; const queryDisabled = !websiteId || !dateRange || urls?.length < 2; - const handleSubmit = (data, e) => { + const handleSubmit = (data: any, e: any) => { e.stopPropagation(); e.preventDefault(); if (!queryDisabled) { @@ -35,11 +34,11 @@ export function FunnelParameters() { } }; - const handleAddUrl = url => { + const handleAddUrl = (url: string) => { updateReport({ parameters: { urls: parameters.urls.concat(url) } }); }; - const handleRemoveUrl = (index, e) => { + const handleRemoveUrl = (index: number, e: any) => { e.stopPropagation(); const urls = [...parameters.urls]; urls.splice(index, 1); @@ -62,7 +61,7 @@ export function FunnelParameters() { }; return ( - + }> - + handleRemoveUrl(index, e)} + /> diff --git a/src/app/(main)/reports/funnel/FunnelReport.js b/src/app/(main)/reports/funnel/FunnelReport.tsx similarity index 100% rename from src/app/(main)/reports/funnel/FunnelReport.js rename to src/app/(main)/reports/funnel/FunnelReport.tsx diff --git a/src/app/(main)/reports/funnel/FunnelTable.js b/src/app/(main)/reports/funnel/FunnelTable.tsx similarity index 100% rename from src/app/(main)/reports/funnel/FunnelTable.js rename to src/app/(main)/reports/funnel/FunnelTable.tsx diff --git a/src/app/(main)/reports/funnel/UrlAddForm.js b/src/app/(main)/reports/funnel/UrlAddForm.tsx similarity index 86% rename from src/app/(main)/reports/funnel/UrlAddForm.js rename to src/app/(main)/reports/funnel/UrlAddForm.tsx index 3e77ce156..88c27ae91 100644 --- a/src/app/(main)/reports/funnel/UrlAddForm.js +++ b/src/app/(main)/reports/funnel/UrlAddForm.tsx @@ -3,7 +3,12 @@ import { useMessages } from 'components/hooks'; import { Button, Form, FormRow, TextField, Flexbox } from 'react-basics'; import styles from './UrlAddForm.module.css'; -export function UrlAddForm({ defaultValue = '', onAdd }) { +export interface UrlAddFormProps { + defaultValue?: string; + onAdd?: (url: string) => void; +} + +export function UrlAddForm({ defaultValue = '', onAdd }: UrlAddFormProps) { const [url, setUrl] = useState(defaultValue); const { formatMessage, labels } = useMessages(); diff --git a/src/app/(main)/reports/insights/InsightsParameters.js b/src/app/(main)/reports/insights/InsightsParameters.tsx similarity index 93% rename from src/app/(main)/reports/insights/InsightsParameters.js rename to src/app/(main)/reports/insights/InsightsParameters.tsx index 91dd09f84..cd643eedf 100644 --- a/src/app/(main)/reports/insights/InsightsParameters.js +++ b/src/app/(main)/reports/insights/InsightsParameters.tsx @@ -1,4 +1,4 @@ -import { useContext, useRef } from 'react'; +import { useContext } from 'react'; import { useFormat, useMessages, useFilters } from 'components/hooks'; import { Form, @@ -24,7 +24,6 @@ export function InsightsParameters() { const { formatMessage, labels } = useMessages(); const { formatValue } = useFormat(); const { filterLabels } = useFilters(); - const ref = useRef(null); const { parameters } = report || {}; const { websiteId, dateRange, fields, filters } = parameters || {}; const { startDate, endDate } = dateRange || {}; @@ -72,7 +71,7 @@ export function InsightsParameters() { updateReport({ parameters: { [id]: data } }); }; - const AddButton = ({ id }) => { + const AddButton = ({ id, onAdd }) => { return ( @@ -84,8 +83,8 @@ export function InsightsParameters() { {id === 'fields' && ( )} @@ -93,7 +92,7 @@ export function InsightsParameters() { )} @@ -103,7 +102,7 @@ export function InsightsParameters() { }; return ( - + {parametersSelected && parameterGroups.map(({ id, label }) => { diff --git a/src/app/(main)/reports/insights/InsightsReport.js b/src/app/(main)/reports/insights/InsightsReport.tsx similarity index 90% rename from src/app/(main)/reports/insights/InsightsReport.js rename to src/app/(main)/reports/insights/InsightsReport.tsx index f99e187bb..b90ff396d 100644 --- a/src/app/(main)/reports/insights/InsightsReport.js +++ b/src/app/(main)/reports/insights/InsightsReport.tsx @@ -13,7 +13,7 @@ const defaultParameters = { parameters: { fields: [], filters: [] }, }; -export default function InsightsReport({ reportId }) { +export default function InsightsReport({ reportId }: { reportId: string }) { return ( } /> diff --git a/src/app/(main)/reports/insights/InsightsTable.js b/src/app/(main)/reports/insights/InsightsTable.tsx similarity index 96% rename from src/app/(main)/reports/insights/InsightsTable.js rename to src/app/(main)/reports/insights/InsightsTable.tsx index 4194fee80..a45176983 100644 --- a/src/app/(main)/reports/insights/InsightsTable.js +++ b/src/app/(main)/reports/insights/InsightsTable.tsx @@ -5,7 +5,7 @@ import { ReportContext } from '../[id]/Report'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; export function InsightsTable() { - const [fields, setFields] = useState(); + const [fields, setFields] = useState([]); const { report } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); const { formatValue } = useFormat(); diff --git a/src/app/(main)/reports/retention/RetentionParameters.js b/src/app/(main)/reports/retention/RetentionParameters.tsx similarity index 87% rename from src/app/(main)/reports/retention/RetentionParameters.js rename to src/app/(main)/reports/retention/RetentionParameters.tsx index 762a313df..fc168695f 100644 --- a/src/app/(main)/reports/retention/RetentionParameters.js +++ b/src/app/(main)/reports/retention/RetentionParameters.tsx @@ -1,4 +1,4 @@ -import { useContext, useRef } from 'react'; +import { useContext } from 'react'; import { useMessages } from 'components/hooks'; import { Form, FormButtons, FormRow, SubmitButton } from 'react-basics'; import { ReportContext } from '../[id]/Report'; @@ -9,14 +9,13 @@ import { parseDateRange } from 'lib/date'; export function RetentionParameters() { const { report, runReport, isRunning, updateReport } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); - const ref = useRef(null); const { parameters } = report || {}; const { websiteId, dateRange } = parameters || {}; const { startDate } = dateRange || {}; const queryDisabled = !websiteId || !dateRange; - const handleSubmit = (data, e) => { + const handleSubmit = (data: any, e: any) => { e.stopPropagation(); e.preventDefault(); @@ -30,7 +29,7 @@ export function RetentionParameters() { }; return ( - + diff --git a/src/app/(main)/reports/retention/RetentionReport.js b/src/app/(main)/reports/retention/RetentionReport.tsx similarity index 92% rename from src/app/(main)/reports/retention/RetentionReport.js rename to src/app/(main)/reports/retention/RetentionReport.tsx index ae42e76bf..35f0fcb1e 100644 --- a/src/app/(main)/reports/retention/RetentionReport.js +++ b/src/app/(main)/reports/retention/RetentionReport.tsx @@ -19,7 +19,7 @@ const defaultParameters = { }, }; -export default function RetentionReport({ reportId }) { +export default function RetentionReport({ reportId }: { reportId: string }) { return ( } /> diff --git a/src/app/(main)/reports/retention/RetentionTable.js b/src/app/(main)/reports/retention/RetentionTable.tsx similarity index 96% rename from src/app/(main)/reports/retention/RetentionTable.js rename to src/app/(main)/reports/retention/RetentionTable.tsx index a71fae6f4..d2e7f129e 100644 --- a/src/app/(main)/reports/retention/RetentionTable.js +++ b/src/app/(main)/reports/retention/RetentionTable.tsx @@ -18,7 +18,7 @@ export function RetentionTable({ days = DAYS }) { return ; } - const rows = data.reduce((arr, row) => { + const rows = data.reduce((arr: any[], row: { date: any; visitors: any; day: any }) => { const { date, visitors, day } = row; if (day === 0) { return arr.concat({ diff --git a/src/app/(main)/settings/profile/DateRangeSetting.js b/src/app/(main)/settings/profile/DateRangeSetting.tsx similarity index 100% rename from src/app/(main)/settings/profile/DateRangeSetting.js rename to src/app/(main)/settings/profile/DateRangeSetting.tsx diff --git a/src/app/(main)/settings/profile/LanguageSetting.js b/src/app/(main)/settings/profile/LanguageSetting.tsx similarity index 100% rename from src/app/(main)/settings/profile/LanguageSetting.js rename to src/app/(main)/settings/profile/LanguageSetting.tsx diff --git a/src/app/(main)/settings/profile/PasswordChangeButton.js b/src/app/(main)/settings/profile/PasswordChangeButton.tsx similarity index 100% rename from src/app/(main)/settings/profile/PasswordChangeButton.js rename to src/app/(main)/settings/profile/PasswordChangeButton.tsx diff --git a/src/app/(main)/settings/profile/PasswordEditForm.js b/src/app/(main)/settings/profile/PasswordEditForm.tsx similarity index 91% rename from src/app/(main)/settings/profile/PasswordEditForm.js rename to src/app/(main)/settings/profile/PasswordEditForm.tsx index 39ecfb770..7062ea597 100644 --- a/src/app/(main)/settings/profile/PasswordEditForm.js +++ b/src/app/(main)/settings/profile/PasswordEditForm.tsx @@ -6,10 +6,10 @@ import useMessages from 'components/hooks/useMessages'; export function PasswordEditForm({ onSave, onClose }) { const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error, isLoading } = useMutation(data => post('/me/password', data)); + const { mutate, error, isLoading } = useMutation((data: any) => post('/me/password', data)); const ref = useRef(null); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { onSave(); @@ -18,7 +18,7 @@ export function PasswordEditForm({ onSave, onClose }) { }); }; - const samePassword = value => { + const samePassword = (value: string) => { if (value !== ref?.current?.getValues('newPassword')) { return formatMessage(messages.noMatchPassword); } diff --git a/src/app/(main)/settings/profile/ProfileHeader.js b/src/app/(main)/settings/profile/ProfileHeader.tsx similarity index 100% rename from src/app/(main)/settings/profile/ProfileHeader.js rename to src/app/(main)/settings/profile/ProfileHeader.tsx diff --git a/src/app/(main)/settings/profile/ProfileSettings.js b/src/app/(main)/settings/profile/ProfileSettings.tsx similarity index 100% rename from src/app/(main)/settings/profile/ProfileSettings.js rename to src/app/(main)/settings/profile/ProfileSettings.tsx diff --git a/src/app/(main)/settings/profile/ThemeSetting.js b/src/app/(main)/settings/profile/ThemeSetting.tsx similarity index 100% rename from src/app/(main)/settings/profile/ThemeSetting.js rename to src/app/(main)/settings/profile/ThemeSetting.tsx diff --git a/src/app/(main)/settings/profile/TimezoneSetting.js b/src/app/(main)/settings/profile/TimezoneSetting.tsx similarity index 100% rename from src/app/(main)/settings/profile/TimezoneSetting.js rename to src/app/(main)/settings/profile/TimezoneSetting.tsx diff --git a/src/app/(main)/settings/teams/TeamAddForm.js b/src/app/(main)/settings/teams/TeamAddForm.tsx similarity index 72% rename from src/app/(main)/settings/teams/TeamAddForm.js rename to src/app/(main)/settings/teams/TeamAddForm.tsx index b8bb8c3a7..5a242cb90 100644 --- a/src/app/(main)/settings/teams/TeamAddForm.js +++ b/src/app/(main)/settings/teams/TeamAddForm.tsx @@ -1,4 +1,3 @@ -import { useRef } from 'react'; import { Form, FormRow, @@ -12,11 +11,12 @@ import { setValue } from 'store/cache'; import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; -export function TeamAddForm({ onSave, onClose }) { +export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) { const { formatMessage, labels } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error, isLoading } = useMutation(data => post('/teams', data)); - const ref = useRef(null); + const { mutate, error, isPending } = useMutation({ + mutationFn: (data: any) => post('/teams', data), + }); const handleSubmit = async data => { mutate(data, { @@ -29,17 +29,17 @@ export function TeamAddForm({ onSave, onClose }) { }; return ( - + - + {formatMessage(labels.save)} - diff --git a/src/app/(main)/settings/teams/TeamDeleteButton.js b/src/app/(main)/settings/teams/TeamDeleteButton.tsx similarity index 79% rename from src/app/(main)/settings/teams/TeamDeleteButton.js rename to src/app/(main)/settings/teams/TeamDeleteButton.tsx index 5e4a41ea0..36a1a1f83 100644 --- a/src/app/(main)/settings/teams/TeamDeleteButton.js +++ b/src/app/(main)/settings/teams/TeamDeleteButton.tsx @@ -2,7 +2,15 @@ import { Button, Icon, Icons, Modal, ModalTrigger, Text } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; import TeamDeleteForm from './TeamDeleteForm'; -export function TeamDeleteButton({ teamId, teamName, onDelete }) { +export function TeamDeleteButton({ + teamId, + teamName, + onDelete, +}: { + teamId: string; + teamName: string; + onDelete?: () => void; +}) { const { formatMessage, labels } = useMessages(); return ( @@ -14,7 +22,7 @@ export function TeamDeleteButton({ teamId, teamName, onDelete }) { {formatMessage(labels.delete)} - {close => ( + {(close: any) => ( )} diff --git a/src/app/(main)/settings/teams/TeamDeleteForm.js b/src/app/(main)/settings/teams/TeamDeleteForm.tsx similarity index 72% rename from src/app/(main)/settings/teams/TeamDeleteForm.js rename to src/app/(main)/settings/teams/TeamDeleteForm.tsx index 9b80668a8..00eedb178 100644 --- a/src/app/(main)/settings/teams/TeamDeleteForm.js +++ b/src/app/(main)/settings/teams/TeamDeleteForm.tsx @@ -3,10 +3,22 @@ import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; import { setValue } from 'store/cache'; -export function TeamDeleteForm({ teamId, teamName, onSave, onClose }) { +export function TeamDeleteForm({ + teamId, + teamName, + onSave, + onClose, +}: { + teamId: string; + teamName: string; + onSave: () => void; + onClose: () => void; +}) { const { formatMessage, labels, messages, FormattedMessage } = useMessages(); const { del, useMutation } = useApi(); - const { mutate, error, isLoading } = useMutation(data => del(`/teams/${teamId}`, data)); + const { mutate, error, isPending } = useMutation({ + mutationFn: (data: any) => del(`/teams/${teamId}`, data), + }); const handleSubmit = async data => { mutate(data, { @@ -24,7 +36,7 @@ export function TeamDeleteForm({ teamId, teamName, onSave, onClose }) { {teamName} }} />

- + {formatMessage(labels.delete)} diff --git a/src/app/(main)/settings/teams/TeamJoinForm.js b/src/app/(main)/settings/teams/TeamJoinForm.tsx similarity index 85% rename from src/app/(main)/settings/teams/TeamJoinForm.js rename to src/app/(main)/settings/teams/TeamJoinForm.tsx index 528e1d75b..5cd382256 100644 --- a/src/app/(main)/settings/teams/TeamJoinForm.js +++ b/src/app/(main)/settings/teams/TeamJoinForm.tsx @@ -12,10 +12,10 @@ import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; import { setValue } from 'store/cache'; -export function TeamJoinForm({ onSave, onClose }) { +export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) { const { formatMessage, labels, getMessage } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error } = useMutation(data => post('/teams/join', data)); + const { mutate, error } = useMutation({ mutationFn: (data: any) => post('/teams/join', data) }); const ref = useRef(null); const handleSubmit = async data => { diff --git a/src/app/(main)/settings/teams/TeamLeaveButton.js b/src/app/(main)/settings/teams/TeamLeaveButton.tsx similarity index 87% rename from src/app/(main)/settings/teams/TeamLeaveButton.js rename to src/app/(main)/settings/teams/TeamLeaveButton.tsx index 7b98f082f..97676e176 100644 --- a/src/app/(main)/settings/teams/TeamLeaveButton.js +++ b/src/app/(main)/settings/teams/TeamLeaveButton.tsx @@ -4,7 +4,15 @@ import useLocale from 'components/hooks/useLocale'; import useUser from 'components/hooks/useUser'; import TeamDeleteForm from './TeamLeaveForm'; -export function TeamLeaveButton({ teamId, teamName, onLeave }) { +export function TeamLeaveButton({ + teamId, + teamName, + onLeave, +}: { + teamId: string; + teamName: string; + onLeave?: () => void; +}) { const { formatMessage, labels } = useMessages(); const { dir } = useLocale(); const { user } = useUser(); diff --git a/src/app/(main)/settings/teams/TeamLeaveForm.js b/src/app/(main)/settings/teams/TeamLeaveForm.tsx similarity index 60% rename from src/app/(main)/settings/teams/TeamLeaveForm.js rename to src/app/(main)/settings/teams/TeamLeaveForm.tsx index a9b6922aa..3b4d4703c 100644 --- a/src/app/(main)/settings/teams/TeamLeaveForm.js +++ b/src/app/(main)/settings/teams/TeamLeaveForm.tsx @@ -3,22 +3,33 @@ import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; import { setValue } from 'store/cache'; -export function TeamLeaveForm({ teamId, userId, teamName, onSave, onClose }) { +export function TeamLeaveForm({ + teamId, + userId, + teamName, + onSave, + onClose, +}: { + teamId: string; + userId: string; + teamName: string; + onSave: () => void; + onClose: () => void; +}) { const { formatMessage, labels, messages, FormattedMessage } = useMessages(); const { del, useMutation } = useApi(); - const { mutate, error, isLoading } = useMutation(() => del(`/teams/${teamId}/users/${userId}`)); + const { mutate, error, isPending } = useMutation({ + mutationFn: () => del(`/teams/${teamId}/users/${userId}`), + }); const handleSubmit = async () => { - mutate( - {}, - { - onSuccess: async () => { - setValue('team:members', Date.now()); - onSave(); - onClose(); - }, + mutate(null, { + onSuccess: async () => { + setValue('team:members', Date.now()); + onSave(); + onClose(); }, - ); + }); }; return ( @@ -27,7 +38,7 @@ export function TeamLeaveForm({ teamId, userId, teamName, onSave, onClose }) { {teamName} }} />

- + {formatMessage(labels.leave)} diff --git a/src/app/(main)/settings/teams/TeamsAddButton.js b/src/app/(main)/settings/teams/TeamsAddButton.tsx similarity index 79% rename from src/app/(main)/settings/teams/TeamsAddButton.js rename to src/app/(main)/settings/teams/TeamsAddButton.tsx index b7850812b..09f9ecbbf 100644 --- a/src/app/(main)/settings/teams/TeamsAddButton.js +++ b/src/app/(main)/settings/teams/TeamsAddButton.tsx @@ -3,7 +3,7 @@ import Icons from 'components/icons'; import useMessages from 'components/hooks/useMessages'; import TeamAddForm from './TeamAddForm'; -export function TeamsAddButton({ onAdd }) { +export function TeamsAddButton({ onAdd }: { onAdd?: () => void }) { const { formatMessage, labels } = useMessages(); return ( @@ -15,7 +15,7 @@ export function TeamsAddButton({ onAdd }) { {formatMessage(labels.createTeam)} - {close => } + {(close: () => void) => } ); diff --git a/src/app/(main)/settings/teams/TeamsDataTable.js b/src/app/(main)/settings/teams/TeamsDataTable.tsx similarity index 88% rename from src/app/(main)/settings/teams/TeamsDataTable.js rename to src/app/(main)/settings/teams/TeamsDataTable.tsx index a8c9e21d1..2424e464e 100644 --- a/src/app/(main)/settings/teams/TeamsDataTable.js +++ b/src/app/(main)/settings/teams/TeamsDataTable.tsx @@ -7,10 +7,10 @@ import useCache from 'store/cache'; export function TeamsDataTable() { const { get } = useApi(); - const modified = useCache(state => state?.teams); + const modified = useCache((state: any) => state?.teams); const queryResult = useFilterQuery({ queryKey: ['teams', { modified }], - queryFn: params => { + queryFn: (params: any) => { return get(`/teams`, { ...params, }); diff --git a/src/app/(main)/settings/teams/TeamsHeader.js b/src/app/(main)/settings/teams/TeamsHeader.tsx similarity index 100% rename from src/app/(main)/settings/teams/TeamsHeader.js rename to src/app/(main)/settings/teams/TeamsHeader.tsx diff --git a/src/app/(main)/settings/teams/TeamsJoinButton.js b/src/app/(main)/settings/teams/TeamsJoinButton.tsx similarity index 100% rename from src/app/(main)/settings/teams/TeamsJoinButton.js rename to src/app/(main)/settings/teams/TeamsJoinButton.tsx diff --git a/src/app/(main)/settings/teams/TeamsTable.js b/src/app/(main)/settings/teams/TeamsTable.tsx similarity index 96% rename from src/app/(main)/settings/teams/TeamsTable.js rename to src/app/(main)/settings/teams/TeamsTable.tsx index 1f7f1da4a..70a7bebbd 100644 --- a/src/app/(main)/settings/teams/TeamsTable.js +++ b/src/app/(main)/settings/teams/TeamsTable.tsx @@ -7,7 +7,7 @@ import { Button, GridColumn, GridTable, Icon, Icons, Text, useBreakpoint } from import TeamDeleteButton from './TeamDeleteButton'; import TeamLeaveButton from './TeamLeaveButton'; -export function TeamsTable({ data = [] }) { +export function TeamsTable({ data = [] }: { data: any[] }) { const { formatMessage, labels } = useMessages(); const { user } = useUser(); const breakpoint = useBreakpoint(); diff --git a/src/app/(main)/settings/teams/WebsiteTags.js b/src/app/(main)/settings/teams/WebsiteTags.tsx similarity index 83% rename from src/app/(main)/settings/teams/WebsiteTags.js rename to src/app/(main)/settings/teams/WebsiteTags.tsx index c17d5763d..4a0f109d8 100644 --- a/src/app/(main)/settings/teams/WebsiteTags.js +++ b/src/app/(main)/settings/teams/WebsiteTags.tsx @@ -1,7 +1,15 @@ import { Button, Icon, Icons, Text } from 'react-basics'; import styles from './WebsiteTags.module.css'; -export function WebsiteTags({ items = [], websites = [], onClick }) { +export function WebsiteTags({ + items = [], + websites = [], + onClick, +}: { + items: any[]; + websites: any[]; + onClick: (e: Event) => void; +}) { if (websites.length === 0) { return null; } diff --git a/src/app/(main)/settings/teams/[id]/TeamEditForm.js b/src/app/(main)/settings/teams/[id]/TeamEditForm.tsx similarity index 100% rename from src/app/(main)/settings/teams/[id]/TeamEditForm.js rename to src/app/(main)/settings/teams/[id]/TeamEditForm.tsx diff --git a/src/app/(main)/settings/teams/[id]/TeamMemberRemoveButton.js b/src/app/(main)/settings/teams/[id]/TeamMemberRemoveButton.tsx similarity index 59% rename from src/app/(main)/settings/teams/[id]/TeamMemberRemoveButton.js rename to src/app/(main)/settings/teams/[id]/TeamMemberRemoveButton.tsx index 603adae3a..cef2977ef 100644 --- a/src/app/(main)/settings/teams/[id]/TeamMemberRemoveButton.js +++ b/src/app/(main)/settings/teams/[id]/TeamMemberRemoveButton.tsx @@ -3,28 +3,37 @@ import useMessages from 'components/hooks/useMessages'; import { Icon, Icons, LoadingButton, Text } from 'react-basics'; import { setValue } from 'store/cache'; -export function TeamMemberRemoveButton({ teamId, userId, disabled, onSave }) { +export function TeamMemberRemoveButton({ + teamId, + userId, + disabled, + onSave, +}: { + teamId: string; + userId: string; + disabled?: boolean; + onSave?: () => void; +}) { const { formatMessage, labels } = useMessages(); const { del, useMutation } = useApi(); - const { mutate, isLoading } = useMutation(() => del(`/teams/${teamId}/users/${userId}`)); + const { mutate, isPending } = useMutation({ + mutationFn: () => del(`/teams/${teamId}/users/${userId}`), + }); const handleRemoveTeamMember = () => { - mutate( - {}, - { - onSuccess: () => { - setValue('team:members', Date.now()); - onSave?.(); - }, + mutate(null, { + onSuccess: () => { + setValue('team:members', Date.now()); + onSave?.(); }, - ); + }); }; return ( handleRemoveTeamMember()} disabled={disabled} - isLoading={isLoading} + isLoading={isPending} > diff --git a/src/app/(main)/settings/teams/[id]/TeamMembers.js b/src/app/(main)/settings/teams/[id]/TeamMembers.tsx similarity index 89% rename from src/app/(main)/settings/teams/[id]/TeamMembers.js rename to src/app/(main)/settings/teams/[id]/TeamMembers.tsx index 1b0c0d180..588a5a52b 100644 --- a/src/app/(main)/settings/teams/[id]/TeamMembers.js +++ b/src/app/(main)/settings/teams/[id]/TeamMembers.tsx @@ -4,7 +4,7 @@ import useFilterQuery from 'components/hooks/useFilterQuery'; import DataTable from 'components/common/DataTable'; import useCache from 'store/cache'; -export function TeamMembers({ teamId, readOnly }) { +export function TeamMembers({ teamId, readOnly }: { teamId: string; readOnly: boolean }) { const { get } = useApi(); const modified = useCache(state => state?.['team:members']); const queryResult = useFilterQuery({ diff --git a/src/app/(main)/settings/teams/[id]/TeamMembersTable.js b/src/app/(main)/settings/teams/[id]/TeamMembersTable.tsx similarity index 90% rename from src/app/(main)/settings/teams/[id]/TeamMembersTable.js rename to src/app/(main)/settings/teams/[id]/TeamMembersTable.tsx index 9a402d444..a08c746bb 100644 --- a/src/app/(main)/settings/teams/[id]/TeamMembersTable.js +++ b/src/app/(main)/settings/teams/[id]/TeamMembersTable.tsx @@ -4,7 +4,15 @@ import useUser from 'components/hooks/useUser'; import { ROLES } from 'lib/constants'; import TeamMemberRemoveButton from './TeamMemberRemoveButton'; -export function TeamMembersTable({ data = [], teamId, readOnly }) { +export function TeamMembersTable({ + data = [], + teamId, + readOnly, +}: { + data: any[]; + teamId: string; + readOnly: boolean; +}) { const { formatMessage, labels } = useMessages(); const { user } = useUser(); const breakpoint = useBreakpoint(); diff --git a/src/app/(main)/settings/teams/[id]/TeamSettings.js b/src/app/(main)/settings/teams/[id]/TeamSettings.tsx similarity index 91% rename from src/app/(main)/settings/teams/[id]/TeamSettings.js rename to src/app/(main)/settings/teams/[id]/TeamSettings.tsx index d7065f97f..cf5ae35cb 100644 --- a/src/app/(main)/settings/teams/[id]/TeamSettings.js +++ b/src/app/(main)/settings/teams/[id]/TeamSettings.tsx @@ -10,7 +10,7 @@ import TeamEditForm from './TeamEditForm'; import TeamMembers from './TeamMembers'; import TeamWebsites from './TeamWebsites'; -export function TeamSettings({ teamId }) { +export function TeamSettings({ teamId }: { teamId: string }) { const { formatMessage, labels, messages } = useMessages(); const { user } = useUser(); const [values, setValues] = useState(null); @@ -24,7 +24,7 @@ export function TeamSettings({ teamId }) { return get(`/teams/${teamId}`); } }, - cacheTime: 0, + gcTime: 0, }); const canEdit = data?.teamUser?.find( ({ userId, role }) => role === ROLES.teamOwner && userId === user.id, @@ -48,7 +48,7 @@ export function TeamSettings({ teamId }) { return ( - + setTab(value)} style={{ marginBottom: 30 }}> {formatMessage(labels.details)} {formatMessage(labels.members)} {formatMessage(labels.websites)} diff --git a/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js b/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.tsx similarity index 86% rename from src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js rename to src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.tsx index 004ae54f1..64a0c58e4 100644 --- a/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js +++ b/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.tsx @@ -7,11 +7,21 @@ import Empty from 'components/common/Empty'; import { setValue } from 'store/cache'; import { useUser } from 'components/hooks'; -export function TeamWebsiteAddForm({ teamId, onSave, onClose }) { +export function TeamWebsiteAddForm({ + teamId, + onSave, + onClose, +}: { + teamId: string; + onSave: () => void; + onClose: () => void; +}) { const { user } = useUser(); const { formatMessage, labels } = useMessages(); const { get, post, useQuery, useMutation } = useApi(); - const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data)); + const { mutate, error } = useMutation({ + mutationFn: (data: any) => post(`/teams/${teamId}/websites`, data), + }); const { data: websites, isLoading } = useQuery({ queryKey: ['websites'], queryFn: () => get('/websites'), @@ -42,7 +52,7 @@ export function TeamWebsiteAddForm({ teamId, onSave, onClose }) { {!isLoading && !hasData && } {hasData && ( - + {row => ( { - queryResult.refetch(); + queryResult.query.refetch(); }; return ( @@ -43,7 +43,9 @@ export function TeamWebsites({ teamId }) { - {({ data }) => } + {({ data }) => ( + + )} ); diff --git a/src/app/(main)/settings/teams/[id]/TeamWebsitesTable.js b/src/app/(main)/settings/teams/[id]/TeamWebsitesTable.tsx similarity index 85% rename from src/app/(main)/settings/teams/[id]/TeamWebsitesTable.js rename to src/app/(main)/settings/teams/[id]/TeamWebsitesTable.tsx index 0f8022121..29b958160 100644 --- a/src/app/(main)/settings/teams/[id]/TeamWebsitesTable.js +++ b/src/app/(main)/settings/teams/[id]/TeamWebsitesTable.tsx @@ -4,7 +4,15 @@ import useMessages from 'components/hooks/useMessages'; import useUser from 'components/hooks/useUser'; import TeamWebsiteRemoveButton from './TeamWebsiteRemoveButton'; -export function TeamWebsitesTable({ data = [], onRemove }) { +export function TeamWebsitesTable({ + data = [], + readOnly, + onRemove, +}: { + data: any[]; + readOnly: boolean; + onRemove: () => void; +}) { const { formatMessage, labels } = useMessages(); const { user } = useUser(); @@ -17,7 +25,7 @@ export function TeamWebsitesTable({ data = [], onRemove }) { const { id: teamId, teamUser } = row.teamWebsite[0].team; const { id: websiteId, userId } = row; const owner = teamUser[0]; - const canRemove = user.id === userId || user.id === owner.userId; + const canRemove = !readOnly && (user.id === userId || user.id === owner.userId); return ( <> {canRemove && ( diff --git a/src/app/(main)/settings/teams/[id]/page.js b/src/app/(main)/settings/teams/[id]/page.tsx similarity index 100% rename from src/app/(main)/settings/teams/[id]/page.js rename to src/app/(main)/settings/teams/[id]/page.tsx diff --git a/src/app/(main)/settings/users/UserAddButton.js b/src/app/(main)/settings/users/UserAddButton.tsx similarity index 92% rename from src/app/(main)/settings/users/UserAddButton.js rename to src/app/(main)/settings/users/UserAddButton.tsx index 0f4bf7349..7f08107cb 100644 --- a/src/app/(main)/settings/users/UserAddButton.js +++ b/src/app/(main)/settings/users/UserAddButton.tsx @@ -3,7 +3,7 @@ import UserAddForm from './UserAddForm'; import useMessages from 'components/hooks/useMessages'; import { setValue } from 'store/cache'; -export function UserAddButton({ onSave }) { +export function UserAddButton({ onSave }: { onSave?: () => void }) { const { formatMessage, labels, messages } = useMessages(); const { showToast } = useToasts(); diff --git a/src/app/(main)/settings/users/UserAddForm.js b/src/app/(main)/settings/users/UserAddForm.tsx similarity index 92% rename from src/app/(main)/settings/users/UserAddForm.js rename to src/app/(main)/settings/users/UserAddForm.tsx index 38c1bedd7..11066a24e 100644 --- a/src/app/(main)/settings/users/UserAddForm.js +++ b/src/app/(main)/settings/users/UserAddForm.tsx @@ -16,7 +16,9 @@ import useMessages from 'components/hooks/useMessages'; export function UserAddForm({ onSave, onClose }) { const { post, useMutation } = useApi(); - const { mutate, error, isLoading } = useMutation(data => post(`/users`, data)); + const { mutate, error, isPending } = useMutation({ + mutationFn: (data: any) => post(`/users`, data), + }); const { formatMessage, labels } = useMessages(); const handleSubmit = async data => { @@ -65,7 +67,7 @@ export function UserAddForm({ onSave, onClose }) { {formatMessage(labels.save)} - diff --git a/src/app/(main)/settings/users/UserDeleteButton.js b/src/app/(main)/settings/users/UserDeleteButton.tsx similarity index 85% rename from src/app/(main)/settings/users/UserDeleteButton.js rename to src/app/(main)/settings/users/UserDeleteButton.tsx index 22d931713..2b93c138e 100644 --- a/src/app/(main)/settings/users/UserDeleteButton.js +++ b/src/app/(main)/settings/users/UserDeleteButton.tsx @@ -3,7 +3,15 @@ import useMessages from 'components/hooks/useMessages'; import useUser from 'components/hooks/useUser'; import UserDeleteForm from './UserDeleteForm'; -export function UserDeleteButton({ userId, username, onDelete }) { +export function UserDeleteButton({ + userId, + username, + onDelete, +}: { + userId: string; + username: string; + onDelete?: () => void; +}) { const { formatMessage, labels } = useMessages(); const { user } = useUser(); diff --git a/src/app/(main)/settings/users/UserDeleteForm.js b/src/app/(main)/settings/users/UserDeleteForm.tsx similarity index 72% rename from src/app/(main)/settings/users/UserDeleteForm.js rename to src/app/(main)/settings/users/UserDeleteForm.tsx index 5a47fdc12..eaa8e4818 100644 --- a/src/app/(main)/settings/users/UserDeleteForm.js +++ b/src/app/(main)/settings/users/UserDeleteForm.tsx @@ -1,14 +1,13 @@ -import { useMutation } from '@tanstack/react-query'; import { Button, Form, FormButtons, SubmitButton } from 'react-basics'; import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; export function UserDeleteForm({ userId, username, onSave, onClose }) { const { formatMessage, FormattedMessage, labels, messages } = useMessages(); - const { del } = useApi(); - const { mutate, error, isLoading } = useMutation(() => del(`/users/${userId}`)); + const { del, useMutation } = useApi(); + const { mutate, error, isPending } = useMutation({ mutationFn: () => del(`/users/${userId}`) }); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { onSave(); @@ -23,10 +22,10 @@ export function UserDeleteForm({ userId, username, onSave, onClose }) { {username} }} />

- + {formatMessage(labels.delete)} - diff --git a/src/app/(main)/settings/users/UserEditForm.js b/src/app/(main)/settings/users/UserEditForm.tsx similarity index 82% rename from src/app/(main)/settings/users/UserEditForm.js rename to src/app/(main)/settings/users/UserEditForm.tsx index 157c9ad69..0b823a94e 100644 --- a/src/app/(main)/settings/users/UserEditForm.js +++ b/src/app/(main)/settings/users/UserEditForm.tsx @@ -13,14 +13,30 @@ import useApi from 'components/hooks/useApi'; import { ROLES } from 'lib/constants'; import useMessages from 'components/hooks/useMessages'; -export function UserEditForm({ userId, data, onSave }) { +export function UserEditForm({ + userId, + data, + onSave, +}: { + userId: string; + data: any[]; + onSave: (data: any) => void; +}) { const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error } = useMutation(({ username, password, role }) => - post(`/users/${userId}`, { username, password, role }), - ); + const { mutate, error } = useMutation({ + mutationFn: ({ + username, + password, + role, + }: { + username: string; + password: string; + role: string; + }) => post(`/users/${userId}`, { username, password, role }), + }); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { onSave(data); diff --git a/src/app/(main)/settings/users/UserWebsites.js b/src/app/(main)/settings/users/UserWebsites.js deleted file mode 100644 index cd53c512e..000000000 --- a/src/app/(main)/settings/users/UserWebsites.js +++ /dev/null @@ -1,36 +0,0 @@ -import Page from 'components/layout/Page'; -import useApi from 'components/hooks/useApi'; -import WebsitesTable from 'app/(main)/settings/websites/WebsitesTable'; -import useApiFilter from 'components/hooks/useApiFilter'; - -export function UserWebsites({ userId }) { - const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } = - useApiFilter(); - const { get, useQuery } = useApi(); - const { data, isLoading, error } = useQuery({ - queryKey: ['user:websites', userId, filter, page, pageSize], - queryFn: () => - get(`/users/${userId}/websites`, { - filter, - page, - pageSize, - }), - }); - const hasData = data && data.length !== 0; - - return ( - - {hasData && ( - - )} - - ); -} - -export default UserWebsites; diff --git a/src/app/(main)/settings/users/UserWebsites.tsx b/src/app/(main)/settings/users/UserWebsites.tsx new file mode 100644 index 000000000..2d06e82a9 --- /dev/null +++ b/src/app/(main)/settings/users/UserWebsites.tsx @@ -0,0 +1,26 @@ +import Page from 'components/layout/Page'; +import useApi from 'components/hooks/useApi'; +import WebsitesTable from 'app/(main)/settings/websites/WebsitesTable'; +import useFilterQuery from 'components/hooks/useFilterQuery'; +import DataTable from 'components/common/DataTable'; + +export function UserWebsites({ userId }) { + const { get } = useApi(); + const queryResult = useFilterQuery({ + queryKey: ['user:websites', userId], + queryFn: (params: any) => get(`/users/${userId}/websites`, params), + }); + const hasData = queryResult.result && queryResult.result.data.length !== 0; + + return ( + + {hasData && ( + + {({ data }) => } + + )} + + ); +} + +export default UserWebsites; diff --git a/src/app/(main)/settings/users/UsersDataTable.js b/src/app/(main)/settings/users/UsersDataTable.tsx similarity index 82% rename from src/app/(main)/settings/users/UsersDataTable.js rename to src/app/(main)/settings/users/UsersDataTable.tsx index 91125309b..b77164515 100644 --- a/src/app/(main)/settings/users/UsersDataTable.js +++ b/src/app/(main)/settings/users/UsersDataTable.tsx @@ -8,10 +8,10 @@ import useCache from 'store/cache'; export function UsersDataTable() { const { get } = useApi(); - const modified = useCache(state => state?.users); + const modified = useCache((state: any) => state?.users); const queryResult = useFilterQuery({ queryKey: ['users', { modified }], - queryFn: params => get(`/users`, params), + queryFn: (params: { [key: string]: any }) => get(`/users`, params), }); return ( diff --git a/src/app/(main)/settings/users/UsersHeader.js b/src/app/(main)/settings/users/UsersHeader.tsx similarity index 85% rename from src/app/(main)/settings/users/UsersHeader.js rename to src/app/(main)/settings/users/UsersHeader.tsx index caf1f913f..0901a6fb2 100644 --- a/src/app/(main)/settings/users/UsersHeader.js +++ b/src/app/(main)/settings/users/UsersHeader.tsx @@ -3,7 +3,7 @@ import PageHeader from 'components/layout/PageHeader'; import useMessages from 'components/hooks/useMessages'; import UserAddButton from './UserAddButton'; -export function UsersHeader({ onAdd }) { +export function UsersHeader({ onAdd }: { onAdd?: () => void }) { const { formatMessage, labels } = useMessages(); return ( diff --git a/src/app/(main)/settings/users/UsersTable.js b/src/app/(main)/settings/users/UsersTable.tsx similarity index 96% rename from src/app/(main)/settings/users/UsersTable.js rename to src/app/(main)/settings/users/UsersTable.tsx index a0b5aba1b..2b840b64c 100644 --- a/src/app/(main)/settings/users/UsersTable.js +++ b/src/app/(main)/settings/users/UsersTable.tsx @@ -6,7 +6,7 @@ import useMessages from 'components/hooks/useMessages'; import useLocale from 'components/hooks/useLocale'; import UserDeleteButton from './UserDeleteButton'; -export function UsersTable({ data = [] }) { +export function UsersTable({ data = [] }: { data: any[] }) { const { formatMessage, labels } = useMessages(); const { dateLocale } = useLocale(); const breakpoint = useBreakpoint(); diff --git a/src/app/(main)/settings/users/[id]/UserSettings.js b/src/app/(main)/settings/users/[id]/UserSettings.js index f635bb054..3d8a92eae 100644 --- a/src/app/(main)/settings/users/[id]/UserSettings.js +++ b/src/app/(main)/settings/users/[id]/UserSettings.js @@ -21,7 +21,7 @@ export function UserSettings({ userId }) { return get(`/users/${userId}`); } }, - cacheTime: 0, + gcTime: 0, }); const handleSave = data => { diff --git a/src/app/(main)/settings/users/[id]/page.js b/src/app/(main)/settings/users/[id]/page.tsx similarity index 100% rename from src/app/(main)/settings/users/[id]/page.js rename to src/app/(main)/settings/users/[id]/page.tsx diff --git a/src/app/(main)/settings/websites/WebsiteAddButton.js b/src/app/(main)/settings/websites/WebsiteAddButton.tsx similarity index 92% rename from src/app/(main)/settings/websites/WebsiteAddButton.js rename to src/app/(main)/settings/websites/WebsiteAddButton.tsx index b1a694296..16681b0ed 100644 --- a/src/app/(main)/settings/websites/WebsiteAddButton.js +++ b/src/app/(main)/settings/websites/WebsiteAddButton.tsx @@ -3,7 +3,7 @@ import WebsiteAddForm from './WebsiteAddForm'; import useMessages from 'components/hooks/useMessages'; import { setValue } from 'store/cache'; -export function WebsiteAddButton({ onSave }) { +export function WebsiteAddButton({ onSave }: { onSave?: () => void }) { const { formatMessage, labels, messages } = useMessages(); const { showToast } = useToasts(); diff --git a/src/app/(main)/settings/websites/WebsiteAddForm.tsx b/src/app/(main)/settings/websites/WebsiteAddForm.tsx index 996241037..9f3ba1783 100644 --- a/src/app/(main)/settings/websites/WebsiteAddForm.tsx +++ b/src/app/(main)/settings/websites/WebsiteAddForm.tsx @@ -14,7 +14,9 @@ import useMessages from 'components/hooks/useMessages'; export function WebsiteAddForm({ onSave, onClose }: { onSave?: () => void; onClose?: () => void }) { const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error, isLoading } = useMutation(data => post('/websites', data)); + const { mutate, error, isPending } = useMutation({ + mutationFn: (data: any) => post('/websites', data), + }); const handleSubmit = async (data: any) => { mutate(data, { @@ -26,7 +28,7 @@ export function WebsiteAddForm({ onSave, onClose }: { onSave?: () => void; onClo }; return ( - + @@ -48,7 +50,7 @@ export function WebsiteAddForm({ onSave, onClose }: { onSave?: () => void; onClo {formatMessage(labels.save)}
{onClose && ( - )} diff --git a/src/app/(main)/settings/websites/WebsiteSettings.js b/src/app/(main)/settings/websites/WebsiteSettings.js index 79ba08fc0..ffda838cb 100644 --- a/src/app/(main)/settings/websites/WebsiteSettings.js +++ b/src/app/(main)/settings/websites/WebsiteSettings.js @@ -20,7 +20,7 @@ export function WebsiteSettings({ websiteId, openExternal = false, analyticsUrl queryKey: ['website', websiteId], queryFn: () => get(`/websites/${websiteId}`), enabled: !!websiteId, - cacheTime: 0, + gcTime: 0, }); const [values, setValues] = useState(null); const [tab, setTab] = useState('details'); diff --git a/src/app/(main)/settings/websites/WebsitesHeader.js b/src/app/(main)/settings/websites/WebsitesHeader.tsx similarity index 100% rename from src/app/(main)/settings/websites/WebsitesHeader.js rename to src/app/(main)/settings/websites/WebsitesHeader.tsx diff --git a/src/app/(main)/settings/websites/WebsitesTable.js b/src/app/(main)/settings/websites/WebsitesTable.tsx similarity index 90% rename from src/app/(main)/settings/websites/WebsitesTable.js rename to src/app/(main)/settings/websites/WebsitesTable.tsx index eef3f7d40..024344481 100644 --- a/src/app/(main)/settings/websites/WebsitesTable.js +++ b/src/app/(main)/settings/websites/WebsitesTable.tsx @@ -1,8 +1,18 @@ +import { ReactNode } from 'react'; import Link from 'next/link'; import { Button, Text, Icon, Icons, GridTable, GridColumn, useBreakpoint } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; import useUser from 'components/hooks/useUser'; +export interface WebsitesTableProps { + data: any[]; + showTeam?: boolean; + showActions?: boolean; + allowEdit?: boolean; + allowView?: boolean; + children?: ReactNode; +} + export function WebsitesTable({ data = [], showTeam, @@ -10,7 +20,7 @@ export function WebsitesTable({ allowEdit, allowView, children, -}) { +}: WebsitesTableProps) { const { formatMessage, labels } = useMessages(); const { user } = useUser(); const breakpoint = useBreakpoint(); diff --git a/src/app/(main)/settings/websites/[id]/ShareUrl.js b/src/app/(main)/settings/websites/[id]/ShareUrl.tsx similarity index 91% rename from src/app/(main)/settings/websites/[id]/ShareUrl.js rename to src/app/(main)/settings/websites/[id]/ShareUrl.tsx index 72ba217cc..191490357 100644 --- a/src/app/(main)/settings/websites/[id]/ShareUrl.js +++ b/src/app/(main)/settings/websites/[id]/ShareUrl.tsx @@ -20,9 +20,9 @@ export function ShareUrl({ websiteId, data, analyticsUrl, onSave }) { const { name, shareId } = data; const [id, setId] = useState(shareId); const { post, useMutation } = useApi(); - const { mutate, error } = useMutation(({ shareId }) => - post(`/websites/${websiteId}`, { shareId }), - ); + const { mutate, error } = useMutation({ + mutationFn: (data: any) => post(`/websites/${websiteId}`, data), + }); const ref = useRef(null); const url = useMemo( () => @@ -32,7 +32,7 @@ export function ShareUrl({ websiteId, data, analyticsUrl, onSave }) { [id, name], ); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { onSave(data); @@ -50,7 +50,7 @@ export function ShareUrl({ websiteId, data, analyticsUrl, onSave }) { setId(id); }; - const handleCheck = checked => { + const handleCheck = (checked: boolean) => { const data = { shareId: checked ? generateId() : null }; mutate(data, { onSuccess: async () => { diff --git a/src/app/(main)/settings/websites/[id]/TrackingCode.js b/src/app/(main)/settings/websites/[id]/TrackingCode.tsx similarity index 87% rename from src/app/(main)/settings/websites/[id]/TrackingCode.js rename to src/app/(main)/settings/websites/[id]/TrackingCode.tsx index 368368d76..b6bbdf89c 100644 --- a/src/app/(main)/settings/websites/[id]/TrackingCode.js +++ b/src/app/(main)/settings/websites/[id]/TrackingCode.tsx @@ -2,7 +2,13 @@ import { TextArea } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; import useConfig from 'components/hooks/useConfig'; -export function TrackingCode({ websiteId, analyticsUrl }) { +export function TrackingCode({ + websiteId, + analyticsUrl, +}: { + websiteId: string; + analyticsUrl: string; +}) { const { formatMessage, messages } = useMessages(); const config = useConfig(); diff --git a/src/app/(main)/settings/websites/[id]/WebsiteData.js b/src/app/(main)/settings/websites/[id]/WebsiteData.tsx similarity index 92% rename from src/app/(main)/settings/websites/[id]/WebsiteData.js rename to src/app/(main)/settings/websites/[id]/WebsiteData.tsx index 07dc92575..b4bfe6093 100644 --- a/src/app/(main)/settings/websites/[id]/WebsiteData.js +++ b/src/app/(main)/settings/websites/[id]/WebsiteData.tsx @@ -3,7 +3,13 @@ import WebsiteDeleteForm from './WebsiteDeleteForm'; import WebsiteResetForm from './WebsiteResetForm'; import useMessages from 'components/hooks/useMessages'; -export function WebsiteData({ websiteId, onSave }) { +export function WebsiteData({ + websiteId, + onSave, +}: { + websiteId: string; + onSave?: (value: string) => void; +}) { const { formatMessage, labels, messages } = useMessages(); const handleReset = async () => { diff --git a/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.js b/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx similarity index 82% rename from src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.js rename to src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx index 1548bddbd..c3b5d74ad 100644 --- a/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.js +++ b/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx @@ -12,10 +12,20 @@ import useMessages from 'components/hooks/useMessages'; const CONFIRM_VALUE = 'DELETE'; -export function WebsiteDeleteForm({ websiteId, onSave, onClose }) { +export function WebsiteDeleteForm({ + websiteId, + onSave, + onClose, +}: { + websiteId: string; + onSave?: () => void; + onClose?: () => void; +}) { const { formatMessage, labels, messages, FormattedMessage } = useMessages(); const { del, useMutation } = useApi(); - const { mutate, error } = useMutation(data => del(`/websites/${websiteId}`, data)); + const { mutate, error } = useMutation({ + mutationFn: (data: any) => del(`/websites/${websiteId}`, data), + }); const handleSubmit = async data => { mutate(data, { diff --git a/src/app/(main)/settings/websites/[id]/WebsiteEditForm.js b/src/app/(main)/settings/websites/[id]/WebsiteEditForm.tsx similarity index 85% rename from src/app/(main)/settings/websites/[id]/WebsiteEditForm.js rename to src/app/(main)/settings/websites/[id]/WebsiteEditForm.tsx index 18ad0ac98..9c05905cb 100644 --- a/src/app/(main)/settings/websites/[id]/WebsiteEditForm.js +++ b/src/app/(main)/settings/websites/[id]/WebsiteEditForm.tsx @@ -4,10 +4,20 @@ import useApi from 'components/hooks/useApi'; import { DOMAIN_REGEX } from 'lib/constants'; import useMessages from 'components/hooks/useMessages'; -export function WebsiteEditForm({ websiteId, data, onSave }) { +export function WebsiteEditForm({ + websiteId, + data, + onSave, +}: { + websiteId: string; + data: any[]; + onSave?: (data: any) => void; +}) { const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error } = useMutation(data => post(`/websites/${websiteId}`, data)); + const { mutate, error } = useMutation({ + mutationFn: (data: any) => post(`/websites/${websiteId}`, data), + }); const ref = useRef(null); const handleSubmit = async data => { diff --git a/src/app/(main)/settings/websites/[id]/WebsiteResetForm.js b/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx similarity index 78% rename from src/app/(main)/settings/websites/[id]/WebsiteResetForm.js rename to src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx index 9886429ba..76c2bc472 100644 --- a/src/app/(main)/settings/websites/[id]/WebsiteResetForm.js +++ b/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx @@ -12,12 +12,22 @@ import useMessages from 'components/hooks/useMessages'; const CONFIRM_VALUE = 'RESET'; -export function WebsiteResetForm({ websiteId, onSave, onClose }) { +export function WebsiteResetForm({ + websiteId, + onSave, + onClose, +}: { + websiteId: string; + onSave?: () => void; + onClose?: () => void; +}) { const { formatMessage, labels, messages, FormattedMessage } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error } = useMutation(data => post(`/websites/${websiteId}/reset`, data)); + const { mutate, error } = useMutation({ + mutationFn: (data: any) => post(`/websites/${websiteId}/reset`, data), + }); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { onSave(); diff --git a/src/app/(main)/settings/websites/[id]/page.js b/src/app/(main)/settings/websites/[id]/page.tsx similarity index 100% rename from src/app/(main)/settings/websites/[id]/page.js rename to src/app/(main)/settings/websites/[id]/page.tsx diff --git a/src/app/(main)/websites/WebsitesBrowse.js b/src/app/(main)/websites/WebsitesBrowse.tsx similarity index 91% rename from src/app/(main)/websites/WebsitesBrowse.js rename to src/app/(main)/websites/WebsitesBrowse.tsx index 3e8df2b26..c426cc069 100644 --- a/src/app/(main)/websites/WebsitesBrowse.js +++ b/src/app/(main)/websites/WebsitesBrowse.tsx @@ -17,7 +17,7 @@ export function WebsitesBrowse() { return ( <> - + setTab(tab)} style={{ marginBottom: 30 }}> {formatMessage(labels.myWebsites)} {formatMessage(labels.teamWebsites)} diff --git a/src/app/(main)/websites/[id]/WebsiteChart.js b/src/app/(main)/websites/[id]/WebsiteChart.tsx similarity index 90% rename from src/app/(main)/websites/[id]/WebsiteChart.js rename to src/app/(main)/websites/[id]/WebsiteChart.tsx index 15a7525f1..eba155c11 100644 --- a/src/app/(main)/websites/[id]/WebsiteChart.js +++ b/src/app/(main)/websites/[id]/WebsiteChart.tsx @@ -3,7 +3,7 @@ import PageviewsChart from 'components/metrics/PageviewsChart'; import { useApi, useDateRange, useTimezone, useNavigation } from 'components/hooks'; import { getDateArray } from 'lib/date'; -export function WebsiteChart({ websiteId }) { +export function WebsiteChart({ websiteId }: { websiteId: string }) { const [dateRange] = useDateRange(websiteId); const { startDate, endDate, unit, modified } = dateRange; const [timezone] = useTimezone(); @@ -45,7 +45,7 @@ export function WebsiteChart({ websiteId }) { return { pageviews: [], sessions: [] }; }, [data, startDate, endDate, unit]); - return ; + return ; } export default WebsiteChart; diff --git a/src/app/(main)/websites/[id]/WebsiteChartList.js b/src/app/(main)/websites/[id]/WebsiteChartList.tsx similarity index 91% rename from src/app/(main)/websites/[id]/WebsiteChartList.js rename to src/app/(main)/websites/[id]/WebsiteChartList.tsx index bc2439ded..b35b6f1f0 100644 --- a/src/app/(main)/websites/[id]/WebsiteChartList.js +++ b/src/app/(main)/websites/[id]/WebsiteChartList.tsx @@ -8,7 +8,15 @@ import WebsiteHeader from './WebsiteHeader'; import { WebsiteMetricsBar } from './WebsiteMetricsBar'; import { useMessages, useLocale } from 'components/hooks'; -export default function WebsiteChartList({ websites, showCharts, limit }) { +export default function WebsiteChartList({ + websites, + showCharts, + limit, +}: { + websites: any[]; + showCharts?: boolean; + limit?: number; +}) { const { formatMessage, labels } = useMessages(); const { websiteOrder } = useDashboard(); const { dir } = useLocale(); diff --git a/src/app/(main)/websites/[id]/WebsiteDetails.js b/src/app/(main)/websites/[id]/WebsiteDetails.tsx similarity index 87% rename from src/app/(main)/websites/[id]/WebsiteDetails.js rename to src/app/(main)/websites/[id]/WebsiteDetails.tsx index c6ad1acc9..4d3a18e79 100644 --- a/src/app/(main)/websites/[id]/WebsiteDetails.js +++ b/src/app/(main)/websites/[id]/WebsiteDetails.tsx @@ -11,7 +11,7 @@ import WebsiteHeader from './WebsiteHeader'; import WebsiteMetricsBar from './WebsiteMetricsBar'; import WebsiteTableView from './WebsiteTableView'; -export default function WebsiteDetails({ websiteId }) { +export default function WebsiteDetails({ websiteId }: { websiteId: string }) { const { data: website, isLoading, error } = useWebsite(websiteId); const pathname = usePathname(); const showLinks = !pathname.includes('/share/'); @@ -27,10 +27,7 @@ export default function WebsiteDetails({ websiteId }) { return ( <> - + {!website && } diff --git a/src/app/(main)/websites/[id]/WebsiteFilterButton.js b/src/app/(main)/websites/[id]/WebsiteFilterButton.tsx similarity index 91% rename from src/app/(main)/websites/[id]/WebsiteFilterButton.js rename to src/app/(main)/websites/[id]/WebsiteFilterButton.tsx index e96856f62..6a02cd474 100644 --- a/src/app/(main)/websites/[id]/WebsiteFilterButton.js +++ b/src/app/(main)/websites/[id]/WebsiteFilterButton.tsx @@ -3,7 +3,13 @@ import PopupForm from 'app/(main)/reports/[id]/PopupForm'; import FilterSelectForm from 'app/(main)/reports/[id]/FilterSelectForm'; import { useMessages, useNavigation } from 'components/hooks'; -export function WebsiteFilterButton({ websiteId, className }) { +export function WebsiteFilterButton({ + websiteId, + className, +}: { + websiteId: string; + className?: string; +}) { const { formatMessage, labels } = useMessages(); const { makeUrl, router } = useNavigation(); @@ -31,9 +37,9 @@ export function WebsiteFilterButton({ websiteId, className }) { {formatMessage(labels.filter)} - {close => { + {(close: () => void) => { return ( - + `${n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`} + format={n => `${+n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`} /> )} diff --git a/src/app/(main)/websites/[id]/WebsiteTableView.js b/src/app/(main)/websites/[id]/WebsiteTableView.tsx similarity index 94% rename from src/app/(main)/websites/[id]/WebsiteTableView.js rename to src/app/(main)/websites/[id]/WebsiteTableView.tsx index 28a8fad60..e530f2bae 100644 --- a/src/app/(main)/websites/[id]/WebsiteTableView.js +++ b/src/app/(main)/websites/[id]/WebsiteTableView.tsx @@ -10,7 +10,7 @@ import CountriesTable from 'components/metrics/CountriesTable'; import EventsTable from 'components/metrics/EventsTable'; import EventsChart from 'components/metrics/EventsChart'; -export default function WebsiteTableView({ websiteId }) { +export default function WebsiteTableView({ websiteId }: { websiteId: string }) { const [countryData, setCountryData] = useState(); const tableProps = { websiteId, diff --git a/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js b/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.tsx similarity index 94% rename from src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js rename to src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.tsx index b02e166c1..419472d59 100644 --- a/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.js +++ b/src/app/(main)/websites/[id]/event-data/EventDataMetricsBar.tsx @@ -5,7 +5,7 @@ import WebsiteDateFilter from 'components/input/WebsiteDateFilter'; import MetricsBar from 'components/metrics/MetricsBar'; import styles from './EventDataMetricsBar.module.css'; -export function EventDataMetricsBar({ websiteId }) { +export function EventDataMetricsBar({ websiteId }: { websiteId: string }) { const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); const [dateRange] = useDateRange(websiteId); diff --git a/src/app/(main)/websites/[id]/event-data/EventDataTable.js b/src/app/(main)/websites/[id]/event-data/EventDataTable.tsx similarity index 100% rename from src/app/(main)/websites/[id]/event-data/EventDataTable.js rename to src/app/(main)/websites/[id]/event-data/EventDataTable.tsx diff --git a/src/app/(main)/websites/[id]/event-data/EventDataValueTable.js b/src/app/(main)/websites/[id]/event-data/EventDataValueTable.tsx similarity index 94% rename from src/app/(main)/websites/[id]/event-data/EventDataValueTable.js rename to src/app/(main)/websites/[id]/event-data/EventDataValueTable.tsx index 4e50f5b9b..7976ce36f 100644 --- a/src/app/(main)/websites/[id]/event-data/EventDataValueTable.js +++ b/src/app/(main)/websites/[id]/event-data/EventDataValueTable.tsx @@ -6,7 +6,7 @@ import PageHeader from 'components/layout/PageHeader'; import Empty from 'components/common/Empty'; import { DATA_TYPES } from 'lib/constants'; -export function EventDataValueTable({ data = [], event }) { +export function EventDataValueTable({ data = [], event }: { data: any[]; event: string }) { const { formatMessage, labels } = useMessages(); const { makeUrl } = useNavigation(); diff --git a/src/app/(main)/websites/[id]/event-data/page.js b/src/app/(main)/websites/[id]/event-data/page.tsx similarity index 100% rename from src/app/(main)/websites/[id]/event-data/page.js rename to src/app/(main)/websites/[id]/event-data/page.tsx diff --git a/src/app/login/LoginForm.js b/src/app/login/LoginForm.tsx similarity index 90% rename from src/app/login/LoginForm.js rename to src/app/login/LoginForm.tsx index 59d145bf3..78cf3dd37 100644 --- a/src/app/login/LoginForm.js +++ b/src/app/login/LoginForm.tsx @@ -1,5 +1,4 @@ 'use client'; -import { useMutation } from '@tanstack/react-query'; import { Form, FormRow, @@ -21,8 +20,10 @@ import styles from './LoginForm.module.css'; export function LoginForm() { const { formatMessage, labels, getMessage } = useMessages(); const router = useRouter(); - const { post } = useApi(); - const { mutate, error, isLoading } = useMutation(data => post('/auth/login', data)); + const { post, useMutation } = useApi(); + const { mutate, error, isPending } = useMutation({ + mutationFn: (data: any) => post('/auth/login', data), + }); const handleSubmit = async data => { mutate(data, { @@ -53,7 +54,7 @@ export function LoginForm() {
- + {formatMessage(labels.login)} diff --git a/src/app/logout/Logout.js b/src/app/logout/Logout.tsx similarity index 100% rename from src/app/logout/Logout.js rename to src/app/logout/Logout.tsx diff --git a/src/app/share/[...id]/Footer.js b/src/app/share/[...id]/Footer.tsx similarity index 100% rename from src/app/share/[...id]/Footer.js rename to src/app/share/[...id]/Footer.tsx diff --git a/src/app/share/[...id]/Header.js b/src/app/share/[...id]/Header.tsx similarity index 100% rename from src/app/share/[...id]/Header.js rename to src/app/share/[...id]/Header.tsx diff --git a/src/app/share/[...id]/Share.js b/src/app/share/[...id]/Share.tsx similarity index 100% rename from src/app/share/[...id]/Share.js rename to src/app/share/[...id]/Share.tsx diff --git a/src/components/common/DataTable.tsx b/src/components/common/DataTable.tsx index 621a44fe4..00aba09c8 100644 --- a/src/components/common/DataTable.tsx +++ b/src/components/common/DataTable.tsx @@ -36,11 +36,11 @@ export function DataTable({ const hasData = Boolean(!isLoading && data?.length); const noResults = Boolean(!isLoading && query && !hasData); - const handleSearch = query => { + const handleSearch = (query: string) => { setParams({ ...params, query, page: params.page ? page : 1 }); }; - const handlePageChange = page => { + const handlePageChange = (page: number) => { setParams({ ...params, query, page }); }; @@ -54,7 +54,7 @@ export function DataTable({ diff --git a/src/components/common/FilterButtons.tsx b/src/components/common/FilterButtons.tsx index e1860c783..a64a6482a 100644 --- a/src/components/common/FilterButtons.tsx +++ b/src/components/common/FilterButtons.tsx @@ -4,7 +4,7 @@ import { ButtonGroup, Button, Flexbox } from 'react-basics'; export interface FilterButtonsProps { items: any[]; selectedKey?: Key; - onSelect: () => void; + onSelect: (key: any) => void; } export function FilterButtons({ items, selectedKey, onSelect }: FilterButtonsProps) { diff --git a/src/components/declarations.d.ts b/src/components/declarations.d.ts index 81533301a..ca55157ba 100644 --- a/src/components/declarations.d.ts +++ b/src/components/declarations.d.ts @@ -1,3 +1,4 @@ declare module '*.css'; declare module '*.svg'; declare module '*.json'; +declare module 'uuid'; diff --git a/src/components/hooks/useApiFilter.ts b/src/components/hooks/useApiFilter.ts deleted file mode 100644 index d411fd434..000000000 --- a/src/components/hooks/useApiFilter.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useState } from 'react'; - -export function useApiFilter() { - const [filter, setFilter] = useState(); - const [filterType, setFilterType] = useState('All'); - const [page, setPage] = useState(1); - const [pageSize, setPageSize] = useState(10); - - const handleFilterChange = value => setFilter(value); - const handlePageChange = value => setPage(value); - const handlePageSizeChange = value => setPageSize(value); - - return { - filter, - setFilter, - filterType, - setFilterType, - page, - setPage, - pageSize, - setPageSize, - handleFilterChange, - handlePageChange, - handlePageSizeChange, - }; -} - -export default useApiFilter; diff --git a/src/components/hooks/useDateRange.ts b/src/components/hooks/useDateRange.ts index 6e70a3689..71361b69c 100644 --- a/src/components/hooks/useDateRange.ts +++ b/src/components/hooks/useDateRange.ts @@ -6,7 +6,7 @@ import websiteStore, { setWebsiteDateRange } from 'store/websites'; import appStore, { setDateRange } from 'store/app'; import useApi from './useApi'; -export function useDateRange(websiteId: string) { +export function useDateRange(websiteId?: string) { const { get } = useApi(); const { locale } = useLocale(); const websiteConfig = websiteStore(state => state[websiteId]?.dateRange); diff --git a/src/components/hooks/useFilterQuery.ts b/src/components/hooks/useFilterQuery.ts index 2c5207416..6dc3abe70 100644 --- a/src/components/hooks/useFilterQuery.ts +++ b/src/components/hooks/useFilterQuery.ts @@ -1,24 +1,33 @@ +import { UseQueryOptions } from '@tanstack/react-query'; import { useState, Dispatch, SetStateAction } from 'react'; import { useApi } from 'components/hooks/useApi'; -import { SearchFilter, FilterResult } from 'lib/types'; +import { FilterResult, SearchFilter } from 'lib/types'; export interface FilterQueryResult { - result: FilterResult; + result: FilterResult; query: any; params: SearchFilter; setParams: Dispatch>; } -export function useFilterQuery(props = {}): FilterQueryResult { +export function useFilterQuery({ + queryKey, + queryFn, + ...options +}: UseQueryOptions): FilterQueryResult { const [params, setParams] = useState({ query: '', page: 1, }); const { useQuery } = useApi(); - const { data, ...query } = useQuery>({ ...props }); + const { data, ...query } = useQuery({ + queryKey: [...queryKey, params], + queryFn: (data: any) => queryFn({ ...data, ...params }), + ...options, + }); return { - result: data, + result: data as FilterResult, query, params, setParams, diff --git a/src/components/hooks/useNavigation.ts b/src/components/hooks/useNavigation.ts index 9f01cd802..fb9bffc58 100644 --- a/src/components/hooks/useNavigation.ts +++ b/src/components/hooks/useNavigation.ts @@ -2,7 +2,12 @@ import { useMemo } from 'react'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { buildUrl } from 'next-basics'; -export function useNavigation() { +export function useNavigation(): { + pathname: string; + query: { [key: string]: string }; + router: any; + makeUrl: (params: any, reset?: boolean) => string; +} { const router = useRouter(); const pathname = usePathname(); const params = useSearchParams(); diff --git a/src/components/hooks/useReports.ts b/src/components/hooks/useReports.ts index 0fad2db15..d2473002d 100644 --- a/src/components/hooks/useReports.ts +++ b/src/components/hooks/useReports.ts @@ -1,19 +1,19 @@ import { useState } from 'react'; import useApi from './useApi'; -import useApiFilter from 'components/hooks/useApiFilter'; +import useFilterQuery from 'components/hooks/useFilterQuery'; -export function useReports() { +export function useReports(websiteId?: string) { const [modified, setModified] = useState(Date.now()); - const { get, useQuery, del, useMutation } = useApi(); - const { mutate } = useMutation(reportId => del(`/reports/${reportId}`)); - const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } = - useApiFilter(); - const { data, error, isLoading } = useQuery({ - queryKey: ['reports', { modified, filter, page, pageSize }], - queryFn: () => get(`/reports`, { filter, page, pageSize }), + const { get, del, useMutation } = useApi(); + const { mutate } = useMutation({ mutationFn: (reportId: string) => del(`/reports/${reportId}`) }); + const queryResult = useFilterQuery({ + queryKey: ['reports', { websiteId, modified }], + queryFn: (params: any) => { + return get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params); + }, }); - const deleteReport = id => { + const deleteReport = (id: any) => { mutate(id, { onSuccess: () => { setModified(Date.now()); @@ -22,16 +22,8 @@ export function useReports() { }; return { - reports: data, - error, - isLoading, + ...queryResult, deleteReport, - filter, - page, - pageSize, - handleFilterChange, - handlePageChange, - handlePageSizeChange, }; } diff --git a/src/components/hooks/useShareToken.ts b/src/components/hooks/useShareToken.ts index 41ae7faf5..189657be4 100644 --- a/src/components/hooks/useShareToken.ts +++ b/src/components/hooks/useShareToken.ts @@ -3,7 +3,11 @@ import useApi from './useApi'; const selector = (state: { shareToken: string }) => state.shareToken; -export function useShareToken(shareId: string) { +export function useShareToken(shareId: string): { + shareToken: any; + isLoading?: boolean; + error?: Error; +} { const shareToken = useStore(selector); const { get, useQuery } = useApi(); const { isLoading, error } = useQuery({ diff --git a/src/components/input/DateFilter.js b/src/components/input/DateFilter.tsx similarity index 89% rename from src/components/input/DateFilter.js rename to src/components/input/DateFilter.tsx index 9fde27ca1..f7739f170 100644 --- a/src/components/input/DateFilter.js +++ b/src/components/input/DateFilter.tsx @@ -3,9 +3,20 @@ import { Icon, Modal, Dropdown, Item, Text, Flexbox } from 'react-basics'; import { endOfYear, isSameDay } from 'date-fns'; import DatePickerForm from 'components/metrics/DatePickerForm'; import useLocale from 'components/hooks/useLocale'; -import { formatDate } from 'lib/date'; -import Icons from 'components/icons'; import useMessages from 'components/hooks/useMessages'; +import Icons from 'components/icons'; +import { formatDate } from 'lib/date'; + +export interface DateFilterProps { + value: string; + startDate: Date; + endDate: Date; + className?: string; + onChange?: (value: string) => void; + selectedUnit?: string; + showAllTime?: boolean; + alignment?: 'start' | 'center' | 'end'; +} export function DateFilter({ value, @@ -16,7 +27,7 @@ export function DateFilter({ selectedUnit, showAllTime = false, alignment = 'end', -}) { +}: DateFilterProps) { const { formatMessage, labels } = useMessages(); const [showPicker, setShowPicker] = useState(false); @@ -65,7 +76,7 @@ export function DateFilter({ }, ].filter(n => n); - const renderValue = value => { + const renderValue = (value: string) => { return value.startsWith('range') ? ( { + const handleChange = (value: string) => { if (value === 'custom') { setShowPicker(true); return; @@ -86,7 +97,7 @@ export function DateFilter({ onChange(value); }; - const handlePickerChange = value => { + const handlePickerChange = (value: string) => { setShowPicker(false); onChange(value); }; @@ -102,7 +113,7 @@ export function DateFilter({ value={value} alignment={alignment} placeholder={formatMessage(labels.selectDate)} - onChange={handleChange} + onChange={key => handleChange(key as any)} > {({ label, value, divider }) => ( diff --git a/src/components/input/LanguageButton.js b/src/components/input/LanguageButton.tsx similarity index 88% rename from src/components/input/LanguageButton.js rename to src/components/input/LanguageButton.tsx index 3c0d0cd6f..1151da0be 100644 --- a/src/components/input/LanguageButton.js +++ b/src/components/input/LanguageButton.tsx @@ -9,7 +9,7 @@ export function LanguageButton() { const { locale, saveLocale, dir } = useLocale(); const items = Object.keys(languages).map(key => ({ ...languages[key], value: key })); - function handleSelect(value, close, e) { + function handleSelect(value: string, close: () => void, e: MouseEvent) { e.stopPropagation(); saveLocale(value); close(); @@ -23,7 +23,7 @@ export function LanguageButton() { - {close => { + {(close: () => void) => { return (
{items.map(({ value, label }) => { @@ -31,7 +31,7 @@ export function LanguageButton() {
handleSelect(value, close, e)} > {label} {value === locale && ( diff --git a/src/components/input/LogoutButton.js b/src/components/input/LogoutButton.tsx similarity index 80% rename from src/components/input/LogoutButton.js rename to src/components/input/LogoutButton.tsx index 6ca358a12..c787f2296 100644 --- a/src/components/input/LogoutButton.js +++ b/src/components/input/LogoutButton.tsx @@ -2,7 +2,11 @@ import { Button, Icon, Icons, TooltipPopup } from 'react-basics'; import Link from 'next/link'; import useMessages from 'components/hooks/useMessages'; -export function LogoutButton({ tooltipPosition = 'top' }) { +export function LogoutButton({ + tooltipPosition = 'top', +}: { + tooltipPosition?: 'top' | 'bottom' | 'left' | 'right'; +}) { const { formatMessage, labels } = useMessages(); return ( diff --git a/src/components/input/MonthSelect.js b/src/components/input/MonthSelect.tsx similarity index 87% rename from src/components/input/MonthSelect.js rename to src/components/input/MonthSelect.tsx index 312c6854a..acb17dfe6 100644 --- a/src/components/input/MonthSelect.js +++ b/src/components/input/MonthSelect.tsx @@ -20,7 +20,7 @@ export function MonthSelect({ date = new Date(), onChange }) { const year = date.getFullYear(); const ref = useRef(); - const handleChange = (close, date) => { + const handleChange = (close: () => void, date: Date) => { onChange(`range:${startOfMonth(date).getTime()}:${endOfMonth(date).getTime()}`); close(); }; @@ -53,12 +53,8 @@ export function MonthSelect({ date = new Date(), onChange }) { - {close => ( - + {(close: any) => ( + )} diff --git a/src/components/input/ProfileButton.js b/src/components/input/ProfileButton.tsx similarity index 100% rename from src/components/input/ProfileButton.js rename to src/components/input/ProfileButton.tsx diff --git a/src/components/input/RefreshButton.js b/src/components/input/RefreshButton.tsx similarity index 87% rename from src/components/input/RefreshButton.js rename to src/components/input/RefreshButton.tsx index 8b40cafa1..01e80378d 100644 --- a/src/components/input/RefreshButton.js +++ b/src/components/input/RefreshButton.tsx @@ -4,7 +4,13 @@ import useDateRange from 'components/hooks/useDateRange'; import Icons from 'components/icons'; import useMessages from 'components/hooks/useMessages'; -export function RefreshButton({ websiteId, isLoading }) { +export function RefreshButton({ + websiteId, + isLoading, +}: { + websiteId: string; + isLoading?: boolean; +}) { const { formatMessage, labels } = useMessages(); const [dateRange] = useDateRange(websiteId); diff --git a/src/components/input/SettingsButton.js b/src/components/input/SettingsButton.tsx similarity index 100% rename from src/components/input/SettingsButton.js rename to src/components/input/SettingsButton.tsx diff --git a/src/components/input/ThemeButton.js b/src/components/input/ThemeButton.tsx similarity index 100% rename from src/components/input/ThemeButton.js rename to src/components/input/ThemeButton.tsx diff --git a/src/components/input/WebsiteDateFilter.js b/src/components/input/WebsiteDateFilter.tsx similarity index 95% rename from src/components/input/WebsiteDateFilter.js rename to src/components/input/WebsiteDateFilter.tsx index 1725ca3be..cf1beaa1d 100644 --- a/src/components/input/WebsiteDateFilter.js +++ b/src/components/input/WebsiteDateFilter.tsx @@ -5,7 +5,7 @@ import { Button, Icon, Icons } from 'react-basics'; import DateFilter from './DateFilter'; import styles from './WebsiteDateFilter.module.css'; -export function WebsiteDateFilter({ websiteId }) { +export function WebsiteDateFilter({ websiteId }: { websiteId: string }) { const [dateRange, setDateRange] = useDateRange(websiteId); const { value, startDate, endDate, selectedUnit } = dateRange; const isFutureDate = diff --git a/src/components/input/WebsiteSelect.js b/src/components/input/WebsiteSelect.tsx similarity index 88% rename from src/components/input/WebsiteSelect.js rename to src/components/input/WebsiteSelect.tsx index 233342159..e125e2589 100644 --- a/src/components/input/WebsiteSelect.js +++ b/src/components/input/WebsiteSelect.tsx @@ -3,7 +3,13 @@ import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; import styles from './WebsiteSelect.module.css'; -export function WebsiteSelect({ websiteId, onSelect }) { +export function WebsiteSelect({ + websiteId, + onSelect, +}: { + websiteId: string; + onSelect?: (key: any) => void; +}) { const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); const { data } = useQuery({ diff --git a/src/components/layout/Grid.js b/src/components/layout/Grid.js deleted file mode 100644 index 86b08887b..000000000 --- a/src/components/layout/Grid.js +++ /dev/null @@ -1,18 +0,0 @@ -import classNames from 'classnames'; -import { mapChildren } from 'react-basics'; -import styles from './Grid.module.css'; - -export function Grid({ className, ...otherProps }) { - return
; -} - -export function GridRow(props) { - const { columns = 'two', className, children, ...otherProps } = props; - return ( -
- {mapChildren(children, child => { - return
{child}
; - })} -
- ); -} diff --git a/src/components/layout/Grid.tsx b/src/components/layout/Grid.tsx new file mode 100644 index 000000000..2a34fdc4f --- /dev/null +++ b/src/components/layout/Grid.tsx @@ -0,0 +1,34 @@ +import { CSSProperties } from 'react'; +import classNames from 'classnames'; +import { mapChildren } from 'react-basics'; +import styles from './Grid.module.css'; + +export interface GridProps { + className?: string; + style?: CSSProperties; + children?: any; +} + +export function Grid({ className, style, children }: GridProps) { + return ( +
+ {children} +
+ ); +} + +export function GridRow(props: { + [x: string]: any; + columns?: 'one' | 'two' | 'three' | 'one-two' | 'two-one'; + className?: string; + children?: any; +}) { + const { columns = 'two', className, children, ...otherProps } = props; + return ( +
+ {mapChildren(children, child => { + return
{child}
; + })} +
+ ); +} diff --git a/src/components/layout/NavGroup.js b/src/components/layout/NavGroup.tsx similarity index 90% rename from src/components/layout/NavGroup.js rename to src/components/layout/NavGroup.tsx index 361dffb5a..e95b61faf 100644 --- a/src/components/layout/NavGroup.js +++ b/src/components/layout/NavGroup.tsx @@ -6,13 +6,21 @@ import Link from 'next/link'; import Icons from 'components/icons'; import styles from './NavGroup.module.css'; +export interface NavGroupProps { + title: string; + items: any[]; + defaultExpanded?: boolean; + allowExpand?: boolean; + minimized?: boolean; +} + export function NavGroup({ title, items, defaultExpanded = true, allowExpand = true, minimized = false, -}) { +}: NavGroupProps) { const pathname = usePathname(); const [expanded, setExpanded] = useState(defaultExpanded); diff --git a/src/components/layout/SideNav.js b/src/components/layout/SideNav.tsx similarity index 82% rename from src/components/layout/SideNav.js rename to src/components/layout/SideNav.tsx index c93881e47..f38bdba07 100644 --- a/src/components/layout/SideNav.js +++ b/src/components/layout/SideNav.tsx @@ -4,6 +4,15 @@ import { usePathname } from 'next/navigation'; import Link from 'next/link'; import styles from './SideNav.module.css'; +export interface SideNavProps { + selectedKey: string; + items: any[]; + shallow?: boolean; + scroll?: boolean; + className?: boolean; + onSelect?: () => void; +} + export function SideNav({ selectedKey, items, @@ -11,7 +20,7 @@ export function SideNav({ scroll = false, className, onSelect = () => {}, -}) { +}: SideNavProps) { const pathname = usePathname(); return ( get(`/websites/${websiteId}/active`), - refetchInterval, enabled: !!websiteId, + refetchInterval, }); const count = useMemo(() => { diff --git a/src/components/metrics/BarChart.js b/src/components/metrics/BarChart.tsx similarity index 83% rename from src/components/metrics/BarChart.js rename to src/components/metrics/BarChart.tsx index 8341fbc74..8bb3a7a7f 100644 --- a/src/components/metrics/BarChart.js +++ b/src/components/metrics/BarChart.tsx @@ -10,12 +10,28 @@ import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; import { renderNumberLabels } from 'lib/charts'; import styles from './BarChart.module.css'; +export interface BarChartProps { + datasets?: any[]; + unit?: string; + animationDuration?: number; + stacked?: boolean; + isLoading?: boolean; + renderXLabel?: (label: string, index: number, values: any[]) => string; + renderYLabel?: (label: string, index: number, values: any[]) => string; + XAxisType?: string; + YAxisType?: string; + renderTooltipPopup?: (setTooltipPopup: (data: any) => void, model: any) => void; + onCreate?: (chart: any) => void; + onUpdate?: (chart: any) => void; + className?: string; +} + export function BarChart({ - datasets, + datasets = [], unit, animationDuration = DEFAULT_ANIMATION_DURATION, stacked = false, - loading = false, + isLoading = false, renderXLabel, renderYLabel, XAxisType = 'time', @@ -24,7 +40,7 @@ export function BarChart({ onCreate, onUpdate, className, -}) { +}: BarChartProps) { const canvas = useRef(); const chart = useRef(null); const [tooltip, setTooltipPopup] = useState(null); @@ -85,7 +101,7 @@ export function BarChart({ color: colors.chart.line, }, ticks: { - color: colors.text, + color: colors.chart.text, callback: renderYLabel || renderNumberLabels, }, }, @@ -106,16 +122,15 @@ export function BarChart({ const createChart = () => { Chart.defaults.font.family = 'Inter'; - const options = getOptions(); - chart.current = new Chart(canvas.current, { type: 'bar', data: { datasets, }, - options, }); + chart.current.options = getOptions(); + onCreate?.(chart.current); }; @@ -147,7 +162,7 @@ export function BarChart({ return (
- {loading && } + {isLoading && }
diff --git a/src/components/metrics/BrowsersTable.js b/src/components/metrics/BrowsersTable.tsx similarity index 85% rename from src/components/metrics/BrowsersTable.js rename to src/components/metrics/BrowsersTable.tsx index afc536637..e1c054358 100644 --- a/src/components/metrics/BrowsersTable.js +++ b/src/components/metrics/BrowsersTable.tsx @@ -1,9 +1,9 @@ import FilterLink from 'components/common/FilterLink'; -import MetricsTable from 'components/metrics/MetricsTable'; +import MetricsTable, { MetricsTableProps } from 'components/metrics/MetricsTable'; import useMessages from 'components/hooks/useMessages'; import useFormat from 'components/hooks/useFormat'; -export function BrowsersTable({ websiteId, ...props }) { +export function BrowsersTable(props: MetricsTableProps) { const { formatMessage, labels } = useMessages(); const { formatBrowser } = useFormat(); @@ -26,7 +26,6 @@ export function BrowsersTable({ websiteId, ...props }) { title={formatMessage(labels.browsers)} type="browser" metric={formatMessage(labels.visitors)} - websiteId={websiteId} renderLabel={renderLink} /> ); diff --git a/src/components/metrics/CitiesTable.js b/src/components/metrics/CitiesTable.tsx similarity index 85% rename from src/components/metrics/CitiesTable.js rename to src/components/metrics/CitiesTable.tsx index c5f1b1d73..69b899628 100644 --- a/src/components/metrics/CitiesTable.js +++ b/src/components/metrics/CitiesTable.tsx @@ -1,16 +1,16 @@ -import MetricsTable from './MetricsTable'; +import MetricsTable, { MetricsTableProps } from './MetricsTable'; import { emptyFilter } from 'lib/filters'; import FilterLink from 'components/common/FilterLink'; import useLocale from 'components/hooks/useLocale'; import useMessages from 'components/hooks/useMessages'; import useCountryNames from 'components/hooks/useCountryNames'; -export function CitiesTable({ websiteId, ...props }) { +export function CitiesTable(props: MetricsTableProps) { const { locale } = useLocale(); const { formatMessage, labels } = useMessages(); const countryNames = useCountryNames(locale); - const renderLabel = (city, country) => { + const renderLabel = (city: string, country: string) => { const name = countryNames[country]; return name ? `${city}, ${name}` : city; }; @@ -34,7 +34,6 @@ export function CitiesTable({ websiteId, ...props }) { title={formatMessage(labels.cities)} type="city" metric={formatMessage(labels.visitors)} - websiteId={websiteId} dataFilter={emptyFilter} renderLabel={renderLink} /> diff --git a/src/components/metrics/CountriesTable.js b/src/components/metrics/CountriesTable.tsx similarity index 73% rename from src/components/metrics/CountriesTable.js rename to src/components/metrics/CountriesTable.tsx index 6f3b75b04..99f9ca2fd 100644 --- a/src/components/metrics/CountriesTable.js +++ b/src/components/metrics/CountriesTable.tsx @@ -1,15 +1,24 @@ import FilterLink from 'components/common/FilterLink'; import useCountryNames from 'components/hooks/useCountryNames'; import { useLocale, useMessages, useFormat } from 'components/hooks'; -import MetricsTable from './MetricsTable'; +import MetricsTable, { MetricsTableProps } from './MetricsTable'; -export function CountriesTable({ websiteId, ...props }) { +export function CountriesTable({ + onDataLoad, + ...props +}: { + onDataLoad: (data: any) => void; +} & MetricsTableProps) { const { locale } = useLocale(); const countryNames = useCountryNames(locale); const { formatMessage, labels } = useMessages(); const { formatCountry } = useFormat(); - function renderLink({ x: code }) { + const handleDataLoad = (data: any) => { + onDataLoad?.(data); + }; + + const renderLink = ({ x: code }) => { return ( ); - } + }; return ( ); } diff --git a/src/components/metrics/DatePickerForm.js b/src/components/metrics/DatePickerForm.tsx similarity index 96% rename from src/components/metrics/DatePickerForm.js rename to src/components/metrics/DatePickerForm.tsx index 5e1906c3c..b4f0781e4 100644 --- a/src/components/metrics/DatePickerForm.js +++ b/src/components/metrics/DatePickerForm.tsx @@ -39,7 +39,7 @@ export function DatePickerForm({ return (
- + setSelected(key as any)}> diff --git a/src/components/metrics/DevicesTable.js b/src/components/metrics/DevicesTable.tsx similarity index 86% rename from src/components/metrics/DevicesTable.js rename to src/components/metrics/DevicesTable.tsx index 606b020ab..4ebdac1b5 100644 --- a/src/components/metrics/DevicesTable.js +++ b/src/components/metrics/DevicesTable.tsx @@ -1,9 +1,9 @@ -import MetricsTable from './MetricsTable'; +import MetricsTable, { MetricsTableProps } from './MetricsTable'; import FilterLink from 'components/common/FilterLink'; import useMessages from 'components/hooks/useMessages'; import { useFormat } from 'components/hooks'; -export function DevicesTable({ websiteId, ...props }) { +export function DevicesTable(props: MetricsTableProps) { const { formatMessage, labels } = useMessages(); const { formatDevice } = useFormat(); @@ -26,7 +26,6 @@ export function DevicesTable({ websiteId, ...props }) { title={formatMessage(labels.devices)} type="device" metric={formatMessage(labels.visitors)} - websiteId={websiteId} renderLabel={renderLink} /> ); diff --git a/src/components/metrics/EventsChart.js b/src/components/metrics/EventsChart.tsx similarity index 92% rename from src/components/metrics/EventsChart.js rename to src/components/metrics/EventsChart.tsx index 610a3616e..be6d4a0c3 100644 --- a/src/components/metrics/EventsChart.js +++ b/src/components/metrics/EventsChart.tsx @@ -7,7 +7,13 @@ import { useApi, useLocale, useDateRange, useTimezone, useNavigation } from 'com import { EVENT_COLORS } from 'lib/constants'; import { renderDateLabels, renderStatusTooltipPopup } from 'lib/charts'; -export function EventsChart({ websiteId, className, token }) { +export interface EventsChartProps { + websiteId: string; + className?: string; + token?: string; +} + +export function EventsChart({ websiteId, className, token }: EventsChartProps) { const { get, useQuery } = useApi(); const [{ startDate, endDate, unit, modified }] = useDateRange(websiteId); const { locale } = useLocale(); @@ -71,7 +77,6 @@ export function EventsChart({ websiteId, className, token }) { className={className} datasets={datasets} unit={unit} - height={300} loading={isLoading} stacked renderXLabel={renderDateLabels(unit, locale)} diff --git a/src/components/metrics/EventsTable.js b/src/components/metrics/EventsTable.tsx similarity index 69% rename from src/components/metrics/EventsTable.js rename to src/components/metrics/EventsTable.tsx index a8ae82aa2..26d9529b4 100644 --- a/src/components/metrics/EventsTable.js +++ b/src/components/metrics/EventsTable.tsx @@ -1,10 +1,10 @@ -import MetricsTable from './MetricsTable'; +import MetricsTable, { MetricsTableProps } from './MetricsTable'; import useMessages from 'components/hooks/useMessages'; -export function EventsTable({ websiteId, ...props }) { +export function EventsTable(props: MetricsTableProps) { const { formatMessage, labels } = useMessages(); - function handleDataLoad(data) { + function handleDataLoad(data: any) { props.onDataLoad?.(data); } @@ -14,7 +14,6 @@ export function EventsTable({ websiteId, ...props }) { title={formatMessage(labels.events)} type="event" metric={formatMessage(labels.actions)} - websiteId={websiteId} onDataLoad={handleDataLoad} /> ); diff --git a/src/components/metrics/FilterTags.js b/src/components/metrics/FilterTags.tsx similarity index 92% rename from src/components/metrics/FilterTags.js rename to src/components/metrics/FilterTags.tsx index db8fdcbdd..140385f5c 100644 --- a/src/components/metrics/FilterTags.js +++ b/src/components/metrics/FilterTags.tsx @@ -18,7 +18,7 @@ export function FilterTags({ params }) { return null; } - function handleCloseFilter(param) { + function handleCloseFilter(param?: string) { if (!param) { router.push(makeUrl({ view }, true)); } else { @@ -44,7 +44,7 @@ export function FilterTags({ params }) {
); })} - diff --git a/src/app/(main)/settings/teams/[id]/TeamEditForm.tsx b/src/app/(main)/settings/teams/[id]/TeamEditForm.tsx index b1dc58545..420afe9bb 100644 --- a/src/app/(main)/settings/teams/[id]/TeamEditForm.tsx +++ b/src/app/(main)/settings/teams/[id]/TeamEditForm.tsx @@ -18,15 +18,17 @@ const generateId = () => getRandomChars(16); export function TeamEditForm({ teamId, data, onSave, readOnly }) { const { formatMessage, labels } = useMessages(); const { post, useMutation } = useApi(); - const { mutate, error } = useMutation(data => post(`/teams/${teamId}`, data)); + const { mutate, error } = useMutation({ + mutationFn: (data: any) => post(`/teams/${teamId}`, data), + }); const ref = useRef(null); const [accessCode, setAccessCode] = useState(data.accessCode); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { ref.current.reset(data); - onSave(data); + onSave?.(data); }, }); }; diff --git a/src/app/(main)/settings/teams/[id]/TeamWebsiteRemoveButton.tsx b/src/app/(main)/settings/teams/[id]/TeamWebsiteRemoveButton.tsx index 59e393e10..0c039d4e9 100644 --- a/src/app/(main)/settings/teams/[id]/TeamWebsiteRemoveButton.tsx +++ b/src/app/(main)/settings/teams/[id]/TeamWebsiteRemoveButton.tsx @@ -5,10 +5,12 @@ import { Icon, Icons, LoadingButton, Text } from 'react-basics'; export function TeamWebsiteRemoveButton({ teamId, websiteId, onSave }) { const { formatMessage, labels } = useMessages(); const { del, useMutation } = useApi(); - const { mutate, isLoading } = useMutation(() => del(`/teams/${teamId}/websites/${websiteId}`)); + const { mutate, isPending } = useMutation({ + mutationFn: () => del(`/teams/${teamId}/websites/${websiteId}`), + }); const handleRemoveTeamMember = async () => { - await mutate(null, { + mutate(null, { onSuccess: () => { onSave(); }, @@ -16,7 +18,7 @@ export function TeamWebsiteRemoveButton({ teamId, websiteId, onSave }) { }; return ( - handleRemoveTeamMember()} isLoading={isLoading}> + handleRemoveTeamMember()} isLoading={isPending}> From 905b480c131a1e570317571d331a74b8b031bc7b Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 3 Dec 2023 22:20:36 -0800 Subject: [PATCH 11/28] Fixed mutate. --- src/app/(main)/reports/ReportDeleteButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(main)/reports/ReportDeleteButton.tsx b/src/app/(main)/reports/ReportDeleteButton.tsx index d3f1bb4f3..32ec819e4 100644 --- a/src/app/(main)/reports/ReportDeleteButton.tsx +++ b/src/app/(main)/reports/ReportDeleteButton.tsx @@ -14,7 +14,7 @@ export function ReportDeleteButton({ }) { const { formatMessage, labels } = useMessages(); const { del, useMutation } = useApi(); - const { mutate } = useMutation(reportId => del(`/reports/${reportId}`)); + const { mutate } = useMutation({ mutationFn: reportId => del(`/reports/${reportId}`) }); const handleConfirm = (close: () => void) => { mutate(reportId as any, { From 66d7a815fcabcf68fa61e54dd83d10b257196f58 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 3 Dec 2023 22:49:30 -0800 Subject: [PATCH 12/28] Fixed websites query. --- src/lib/auth.ts | 2 +- src/lib/crypto.ts | 2 +- src/pages/api/websites/index.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 97e20d980..cb3d3cc79 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -59,7 +59,7 @@ export async function canViewWebsite({ user, shareToken }: Auth, websiteId: stri return !!(await findTeamWebsiteByUserId(websiteId, user.id)); } -export async function canViewAllWebsite({ user }: Auth) { +export async function canViewAllWebsites({ user }: Auth) { return user.isAdmin; } diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts index b0ce20dee..a27633525 100644 --- a/src/lib/crypto.ts +++ b/src/lib/crypto.ts @@ -12,7 +12,7 @@ export function salt() { return hash(secret(), ROTATING_SALT); } -export function uuid(...args: [any]) { +export function uuid(...args: any) { if (!args.length) return v4(); return v5(hash(...args, salt()), v5.DNS); diff --git a/src/pages/api/websites/index.ts b/src/pages/api/websites/index.ts index 099649fae..02ec713bb 100644 --- a/src/pages/api/websites/index.ts +++ b/src/pages/api/websites/index.ts @@ -1,4 +1,4 @@ -import { canCreateWebsite, canViewAllWebsite } from 'lib/auth'; +import { canCreateWebsite, canViewAllWebsites } from 'lib/auth'; import { uuid } from 'lib/crypto'; import { useAuth, useCors, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, SearchFilter } from 'lib/types'; @@ -41,7 +41,7 @@ export default async ( } = req.auth; if (req.method === 'GET') { - if (canViewAllWebsite(req.auth)) { + if (await canViewAllWebsites(req.auth)) { const websites = getWebsites(req.query, { include: { teamWebsite: { From e718b22599cf67ff413671d4af6c712ad17aacdd Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 3 Dec 2023 23:02:28 -0800 Subject: [PATCH 13/28] Fixed types. --- src/pages/api/websites/index.ts | 2 +- src/queries/admin/website.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/api/websites/index.ts b/src/pages/api/websites/index.ts index 02ec713bb..89162067e 100644 --- a/src/pages/api/websites/index.ts +++ b/src/pages/api/websites/index.ts @@ -42,7 +42,7 @@ export default async ( if (req.method === 'GET') { if (await canViewAllWebsites(req.auth)) { - const websites = getWebsites(req.query, { + const websites = await getWebsites(req.query, { include: { teamWebsite: { include: { diff --git a/src/queries/admin/website.ts b/src/queries/admin/website.ts index 0e7f5124f..524e2e141 100644 --- a/src/queries/admin/website.ts +++ b/src/queries/admin/website.ts @@ -21,7 +21,7 @@ export async function getWebsiteByShareId(shareId: string) { export async function getWebsites( filters: WebsiteSearchFilter, options?: { include?: Prisma.WebsiteInclude }, -): Promise> { +): Promise> { const { userId, teamId, includeTeams, onlyTeams, query } = filters; const mode = prisma.getSearchMode(); @@ -105,7 +105,7 @@ export async function getWebsites( export async function getWebsitesByUserId( userId: string, filters?: WebsiteSearchFilter, -): Promise> { +): Promise> { return getWebsites( { userId, ...filters }, { @@ -133,7 +133,7 @@ export async function getWebsitesByUserId( export async function getWebsitesByTeamId( teamId: string, filters?: WebsiteSearchFilter, -): Promise> { +): Promise> { return getWebsites( { teamId, From 3f657d97b26d7763d1d2a59cf4166efea1c3f5c7 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 3 Dec 2023 23:16:50 -0800 Subject: [PATCH 14/28] Fixed bar chart rendering issue. --- src/components/metrics/BarChart.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/metrics/BarChart.tsx b/src/components/metrics/BarChart.tsx index 8bb3a7a7f..6d28dc201 100644 --- a/src/components/metrics/BarChart.tsx +++ b/src/components/metrics/BarChart.tsx @@ -127,10 +127,9 @@ export function BarChart({ data: { datasets, }, + options: getOptions() as any, }); - chart.current.options = getOptions(); - onCreate?.(chart.current); }; @@ -160,7 +159,7 @@ export function BarChart({ }, [datasets, unit, theme, animationDuration, locale]); return ( -
+ <>
{isLoading && } @@ -171,7 +170,7 @@ export function BarChart({
{tooltip}
)} -
+ ); } From 4fca98d25d91066e8e107d0a137de52a118001a9 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 5 Dec 2023 19:22:14 -0800 Subject: [PATCH 15/28] Added SettingsContext. --- next.config.js | 1 + src/app/(main)/dashboard/Dashboard.tsx | 2 +- src/app/(main)/settings/SettingsContext.tsx | 5 +++ src/app/(main)/settings/layout.tsx | 29 ++++++++++----- .../(main)/settings/users/UserAddButton.tsx | 2 +- src/app/(main)/settings/users/UserAddForm.tsx | 2 +- .../settings/users/UserDeleteButton.tsx | 2 +- .../{UserSettings.js => UserSettings.tsx} | 10 +++--- .../settings/websites/WebsiteAddButton.tsx | 2 +- .../settings/websites/WebsiteAddForm.tsx | 5 ++- ...WebsiteSettings.js => WebsiteSettings.tsx} | 31 +++++++--------- .../settings/websites/WebsitesDataTable.tsx | 36 +++++++++---------- .../settings/websites/WebsitesTable.tsx | 8 +++-- .../settings/websites/[id]/ShareUrl.tsx | 11 +++--- .../settings/websites/[id]/TrackingCode.tsx | 13 +++---- .../settings/websites/[id]/WebsiteData.tsx | 4 +-- .../websites/[id]/WebsiteDeleteForm.tsx | 7 ++-- .../websites/[id]/WebsiteEditForm.tsx | 8 +++-- .../websites/[id]/WebsiteResetForm.tsx | 5 ++- src/app/sso/page.tsx | 2 +- src/components/layout/Page.tsx | 2 +- src/index.ts | 2 ++ 22 files changed, 106 insertions(+), 83 deletions(-) create mode 100644 src/app/(main)/settings/SettingsContext.tsx rename src/app/(main)/settings/users/[id]/{UserSettings.js => UserSettings.tsx} (90%) rename src/app/(main)/settings/websites/{WebsiteSettings.js => WebsiteSettings.tsx} (77%) diff --git a/next.config.js b/next.config.js index c73790b8f..a155ece75 100644 --- a/next.config.js +++ b/next.config.js @@ -87,6 +87,7 @@ const config = { defaultLocale: process.env.DEFAULT_LOCALE || '', disableLogin: process.env.DISABLE_LOGIN || '', disableUI: process.env.DISABLE_UI || '', + hostUrl: process.env.HOST_URL || '', }, basePath, output: 'standalone', diff --git a/src/app/(main)/dashboard/Dashboard.tsx b/src/app/(main)/dashboard/Dashboard.tsx index 0f281aa79..ec1d793c2 100644 --- a/src/app/(main)/dashboard/Dashboard.tsx +++ b/src/app/(main)/dashboard/Dashboard.tsx @@ -38,7 +38,7 @@ export function Dashboard() { const { page } = params; if (query.isLoading) { - return ; + return ; } return ( diff --git a/src/app/(main)/settings/SettingsContext.tsx b/src/app/(main)/settings/SettingsContext.tsx new file mode 100644 index 000000000..898a40cc0 --- /dev/null +++ b/src/app/(main)/settings/SettingsContext.tsx @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +export const SettingsContext = createContext(null); + +export default SettingsContext; diff --git a/src/app/(main)/settings/layout.tsx b/src/app/(main)/settings/layout.tsx index f738f8834..f96123613 100644 --- a/src/app/(main)/settings/layout.tsx +++ b/src/app/(main)/settings/layout.tsx @@ -4,6 +4,7 @@ import useUser from 'components/hooks/useUser'; import useMessages from 'components/hooks/useMessages'; import SideNav from 'components/layout/SideNav'; import styles from './layout.module.css'; +import SettingsContext from './SettingsContext'; export default function SettingsLayout({ children }) { const { user } = useUser(); @@ -24,14 +25,26 @@ export default function SettingsLayout({ children }) { return null; } + const hostUrl = process.env.hostUrl || location.origin; + + const config = { + settingsUrl: '/settings/websites', + hostUrl, + shareUrl: hostUrl, + trackingCodeUrl: hostUrl, + websitesUrl: `/websites`, + }; + return ( -
- {!cloudMode && ( -
- -
- )} -
{children}
-
+ +
+ {!cloudMode && ( +
+ +
+ )} +
{children}
+
+
); } diff --git a/src/app/(main)/settings/users/UserAddButton.tsx b/src/app/(main)/settings/users/UserAddButton.tsx index 7f08107cb..1158ecec6 100644 --- a/src/app/(main)/settings/users/UserAddButton.tsx +++ b/src/app/(main)/settings/users/UserAddButton.tsx @@ -22,7 +22,7 @@ export function UserAddButton({ onSave }: { onSave?: () => void }) { {formatMessage(labels.createUser)} - {close => } + {(close: () => void) => } ); diff --git a/src/app/(main)/settings/users/UserAddForm.tsx b/src/app/(main)/settings/users/UserAddForm.tsx index 11066a24e..8c1537757 100644 --- a/src/app/(main)/settings/users/UserAddForm.tsx +++ b/src/app/(main)/settings/users/UserAddForm.tsx @@ -21,7 +21,7 @@ export function UserAddForm({ onSave, onClose }) { }); const { formatMessage, labels } = useMessages(); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { onSave(data); diff --git a/src/app/(main)/settings/users/UserDeleteButton.tsx b/src/app/(main)/settings/users/UserDeleteButton.tsx index 2b93c138e..775004a84 100644 --- a/src/app/(main)/settings/users/UserDeleteButton.tsx +++ b/src/app/(main)/settings/users/UserDeleteButton.tsx @@ -24,7 +24,7 @@ export function UserDeleteButton({ {formatMessage(labels.delete)} - {close => ( + {(close: () => void) => ( )} diff --git a/src/app/(main)/settings/users/[id]/UserSettings.js b/src/app/(main)/settings/users/[id]/UserSettings.tsx similarity index 90% rename from src/app/(main)/settings/users/[id]/UserSettings.js rename to src/app/(main)/settings/users/[id]/UserSettings.tsx index 3d8a92eae..d9b149c33 100644 --- a/src/app/(main)/settings/users/[id]/UserSettings.js +++ b/src/app/(main)/settings/users/[id]/UserSettings.tsx @@ -1,17 +1,17 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { Key, useEffect, useState } from 'react'; import { Item, Loading, Tabs, useToasts } from 'react-basics'; import UserEditForm from '../UserEditForm'; import PageHeader from 'components/layout/PageHeader'; import useApi from 'components/hooks/useApi'; -import UserWebsites from '../UserWebsites'; import useMessages from 'components/hooks/useMessages'; +import UserWebsites from '../UserWebsites'; export function UserSettings({ userId }) { const { formatMessage, labels, messages } = useMessages(); const [edit, setEdit] = useState(false); const [values, setValues] = useState(null); - const [tab, setTab] = useState('details'); + const [tab, setTab] = useState('details'); const { get, useQuery } = useApi(); const { showToast } = useToasts(); const { data, isLoading } = useQuery({ @@ -24,7 +24,7 @@ export function UserSettings({ userId }) { gcTime: 0, }); - const handleSave = data => { + const handleSave = (data: any) => { showToast({ message: formatMessage(messages.saved), variant: 'success' }); if (data) { setValues(state => ({ ...state, ...data })); @@ -42,7 +42,7 @@ export function UserSettings({ userId }) { }, [data]); if (isLoading || !values) { - return ; + return ; } return ( diff --git a/src/app/(main)/settings/websites/WebsiteAddButton.tsx b/src/app/(main)/settings/websites/WebsiteAddButton.tsx index 16681b0ed..7b4c92deb 100644 --- a/src/app/(main)/settings/websites/WebsiteAddButton.tsx +++ b/src/app/(main)/settings/websites/WebsiteAddButton.tsx @@ -22,7 +22,7 @@ export function WebsiteAddButton({ onSave }: { onSave?: () => void }) { {formatMessage(labels.addWebsite)} - {close => } + {(close: () => void) => } ); diff --git a/src/app/(main)/settings/websites/WebsiteAddForm.tsx b/src/app/(main)/settings/websites/WebsiteAddForm.tsx index 9f3ba1783..6d8fb8c32 100644 --- a/src/app/(main)/settings/websites/WebsiteAddForm.tsx +++ b/src/app/(main)/settings/websites/WebsiteAddForm.tsx @@ -10,12 +10,15 @@ import { import useApi from 'components/hooks/useApi'; import { DOMAIN_REGEX } from 'lib/constants'; import useMessages from 'components/hooks/useMessages'; +import { useContext } from 'react'; +import SettingsContext from '../SettingsContext'; export function WebsiteAddForm({ onSave, onClose }: { onSave?: () => void; onClose?: () => void }) { const { formatMessage, labels, messages } = useMessages(); + const { websitesUrl } = useContext(SettingsContext); const { post, useMutation } = useApi(); const { mutate, error, isPending } = useMutation({ - mutationFn: (data: any) => post('/websites', data), + mutationFn: (data: any) => post(websitesUrl, data), }); const handleSubmit = async (data: any) => { diff --git a/src/app/(main)/settings/websites/WebsiteSettings.js b/src/app/(main)/settings/websites/WebsiteSettings.tsx similarity index 77% rename from src/app/(main)/settings/websites/WebsiteSettings.js rename to src/app/(main)/settings/websites/WebsiteSettings.tsx index ffda838cb..e925d1cf4 100644 --- a/src/app/(main)/settings/websites/WebsiteSettings.js +++ b/src/app/(main)/settings/websites/WebsiteSettings.tsx @@ -1,5 +1,5 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState, Key } from 'react'; import { Item, Tabs, useToasts, Button, Text, Icon, Icons, Loading } from 'react-basics'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; @@ -10,33 +10,35 @@ import TrackingCode from './[id]/TrackingCode'; import ShareUrl from './[id]/ShareUrl'; import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; +import SettingsContext from '../SettingsContext'; -export function WebsiteSettings({ websiteId, openExternal = false, analyticsUrl }) { +export function WebsiteSettings({ websiteId, openExternal = false }) { const router = useRouter(); const { formatMessage, labels, messages } = useMessages(); const { get, useQuery } = useApi(); const { showToast } = useToasts(); + const { websitesUrl, settingsUrl } = useContext(SettingsContext); const { data, isLoading } = useQuery({ queryKey: ['website', websiteId], - queryFn: () => get(`/websites/${websiteId}`), + queryFn: () => get(`${websitesUrl}/${websiteId}`), enabled: !!websiteId, gcTime: 0, }); const [values, setValues] = useState(null); - const [tab, setTab] = useState('details'); + const [tab, setTab] = useState('details'); const showSuccess = () => { showToast({ message: formatMessage(messages.saved), variant: 'success' }); }; - const handleSave = data => { + const handleSave = (data: any) => { showSuccess(); - setValues(state => ({ ...state, ...data })); + setValues((state: any) => ({ ...state, ...data })); }; - const handleReset = async value => { + const handleReset = async (value: string) => { if (value === 'delete') { - router.push('/settings/websites'); + router.push(settingsUrl); } else if (value === 'reset') { showSuccess(); } @@ -55,7 +57,7 @@ export function WebsiteSettings({ websiteId, openExternal = false, analyticsUrl return ( <> - + - {close => ( + {(close: () => void) => ( )} @@ -42,7 +42,7 @@ export function WebsiteData({ - {close => ( + {(close: () => void) => ( )} diff --git a/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx b/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx index c3b5d74ad..e0f71041d 100644 --- a/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx +++ b/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx @@ -9,6 +9,8 @@ import { } from 'react-basics'; import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; +import { useContext } from 'react'; +import SettingsContext from '../../SettingsContext'; const CONFIRM_VALUE = 'DELETE'; @@ -22,12 +24,13 @@ export function WebsiteDeleteForm({ onClose?: () => void; }) { const { formatMessage, labels, messages, FormattedMessage } = useMessages(); + const { websitesUrl } = useContext(SettingsContext); const { del, useMutation } = useApi(); const { mutate, error } = useMutation({ - mutationFn: (data: any) => del(`/websites/${websiteId}`, data), + mutationFn: (data: any) => del(`${websitesUrl}/${websiteId}`, data), }); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { onSave(); diff --git a/src/app/(main)/settings/websites/[id]/WebsiteEditForm.tsx b/src/app/(main)/settings/websites/[id]/WebsiteEditForm.tsx index 9c05905cb..80b36cae1 100644 --- a/src/app/(main)/settings/websites/[id]/WebsiteEditForm.tsx +++ b/src/app/(main)/settings/websites/[id]/WebsiteEditForm.tsx @@ -1,8 +1,9 @@ import { SubmitButton, Form, FormInput, FormRow, FormButtons, TextField } from 'react-basics'; -import { useRef } from 'react'; +import { useContext, useRef } from 'react'; import useApi from 'components/hooks/useApi'; import { DOMAIN_REGEX } from 'lib/constants'; import useMessages from 'components/hooks/useMessages'; +import SettingsContext from '../../SettingsContext'; export function WebsiteEditForm({ websiteId, @@ -14,13 +15,14 @@ export function WebsiteEditForm({ onSave?: (data: any) => void; }) { const { formatMessage, labels, messages } = useMessages(); + const { websitesUrl } = useContext(SettingsContext); const { post, useMutation } = useApi(); const { mutate, error } = useMutation({ - mutationFn: (data: any) => post(`/websites/${websiteId}`, data), + mutationFn: (data: any) => post(`${websitesUrl}/${websiteId}`, data), }); const ref = useRef(null); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { ref.current.reset(data); diff --git a/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx b/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx index 76c2bc472..0c02c77bc 100644 --- a/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx +++ b/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx @@ -9,6 +9,8 @@ import { } from 'react-basics'; import useApi from 'components/hooks/useApi'; import useMessages from 'components/hooks/useMessages'; +import { useContext } from 'react'; +import SettingsContext from '../../SettingsContext'; const CONFIRM_VALUE = 'RESET'; @@ -22,9 +24,10 @@ export function WebsiteResetForm({ onClose?: () => void; }) { const { formatMessage, labels, messages, FormattedMessage } = useMessages(); + const { websitesUrl } = useContext(SettingsContext); const { post, useMutation } = useApi(); const { mutate, error } = useMutation({ - mutationFn: (data: any) => post(`/websites/${websiteId}/reset`, data), + mutationFn: (data: any) => post(`${websitesUrl}/${websiteId}/reset`, data), }); const handleSubmit = async (data: any) => { diff --git a/src/app/sso/page.tsx b/src/app/sso/page.tsx index 75ea945d4..e577767a8 100644 --- a/src/app/sso/page.tsx +++ b/src/app/sso/page.tsx @@ -18,5 +18,5 @@ export default function SSOPage() { } }, [router, url, token]); - return ; + return ; } diff --git a/src/components/layout/Page.tsx b/src/components/layout/Page.tsx index 2f7020128..e32a09a3a 100644 --- a/src/components/layout/Page.tsx +++ b/src/components/layout/Page.tsx @@ -23,7 +23,7 @@ export function Page({ } if (isLoading) { - return ; + return ; } return
{children}
; diff --git a/src/index.ts b/src/index.ts index de5550518..7b1920547 100644 --- a/src/index.ts +++ b/src/index.ts @@ -48,6 +48,8 @@ export * from 'app/(main)/settings/websites/WebsiteSettings'; export * from 'app/(main)/settings/websites/WebsitesDataTable'; export * from 'app/(main)/settings/websites/WebsitesTable'; +export * from 'app/(main)/settings/SettingsContext'; + export * from 'components/common/ConfirmDeleteForm'; export * from 'components/common/DataTable'; export * from 'components/common/Empty'; From c8eb76c7af8c6aee1a084c805314ff17eb4301c9 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 6 Dec 2023 01:26:58 -0800 Subject: [PATCH 16/28] Fixed share url. --- src/app/(main)/settings/layout.tsx | 3 +-- src/app/(main)/settings/websites/WebsiteSettings.tsx | 2 +- src/app/(main)/settings/websites/[id]/ShareUrl.tsx | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/app/(main)/settings/layout.tsx b/src/app/(main)/settings/layout.tsx index f96123613..1c30d2dba 100644 --- a/src/app/(main)/settings/layout.tsx +++ b/src/app/(main)/settings/layout.tsx @@ -21,7 +21,7 @@ export default function SettingsLayout({ children }) { const getKey = () => items.find(({ url }) => pathname === url)?.key; - if (cloudMode && pathname != '/settings/profile') { + if (cloudMode && pathname !== '/settings/profile') { return null; } @@ -29,7 +29,6 @@ export default function SettingsLayout({ children }) { const config = { settingsUrl: '/settings/websites', - hostUrl, shareUrl: hostUrl, trackingCodeUrl: hostUrl, websitesUrl: `/websites`, diff --git a/src/app/(main)/settings/websites/WebsiteSettings.tsx b/src/app/(main)/settings/websites/WebsiteSettings.tsx index e925d1cf4..4607b423c 100644 --- a/src/app/(main)/settings/websites/WebsiteSettings.tsx +++ b/src/app/(main)/settings/websites/WebsiteSettings.tsx @@ -51,7 +51,7 @@ export function WebsiteSettings({ websiteId, openExternal = false }) { }, [data]); if (isLoading || !values) { - return ; + return ; } return ( diff --git a/src/app/(main)/settings/websites/[id]/ShareUrl.tsx b/src/app/(main)/settings/websites/[id]/ShareUrl.tsx index 07a590faa..90ce6ea69 100644 --- a/src/app/(main)/settings/websites/[id]/ShareUrl.tsx +++ b/src/app/(main)/settings/websites/[id]/ShareUrl.tsx @@ -17,15 +17,15 @@ import SettingsContext from '../../SettingsContext'; const generateId = () => getRandomChars(16); export function ShareUrl({ websiteId, data, onSave }) { + const ref = useRef(null); + const { shareUrl, websitesUrl } = useContext(SettingsContext); const { formatMessage, labels, messages } = useMessages(); const { name, shareId } = data; const [id, setId] = useState(shareId); const { post, useMutation } = useApi(); const { mutate, error } = useMutation({ - mutationFn: (data: any) => post(`/websites/${websiteId}`, data), + mutationFn: (data: any) => post(`${websitesUrl}/${websiteId}`, data), }); - const ref = useRef(null); - const { shareUrl } = useContext(SettingsContext); const url = useMemo( () => `${shareUrl}${process.env.basePath}/share/${id}/${encodeURIComponent(name)}`, [id, name], From e67282d7d895a7c430174a6b343d05df8fe54c88 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 8 Dec 2023 22:14:55 -0800 Subject: [PATCH 17/28] Render correct OS names. --- src/components/input/SettingsButton.tsx | 7 +------ src/components/metrics/OSTable.tsx | 10 ++++++++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/input/SettingsButton.tsx b/src/components/input/SettingsButton.tsx index 46c725973..2a076d42b 100644 --- a/src/components/input/SettingsButton.tsx +++ b/src/components/input/SettingsButton.tsx @@ -15,12 +15,7 @@ export function SettingsButton() { - e.stopPropagation()} - > + diff --git a/src/components/metrics/OSTable.tsx b/src/components/metrics/OSTable.tsx index e8b8e81f7..c39cba226 100644 --- a/src/components/metrics/OSTable.tsx +++ b/src/components/metrics/OSTable.tsx @@ -2,14 +2,20 @@ import MetricsTable from './MetricsTable'; import FilterLink from 'components/common/FilterLink'; import useMessages from 'components/hooks/useMessages'; +const names = { + 'Mac OS': 'macOS', + 'Chrome OS': 'ChromeOS', + 'Sun OS': 'SunOS', +}; + export function OSTable({ websiteId, limit }: { websiteId: string; limit?: number }) { const { formatMessage, labels } = useMessages(); function renderLink({ x: os }) { return ( - + {os} Date: Sat, 9 Dec 2023 00:35:54 -0800 Subject: [PATCH 18/28] Convert realtime components to TS. --- ...bsiteEventData.js => WebsiteEventData.tsx} | 2 +- .../realtime/{Realtime.js => Realtime.tsx} | 22 +++++----- ...timeCountries.js => RealtimeCountries.tsx} | 0 .../{RealtimeHeader.js => RealtimeHeader.tsx} | 5 ++- .../{RealtimeHome.js => RealtimeHome.tsx} | 0 .../{RealtimeLog.js => RealtimeLog.tsx} | 4 +- .../{RealtimeUrls.js => RealtimeUrls.tsx} | 21 ++++++---- .../{WebsiteReports.js => WebsiteReports.tsx} | 0 src/components/hooks/useDateRange.ts | 14 ++++--- src/components/metrics/RealtimeChart.tsx | 7 ++-- src/lib/date.ts | 40 ++++++++++--------- src/lib/types.ts | 11 ++++- 12 files changed, 75 insertions(+), 51 deletions(-) rename src/app/(main)/websites/[id]/event-data/{WebsiteEventData.js => WebsiteEventData.tsx} (96%) rename src/app/(main)/websites/[id]/realtime/{Realtime.js => Realtime.tsx} (84%) rename src/app/(main)/websites/[id]/realtime/{RealtimeCountries.js => RealtimeCountries.tsx} (100%) rename src/app/(main)/websites/[id]/realtime/{RealtimeHeader.js => RealtimeHeader.tsx} (85%) rename src/app/(main)/websites/[id]/realtime/{RealtimeHome.js => RealtimeHome.tsx} (100%) rename src/app/(main)/websites/[id]/realtime/{RealtimeLog.js => RealtimeLog.tsx} (97%) rename src/app/(main)/websites/[id]/realtime/{RealtimeUrls.js => RealtimeUrls.tsx} (85%) rename src/app/(main)/websites/[id]/reports/{WebsiteReports.js => WebsiteReports.tsx} (100%) diff --git a/src/app/(main)/websites/[id]/event-data/WebsiteEventData.js b/src/app/(main)/websites/[id]/event-data/WebsiteEventData.tsx similarity index 96% rename from src/app/(main)/websites/[id]/event-data/WebsiteEventData.js rename to src/app/(main)/websites/[id]/event-data/WebsiteEventData.tsx index b67ee95e4..61a4dc622 100644 --- a/src/app/(main)/websites/[id]/event-data/WebsiteEventData.js +++ b/src/app/(main)/websites/[id]/event-data/WebsiteEventData.tsx @@ -6,7 +6,7 @@ import { EventDataMetricsBar } from './EventDataMetricsBar'; import { useDateRange, useApi, useNavigation } from 'components/hooks'; import styles from './WebsiteEventData.module.css'; -function useData(websiteId, event) { +function useData(websiteId: string, event: string) { const [dateRange] = useDateRange(websiteId); const { startDate, endDate } = dateRange; const { get, useQuery } = useApi(); diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.js b/src/app/(main)/websites/[id]/realtime/Realtime.tsx similarity index 84% rename from src/app/(main)/websites/[id]/realtime/Realtime.js rename to src/app/(main)/websites/[id]/realtime/Realtime.tsx index 37df458ca..6de65d7ae 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.js +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -1,7 +1,7 @@ 'use client'; import { useMemo, useState, useEffect } from 'react'; import { subMinutes, startOfMinute } from 'date-fns'; -import firstBy from 'thenby'; +import thenby from 'thenby'; import { Grid, GridRow } from 'components/layout/Grid'; import Page from 'components/layout/Page'; import RealtimeChart from 'components/metrics/RealtimeChart'; @@ -15,9 +15,10 @@ import useApi from 'components/hooks/useApi'; import { percentFilter } from 'lib/filters'; import { REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants'; import { useWebsite } from 'components/hooks'; +import { RealtimeData } from 'lib/types'; import styles from './Realtime.module.css'; -function mergeData(state = [], data = [], time) { +function mergeData(state = [], data = [], time: number) { const ids = state.map(({ __id }) => __id); return state .concat(data.filter(({ __id }) => !ids.includes(__id))) @@ -25,7 +26,7 @@ function mergeData(state = [], data = [], time) { } export function Realtime({ websiteId }) { - const [currentData, setCurrentData] = useState(); + const [currentData, setCurrentData] = useState(); const { get, useQuery } = useApi(); const { data: website } = useWebsite(websiteId); const { data, isLoading, error } = useQuery({ @@ -33,7 +34,6 @@ export function Realtime({ websiteId }) { queryFn: () => get(`/realtime/${websiteId}`, { startAt: currentData?.timestamp || 0 }), enabled: !!(websiteId && website), refetchInterval: REALTIME_INTERVAL, - cache: false, }); useEffect(() => { @@ -50,9 +50,9 @@ export function Realtime({ websiteId }) { } }, [data]); - const realtimeData = useMemo(() => { + const realtimeData: RealtimeData = useMemo(() => { if (!currentData) { - return { pageviews: [], sessions: [], events: [], countries: [], visitors: [] }; + return { pageviews: [], sessions: [], events: [], countries: [], visitors: [], timestamp: 0 }; } currentData.countries = percentFilter( @@ -75,7 +75,7 @@ export function Realtime({ websiteId }) { } return arr; }, []) - .sort(firstBy('y', -1)), + .sort(thenby.firstBy('y', -1)), ); currentData.visitors = currentData.sessions.reduce((arr, val) => { @@ -89,18 +89,18 @@ export function Realtime({ websiteId }) { }, [currentData]); if (isLoading || error) { - return ; + return ; } return ( <> - + - - + + diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeCountries.js b/src/app/(main)/websites/[id]/realtime/RealtimeCountries.tsx similarity index 100% rename from src/app/(main)/websites/[id]/realtime/RealtimeCountries.js rename to src/app/(main)/websites/[id]/realtime/RealtimeCountries.tsx diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeHeader.js b/src/app/(main)/websites/[id]/realtime/RealtimeHeader.tsx similarity index 85% rename from src/app/(main)/websites/[id]/realtime/RealtimeHeader.js rename to src/app/(main)/websites/[id]/realtime/RealtimeHeader.tsx index 75f2f2d43..ad03efd14 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeHeader.js +++ b/src/app/(main)/websites/[id]/realtime/RealtimeHeader.tsx @@ -1,10 +1,11 @@ import MetricCard from 'components/metrics/MetricCard'; import useMessages from 'components/hooks/useMessages'; +import { RealtimeData } from 'lib/types'; import styles from './RealtimeHeader.module.css'; -export function RealtimeHeader({ data = {} }) { +export function RealtimeHeader({ data }: { data: RealtimeData }) { const { formatMessage, labels } = useMessages(); - const { pageviews, visitors, events, countries } = data; + const { pageviews, visitors, events, countries } = data || {}; return (
diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeHome.js b/src/app/(main)/websites/[id]/realtime/RealtimeHome.tsx similarity index 100% rename from src/app/(main)/websites/[id]/realtime/RealtimeHome.js rename to src/app/(main)/websites/[id]/realtime/RealtimeHome.tsx diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeLog.js b/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx similarity index 97% rename from src/app/(main)/websites/[id]/realtime/RealtimeLog.js rename to src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx index b388b37b5..e33320ec7 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeLog.js +++ b/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx @@ -1,7 +1,7 @@ import { useMemo, useState } from 'react'; import { StatusLight, Icon, Text } from 'react-basics'; import { FixedSizeList } from 'react-window'; -import firstBy from 'thenby'; +import thenby from 'thenby'; import FilterButtons from 'components/common/FilterButtons'; import Empty from 'components/common/Empty'; import useLocale from 'components/hooks/useLocale'; @@ -130,7 +130,7 @@ export function RealtimeLog({ data, websiteDomain }) { } const { pageviews, visitors, events } = data; - const logs = [...pageviews, ...visitors, ...events].sort(firstBy('createdAt', -1)); + const logs = [...pageviews, ...visitors, ...events].sort(thenby.firstBy('createdAt', -1)); if (filter !== TYPE_ALL) { return logs.filter(({ __type }) => __type === filter); diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeUrls.js b/src/app/(main)/websites/[id]/realtime/RealtimeUrls.tsx similarity index 85% rename from src/app/(main)/websites/[id]/realtime/RealtimeUrls.js rename to src/app/(main)/websites/[id]/realtime/RealtimeUrls.tsx index 674858b2d..27a9ec5a6 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeUrls.js +++ b/src/app/(main)/websites/[id]/realtime/RealtimeUrls.tsx @@ -1,15 +1,22 @@ -import { useMemo, useState } from 'react'; +import { Key, useMemo, useState } from 'react'; import { ButtonGroup, Button, Flexbox } from 'react-basics'; -import firstBy from 'thenby'; +import thenby from 'thenby'; import { percentFilter } from 'lib/filters'; import ListTable from 'components/metrics/ListTable'; import { FILTER_PAGES, FILTER_REFERRERS } from 'lib/constants'; import useMessages from 'components/hooks/useMessages'; +import { RealtimeData } from 'lib/types'; -export function RealtimeUrls({ websiteDomain, data = {} }) { +export function RealtimeUrls({ + websiteDomain, + data, +}: { + websiteDomain: string; + data: RealtimeData; +}) { const { formatMessage, labels } = useMessages(); - const { pageviews } = data; - const [filter, setFilter] = useState(FILTER_REFERRERS); + const { pageviews } = data || {}; + const [filter, setFilter] = useState(FILTER_REFERRERS); const limit = 15; const buttons = [ @@ -48,7 +55,7 @@ export function RealtimeUrls({ websiteDomain, data = {} }) { } return arr; }, []) - .sort(firstBy('y', -1)) + .sort(thenby.firstBy('y', -1)) .slice(0, limit), ); @@ -64,7 +71,7 @@ export function RealtimeUrls({ websiteDomain, data = {} }) { } return arr; }, []) - .sort(firstBy('y', -1)) + .sort(thenby.firstBy('y', -1)) .slice(0, limit), ); diff --git a/src/app/(main)/websites/[id]/reports/WebsiteReports.js b/src/app/(main)/websites/[id]/reports/WebsiteReports.tsx similarity index 100% rename from src/app/(main)/websites/[id]/reports/WebsiteReports.js rename to src/app/(main)/websites/[id]/reports/WebsiteReports.tsx diff --git a/src/components/hooks/useDateRange.ts b/src/components/hooks/useDateRange.ts index 71361b69c..d8a493316 100644 --- a/src/components/hooks/useDateRange.ts +++ b/src/components/hooks/useDateRange.ts @@ -1,9 +1,10 @@ import { getMinimumUnit, parseDateRange } from 'lib/date'; import { setItem } from 'next-basics'; import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE } from 'lib/constants'; -import useLocale from './useLocale'; import websiteStore, { setWebsiteDateRange } from 'store/websites'; import appStore, { setDateRange } from 'store/app'; +import { DateRange } from 'lib/types'; +import useLocale from './useLocale'; import useApi from './useApi'; export function useDateRange(websiteId?: string) { @@ -14,9 +15,9 @@ export function useDateRange(websiteId?: string) { const globalConfig = appStore(state => state.dateRange); const dateRange = parseDateRange(websiteConfig || globalConfig || defaultConfig, locale); - const saveDateRange = async value => { + const saveDateRange = async (value: DateRange | string) => { if (websiteId) { - let dateRange = value; + let dateRange: DateRange | string = value; if (typeof value === 'string') { if (value === 'all') { @@ -37,14 +38,17 @@ export function useDateRange(websiteId?: string) { } } - setWebsiteDateRange(websiteId, dateRange); + setWebsiteDateRange(websiteId, dateRange as DateRange); } else { setItem(DATE_RANGE_CONFIG, value); setDateRange(value); } }; - return [dateRange, saveDateRange]; + return [dateRange, saveDateRange] as [ + { startDate: Date; endDate: Date }, + (value: string | DateRange) => void, + ]; } export default useDateRange; diff --git a/src/components/metrics/RealtimeChart.tsx b/src/components/metrics/RealtimeChart.tsx index f1a781bdc..1ca0719a3 100644 --- a/src/components/metrics/RealtimeChart.tsx +++ b/src/components/metrics/RealtimeChart.tsx @@ -3,6 +3,7 @@ import { format, startOfMinute, subMinutes, isBefore } from 'date-fns'; import PageviewsChart from './PageviewsChart'; import { getDateArray } from 'lib/date'; import { DEFAULT_ANIMATION_DURATION, REALTIME_RANGE } from 'lib/constants'; +import { RealtimeData } from 'lib/types'; function mapData(data: any[]) { let last = 0; @@ -24,11 +25,9 @@ function mapData(data: any[]) { } export interface RealtimeChartProps { - data: { - pageviews: any[]; - visitors: any[]; - }; + data: RealtimeData; unit: string; + className?: string; } export function RealtimeChart({ data, unit, ...props }: RealtimeChartProps) { diff --git a/src/lib/date.ts b/src/lib/date.ts index 510573092..81c37e699 100644 --- a/src/lib/date.ts +++ b/src/lib/date.ts @@ -32,6 +32,7 @@ import { subWeeks, } from 'date-fns'; import { getDateLocale } from 'lib/lang'; +import { DateRange } from 'lib/types'; export const TIME_UNIT = { minute: 'minute', @@ -54,13 +55,13 @@ export function getTimezone() { return moment.tz.guess(); } -export function getLocalTime(t) { +export function getLocalTime(t: string | number | Date) { return addMinutes(new Date(t), new Date().getTimezoneOffset()); } -export function parseDateRange(value, locale = 'en-US') { +export function parseDateRange(value: string | object, locale = 'en-US'): DateRange { if (typeof value === 'object') { - return value; + return value as DateRange; } if (value === 'all') { @@ -93,7 +94,7 @@ export function parseDateRange(value, locale = 'en-US') { if (!match) return null; const { num, unit } = match.groups; - const selectedUnit = { num, unit }; + const selectedUnit = { num: +num, unit }; if (+num === 1) { switch (unit) { @@ -172,7 +173,7 @@ export function parseDateRange(value, locale = 'en-US') { switch (unit) { case 'day': return { - startDate: subDays(startOfDay(now), num - 1), + startDate: subDays(startOfDay(now), +num - 1), endDate: endOfDay(now), unit, value, @@ -180,7 +181,7 @@ export function parseDateRange(value, locale = 'en-US') { }; case 'hour': return { - startDate: subHours(startOfHour(now), num - 1), + startDate: subHours(startOfHour(now), +num - 1), endDate: endOfHour(now), unit, value, @@ -189,7 +190,10 @@ export function parseDateRange(value, locale = 'en-US') { } } -export function incrementDateRange(value, increment) { +export function incrementDateRange( + value: { startDate: any; endDate: any; selectedUnit: any }, + increment: number, +) { const { startDate, endDate, selectedUnit } = value; const { num, unit } = selectedUnit; @@ -235,7 +239,7 @@ export function incrementDateRange(value, increment) { } } -export function getAllowedUnits(startDate, endDate) { +export function getAllowedUnits(startDate: Date, endDate: Date) { const units = ['minute', 'hour', 'day', 'month', 'year']; const minUnit = getMinimumUnit(startDate, endDate); const index = units.indexOf(minUnit === 'year' ? 'month' : minUnit); @@ -243,7 +247,7 @@ export function getAllowedUnits(startDate, endDate) { return index >= 0 ? units.splice(index) : []; } -export function getMinimumUnit(startDate, endDate) { +export function getMinimumUnit(startDate: number | Date, endDate: number | Date) { if (differenceInMinutes(endDate, startDate) <= 60) { return 'minute'; } else if (differenceInHours(endDate, startDate) <= 48) { @@ -257,25 +261,25 @@ export function getMinimumUnit(startDate, endDate) { return 'year'; } -export function getDateFromString(str) { +export function getDateFromString(str: string) { const [ymd, hms] = str.split(' '); const [year, month, day] = ymd.split('-'); if (hms) { const [hour, min, sec] = hms.split(':'); - return new Date(year, month - 1, day, hour, min, sec); + return new Date(+year, +month - 1, +day, +hour, +min, +sec); } - return new Date(year, month - 1, day); + return new Date(+year, +month - 1, +day); } -export function getDateArray(data, startDate, endDate, unit) { +export function getDateArray(data: any[], startDate: Date, endDate: Date, unit: string) { const arr = []; const [diff, add, normalize] = dateFuncs[unit]; const n = diff(endDate, startDate) + 1; - function findData(date) { + function findData(date: Date) { const d = data.find(({ x }) => { return normalize(getDateFromString(x)).getTime() === date.getTime(); }); @@ -293,7 +297,7 @@ export function getDateArray(data, startDate, endDate, unit) { return arr; } -export function getDateLength(startDate, endDate, unit) { +export function getDateLength(startDate: Date, endDate: Date, unit: string | number) { const [diff] = dateFuncs[unit]; return diff(endDate, startDate) + 1; } @@ -310,7 +314,7 @@ export const CUSTOM_FORMATS = { }, }; -export function formatDate(date, str, locale = 'en-US') { +export function formatDate(date: string | number | Date, str: string, locale = 'en-US') { return format( typeof date === 'string' ? new Date(date) : date, CUSTOM_FORMATS?.[locale]?.[str] || str, @@ -320,10 +324,10 @@ export function formatDate(date, str, locale = 'en-US') { ); } -export function maxDate(...args) { +export function maxDate(...args: Date[]) { return max(args.filter(n => isDate(n))); } -export function minDate(...args) { +export function minDate(...args: any[]) { return min(args.filter(n => isDate(n))); } diff --git a/src/lib/types.ts b/src/lib/types.ts index a7fab1e7d..900f2e5a6 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -180,7 +180,7 @@ export interface DateRange { endDate: Date; value: string; unit?: TimeUnit; - selectedUnit?: TimeUnit; + selectedUnit?: { num: number; unit: TimeUnit }; } export interface QueryFilters { @@ -207,3 +207,12 @@ export interface QueryOptions { joinSession?: boolean; columns?: { [key: string]: string }; } + +export interface RealtimeData { + pageviews: any[]; + sessions: any[]; + events: any[]; + timestamp: number; + countries?: any[]; + visitors?: any[]; +} From c520a329d25a7c8b66cf8c21372cabebca5adb9e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 9 Dec 2023 01:25:02 -0800 Subject: [PATCH 19/28] Fixed activity log timestamp. --- package.json | 1 + src/app/(main)/websites/[id]/realtime/Realtime.tsx | 2 +- src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx | 6 +++--- yarn.lock | 7 +++++++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index a158696cf..782e039bf 100644 --- a/package.json +++ b/package.json @@ -129,6 +129,7 @@ "@types/node": "^20.9.0", "@types/react": "^18.2.41", "@types/react-dom": "^18.2.17", + "@types/react-window": "^1.8.8", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", "cross-env": "^7.0.3", diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.tsx b/src/app/(main)/websites/[id]/realtime/Realtime.tsx index 6de65d7ae..285d98454 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.tsx +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -95,7 +95,7 @@ export function Realtime({ websiteId }) { return ( <> - + diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx b/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx index e33320ec7..7cae0abca 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx +++ b/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx @@ -8,11 +8,11 @@ import useLocale from 'components/hooks/useLocale'; import useCountryNames from 'components/hooks/useCountryNames'; import { BROWSERS } from 'lib/constants'; import { stringToColor } from 'lib/format'; -import { formatDate } from 'lib/date'; import { safeDecodeURI } from 'next-basics'; import Icons from 'components/icons'; import styles from './RealtimeLog.module.css'; import useMessages from 'components/hooks/useMessages'; +import { format } from 'date-fns'; const TYPE_ALL = 'all'; const TYPE_PAGEVIEW = 'pageview'; @@ -50,7 +50,7 @@ export function RealtimeLog({ data, websiteDomain }) { }, ]; - const getTime = ({ createdAt }) => formatDate(new Date(createdAt), 'pp', locale); + const getTime = ({ timestamp }) => format(timestamp, 'h:mm:ss'); const getColor = ({ id, sessionId }) => stringToColor(sessionId || id); @@ -146,7 +146,7 @@ export function RealtimeLog({ data, websiteDomain }) {
{logs?.length === 0 && } {logs?.length > 0 && ( - + {Row} )} diff --git a/yarn.lock b/yarn.lock index bdb0d4843..1ecdaaf31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2431,6 +2431,13 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" +"@types/react-window@^1.8.8": + version "1.8.8" + resolved "https://registry.yarnpkg.com/@types/react-window/-/react-window-1.8.8.tgz#c20645414d142364fbe735818e1c1e0a145696e3" + integrity sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@16 || 17 || 18": version "18.2.30" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.30.tgz#b84f786864fc46f18545364a54d5e1316308e59b" From 7a5f28870f3f87452cc0d0e33ecba933308228f2 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 9 Dec 2023 01:41:07 -0800 Subject: [PATCH 20/28] Fixed realtime chart rendering of initial payload. --- .../websites/[id]/realtime/Realtime.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.tsx b/src/app/(main)/websites/[id]/realtime/Realtime.tsx index 285d98454..e77af2898 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.tsx +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -38,15 +38,19 @@ export function Realtime({ websiteId }) { useEffect(() => { if (data) { - const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE); - const time = date.getTime(); + if (!currentData) { + setCurrentData(data); + } else { + const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE); + const time = date.getTime(); - setCurrentData(state => ({ - pageviews: mergeData(state?.pageviews, data.pageviews, time), - sessions: mergeData(state?.sessions, data.sessions, time), - events: mergeData(state?.events, data.events, time), - timestamp: data.timestamp, - })); + setCurrentData(state => ({ + pageviews: mergeData(state?.pageviews, data.pageviews, time), + sessions: mergeData(state?.sessions, data.sessions, time), + events: mergeData(state?.events, data.events, time), + timestamp: data.timestamp, + })); + } } }, [data]); From 92a513e4d06c861968d337e120eafac837242c6e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 9 Dec 2023 20:55:50 -0800 Subject: [PATCH 21/28] Fixed realtime chart rendering. --- .../websites/[id]/realtime/Realtime.tsx | 27 +++++++--------- src/pages/api/realtime/[id].ts | 6 ++-- src/queries/analytics/events/getEvents.ts | 4 +++ src/queries/analytics/getRealtimeData.ts | 32 ++++++++++++++----- src/queries/analytics/sessions/getSessions.ts | 6 +++- 5 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.tsx b/src/app/(main)/websites/[id]/realtime/Realtime.tsx index e77af2898..34e292812 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.tsx +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -19,9 +19,9 @@ import { RealtimeData } from 'lib/types'; import styles from './Realtime.module.css'; function mergeData(state = [], data = [], time: number) { - const ids = state.map(({ __id }) => __id); + const ids = state.map(({ id }) => id); return state - .concat(data.filter(({ __id }) => !ids.includes(__id))) + .concat(data.filter(({ id }) => !ids.includes(id))) .filter(({ timestamp }) => timestamp >= time); } @@ -38,21 +38,18 @@ export function Realtime({ websiteId }) { useEffect(() => { if (data) { - if (!currentData) { - setCurrentData(data); - } else { - const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE); - const time = date.getTime(); + const date = subMinutes(startOfMinute(new Date()), REALTIME_RANGE); + const time = date.getTime(); + const { pageviews, sessions, events, timestamp } = data; - setCurrentData(state => ({ - pageviews: mergeData(state?.pageviews, data.pageviews, time), - sessions: mergeData(state?.sessions, data.sessions, time), - events: mergeData(state?.events, data.events, time), - timestamp: data.timestamp, - })); - } + setCurrentData(state => ({ + pageviews: mergeData(state?.pageviews, pageviews, time), + sessions: mergeData(state?.sessions, sessions, time), + events: mergeData(state?.events, events, time), + timestamp, + })); } - }, [data]); + }, [data, currentData]); const realtimeData: RealtimeData = useMemo(() => { if (!currentData) { diff --git a/src/pages/api/realtime/[id].ts b/src/pages/api/realtime/[id].ts index 212d4a0f2..0edd589d8 100644 --- a/src/pages/api/realtime/[id].ts +++ b/src/pages/api/realtime/[id].ts @@ -1,4 +1,4 @@ -import { subMinutes } from 'date-fns'; +import { startOfMinute, subMinutes } from 'date-fns'; import { canViewWebsite } from 'lib/auth'; import { useAuth, useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, RealtimeInit } from 'lib/types'; @@ -6,6 +6,8 @@ import { NextApiResponse } from 'next'; import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { getRealtimeData } from 'queries'; import * as yup from 'yup'; +import { REALTIME_RANGE } from 'lib/constants'; + export interface RealtimeRequestQuery { id: string; startAt: number; @@ -32,7 +34,7 @@ export default async ( return unauthorized(res); } - let startTime = subMinutes(new Date(), 30); + let startTime = subMinutes(startOfMinute(new Date()), REALTIME_RANGE); if (+startAt > startTime.getTime()) { startTime = new Date(+startAt); diff --git a/src/queries/analytics/events/getEvents.ts b/src/queries/analytics/events/getEvents.ts index fe074ec28..9ef279734 100644 --- a/src/queries/analytics/events/getEvents.ts +++ b/src/queries/analytics/events/getEvents.ts @@ -18,6 +18,9 @@ function relationalQuery(websiteId: string, startDate: Date, eventType: number) gte: startDate, }, }, + orderBy: { + createdAt: 'asc', + }, }); } @@ -39,6 +42,7 @@ function clickhouseQuery(websiteId: string, startDate: Date, eventType: number) where website_id = {websiteId:UUID} and created_at >= {startDate:DateTime64} and event_type = {eventType:UInt32} + order by created_at asc `, { websiteId, diff --git a/src/queries/analytics/getRealtimeData.ts b/src/queries/analytics/getRealtimeData.ts index 337e74751..868a5c70e 100644 --- a/src/queries/analytics/getRealtimeData.ts +++ b/src/queries/analytics/getRealtimeData.ts @@ -1,4 +1,3 @@ -import { md5 } from 'next-basics'; import { getSessions, getEvents } from 'queries/index'; import { EVENT_TYPE } from 'lib/constants'; @@ -9,18 +8,35 @@ export async function getRealtimeData(websiteId: string, startDate: Date) { getEvents(websiteId, startDate, EVENT_TYPE.customEvent), ]); - const decorate = (id: string, data: any[]) => { - return data.map((props: { [key: string]: any }) => ({ - ...props, - __id: md5(id, ...Object.values(props)), - __type: id, - timestamp: props.timestamp ? props.timestamp * 1000 : new Date(props.createdAt).getTime(), + const decorate = (type: string, data: any[]) => { + return data.map((values: { [key: string]: any }) => ({ + ...values, + __type: type, + timestamp: values.timestamp ? values.timestamp * 1000 : new Date(values.createdAt).getTime(), })); }; + const set = new Set(); + const uniques = (type: string, data: any[]) => { + return data.reduce((arr, values: { [key: string]: any }) => { + if (!set.has(values.id)) { + set.add(values.id); + + return arr.concat({ + ...values, + __type: type, + timestamp: values.timestamp + ? values.timestamp * 1000 + : new Date(values.createdAt).getTime(), + }); + } + return arr; + }, []); + }; + return { pageviews: decorate('pageview', pageviews), - sessions: decorate('session', sessions), + sessions: uniques('session', sessions), events: decorate('event', events), timestamp: Date.now(), }; diff --git a/src/queries/analytics/sessions/getSessions.ts b/src/queries/analytics/sessions/getSessions.ts index d67edd5ef..b92e3af99 100644 --- a/src/queries/analytics/sessions/getSessions.ts +++ b/src/queries/analytics/sessions/getSessions.ts @@ -17,6 +17,9 @@ async function relationalQuery(websiteId: string, startDate: Date) { gte: startDate, }, }, + orderBy: { + createdAt: 'asc', + }, }); } @@ -25,7 +28,7 @@ async function clickhouseQuery(websiteId: string, startDate: Date) { return rawQuery( ` - select distinct + select session_id as id, website_id as websiteId, created_at as createdAt, @@ -43,6 +46,7 @@ async function clickhouseQuery(websiteId: string, startDate: Date) { from website_event where website_id = {websiteId:UUID} and created_at >= {startDate:DateTime64} + order by created_at asc `, { websiteId, From 44e243ad12b181bcaf05d644feb10e53f8b92acd Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 9 Dec 2023 21:30:57 -0800 Subject: [PATCH 22/28] Moved SettingsProvider to root Providers component. --- src/app/(main)/settings/layout.tsx | 28 ++++++------------- .../websites/[id]/realtime/Realtime.tsx | 2 +- src/app/Providers.tsx | 26 +++++++++++++---- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/app/(main)/settings/layout.tsx b/src/app/(main)/settings/layout.tsx index 1c30d2dba..e36b5b53d 100644 --- a/src/app/(main)/settings/layout.tsx +++ b/src/app/(main)/settings/layout.tsx @@ -4,7 +4,6 @@ import useUser from 'components/hooks/useUser'; import useMessages from 'components/hooks/useMessages'; import SideNav from 'components/layout/SideNav'; import styles from './layout.module.css'; -import SettingsContext from './SettingsContext'; export default function SettingsLayout({ children }) { const { user } = useUser(); @@ -25,25 +24,14 @@ export default function SettingsLayout({ children }) { return null; } - const hostUrl = process.env.hostUrl || location.origin; - - const config = { - settingsUrl: '/settings/websites', - shareUrl: hostUrl, - trackingCodeUrl: hostUrl, - websitesUrl: `/websites`, - }; - return ( - -
- {!cloudMode && ( -
- -
- )} -
{children}
-
-
+
+ {!cloudMode && ( +
+ +
+ )} +
{children}
+
); } diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.tsx b/src/app/(main)/websites/[id]/realtime/Realtime.tsx index 34e292812..7f17190dd 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.tsx +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -64,7 +64,7 @@ export function Realtime({ websiteId }) { } return arr; }, []) - .reduce((arr, { country }) => { + .reduce((arr: { x: any; y: number }[], { country }: any) => { if (country) { const row = arr.find(({ x }) => x === country); diff --git a/src/app/Providers.tsx b/src/app/Providers.tsx index c3d626993..0abebf867 100644 --- a/src/app/Providers.tsx +++ b/src/app/Providers.tsx @@ -3,6 +3,7 @@ import { IntlProvider } from 'react-intl'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactBasicsProvider } from 'react-basics'; import ErrorBoundary from 'components/common/ErrorBoundary'; +import SettingsContext from 'app/(main)/settings/SettingsContext'; import useLocale from 'components/hooks/useLocale'; import 'chartjs-adapter-date-fns'; @@ -24,14 +25,29 @@ function MessagesProvider({ children }) { ); } +function SettingsProvider({ children }) { + const hostUrl = process.env.hostUrl || location.origin; + + const config = { + settingsUrl: '/settings/websites', + shareUrl: hostUrl, + trackingCodeUrl: hostUrl, + websitesUrl: `/websites`, + }; + + return {children}; +} + export function Providers({ children }) { return ( - - - {children} - - + + + + {children} + + + ); } From 08bd9e835720901f6d3c8867adfa492d4eb5c128 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 9 Dec 2023 22:18:47 -0800 Subject: [PATCH 23/28] Upgrade to Next 14. --- package.json | 2 +- .../websites/[id]/realtime/Realtime.tsx | 10 +- src/app/Providers.tsx | 19 +-- yarn.lock | 111 +++++++++--------- 4 files changed, 74 insertions(+), 68 deletions(-) diff --git a/package.json b/package.json index 782e039bf..db10755c9 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "kafkajs": "^2.1.0", "maxmind": "^4.3.6", "moment-timezone": "^0.5.35", - "next": "13.5.6", + "next": "14.0.4", "next-basics": "^0.39.0", "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.tsx b/src/app/(main)/websites/[id]/realtime/Realtime.tsx index 7f17190dd..ee710d737 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.tsx +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -6,16 +6,16 @@ import { Grid, GridRow } from 'components/layout/Grid'; import Page from 'components/layout/Page'; import RealtimeChart from 'components/metrics/RealtimeChart'; import WorldMap from 'components/metrics/WorldMap'; +import useApi from 'components/hooks/useApi'; +import { useWebsite } from 'components/hooks'; +import { percentFilter } from 'lib/filters'; +import { REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants'; +import { RealtimeData } from 'lib/types'; import RealtimeLog from './RealtimeLog'; import RealtimeHeader from './RealtimeHeader'; import RealtimeUrls from './RealtimeUrls'; import RealtimeCountries from './RealtimeCountries'; import WebsiteHeader from '../WebsiteHeader'; -import useApi from 'components/hooks/useApi'; -import { percentFilter } from 'lib/filters'; -import { REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants'; -import { useWebsite } from 'components/hooks'; -import { RealtimeData } from 'lib/types'; import styles from './Realtime.module.css'; function mergeData(state = [], data = [], time: number) { diff --git a/src/app/Providers.tsx b/src/app/Providers.tsx index 0abebf867..c2ddd2ff7 100644 --- a/src/app/Providers.tsx +++ b/src/app/Providers.tsx @@ -1,4 +1,5 @@ 'use client'; +import { useEffect, useState } from 'react'; import { IntlProvider } from 'react-intl'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactBasicsProvider } from 'react-basics'; @@ -26,14 +27,18 @@ function MessagesProvider({ children }) { } function SettingsProvider({ children }) { - const hostUrl = process.env.hostUrl || location.origin; + const [config, setConfig] = useState({}); - const config = { - settingsUrl: '/settings/websites', - shareUrl: hostUrl, - trackingCodeUrl: hostUrl, - websitesUrl: `/websites`, - }; + useEffect(() => { + const hostUrl = process.env.hostUrl || window?.location.origin; + + setConfig({ + settingsUrl: '/settings/websites', + shareUrl: hostUrl, + trackingCodeUrl: hostUrl, + websitesUrl: `/websites`, + }); + }, []); return {children}; } diff --git a/yarn.lock b/yarn.lock index 1ecdaaf31..b24830a3c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1771,10 +1771,10 @@ "@netlify/node-cookies" "^0.1.0" urlpattern-polyfill "8.0.2" -"@next/env@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.6.tgz#c1148e2e1aa166614f05161ee8f77ded467062bc" - integrity sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw== +"@next/env@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/env/-/env-14.0.4.tgz#d5cda0c4a862d70ae760e58c0cd96a8899a2e49a" + integrity sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ== "@next/eslint-plugin-next@12.3.4": version "12.3.4" @@ -1783,50 +1783,50 @@ dependencies: glob "7.1.7" -"@next/swc-darwin-arm64@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.6.tgz#b15d139d8971360fca29be3bdd703c108c9a45fb" - integrity sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA== +"@next/swc-darwin-arm64@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz#27b1854c2cd04eb1d5e75081a1a792ad91526618" + integrity sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg== -"@next/swc-darwin-x64@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz#9c72ee31cc356cb65ce6860b658d807ff39f1578" - integrity sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA== +"@next/swc-darwin-x64@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz#9940c449e757d0ee50bb9e792d2600cc08a3eb3b" + integrity sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw== -"@next/swc-linux-arm64-gnu@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz#59f5f66155e85380ffa26ee3d95b687a770cfeab" - integrity sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg== +"@next/swc-linux-arm64-gnu@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz#0eafd27c8587f68ace7b4fa80695711a8434de21" + integrity sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w== -"@next/swc-linux-arm64-musl@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz#f012518228017052736a87d69bae73e587c76ce2" - integrity sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q== +"@next/swc-linux-arm64-musl@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz#2b0072adb213f36dada5394ea67d6e82069ae7dd" + integrity sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ== -"@next/swc-linux-x64-gnu@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz#339b867a7e9e7ee727a700b496b269033d820df4" - integrity sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw== +"@next/swc-linux-x64-gnu@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz#68c67d20ebc8e3f6ced6ff23a4ba2a679dbcec32" + integrity sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A== -"@next/swc-linux-x64-musl@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz#ae0ae84d058df758675830bcf70ca1846f1028f2" - integrity sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ== +"@next/swc-linux-x64-musl@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz#67cd81b42fb2caf313f7992fcf6d978af55a1247" + integrity sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw== -"@next/swc-win32-arm64-msvc@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz#a5cc0c16920485a929a17495064671374fdbc661" - integrity sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg== +"@next/swc-win32-arm64-msvc@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz#be06585906b195d755ceda28f33c633e1443f1a3" + integrity sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w== -"@next/swc-win32-ia32-msvc@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz#6a2409b84a2cbf34bf92fe714896455efb4191e4" - integrity sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg== +"@next/swc-win32-ia32-msvc@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz#e76cabefa9f2d891599c3d85928475bd8d3f6600" + integrity sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg== -"@next/swc-win32-x64-msvc@13.5.6": - version "13.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz#4a3e2a206251abc729339ba85f60bc0433c2865d" - integrity sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ== +"@next/swc-win32-x64-msvc@14.0.4": + version "14.0.4" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz#e74892f1a9ccf41d3bf5979ad6d3d77c07b9cba1" + integrity sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -4949,7 +4949,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4: +graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.2, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -6330,28 +6330,29 @@ next-basics@^0.39.0: jsonwebtoken "^9.0.0" pure-rand "^6.0.2" -next@13.5.6: - version "13.5.6" - resolved "https://registry.yarnpkg.com/next/-/next-13.5.6.tgz#e964b5853272236c37ce0dd2c68302973cf010b1" - integrity sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw== +next@14.0.4: + version "14.0.4" + resolved "https://registry.yarnpkg.com/next/-/next-14.0.4.tgz#bf00b6f835b20d10a5057838fa2dfced1d0d84dc" + integrity sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA== dependencies: - "@next/env" "13.5.6" + "@next/env" "14.0.4" "@swc/helpers" "0.5.2" busboy "1.6.0" caniuse-lite "^1.0.30001406" + graceful-fs "^4.2.11" postcss "8.4.31" styled-jsx "5.1.1" watchpack "2.4.0" optionalDependencies: - "@next/swc-darwin-arm64" "13.5.6" - "@next/swc-darwin-x64" "13.5.6" - "@next/swc-linux-arm64-gnu" "13.5.6" - "@next/swc-linux-arm64-musl" "13.5.6" - "@next/swc-linux-x64-gnu" "13.5.6" - "@next/swc-linux-x64-musl" "13.5.6" - "@next/swc-win32-arm64-msvc" "13.5.6" - "@next/swc-win32-ia32-msvc" "13.5.6" - "@next/swc-win32-x64-msvc" "13.5.6" + "@next/swc-darwin-arm64" "14.0.4" + "@next/swc-darwin-x64" "14.0.4" + "@next/swc-linux-arm64-gnu" "14.0.4" + "@next/swc-linux-arm64-musl" "14.0.4" + "@next/swc-linux-x64-gnu" "14.0.4" + "@next/swc-linux-x64-musl" "14.0.4" + "@next/swc-win32-arm64-msvc" "14.0.4" + "@next/swc-win32-ia32-msvc" "14.0.4" + "@next/swc-win32-x64-msvc" "14.0.4" nice-try@^1.0.4: version "1.0.5" From 3a28fea8aca027a8c54dfdc943a20d2810229392 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 9 Dec 2023 22:45:55 -0800 Subject: [PATCH 24/28] Fixed broken links behavior. --- src/app/(main)/websites/[id]/realtime/Realtime.tsx | 2 +- src/app/share/[...id]/Header.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.tsx b/src/app/(main)/websites/[id]/realtime/Realtime.tsx index ee710d737..bd9f74bc1 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.tsx +++ b/src/app/(main)/websites/[id]/realtime/Realtime.tsx @@ -49,7 +49,7 @@ export function Realtime({ websiteId }) { timestamp, })); } - }, [data, currentData]); + }, [data]); const realtimeData: RealtimeData = useMemo(() => { if (!currentData) { diff --git a/src/app/share/[...id]/Header.tsx b/src/app/share/[...id]/Header.tsx index 41e93f52e..2b82908df 100644 --- a/src/app/share/[...id]/Header.tsx +++ b/src/app/share/[...id]/Header.tsx @@ -19,8 +19,8 @@ export function Header() {
- - + +
From cad719fd235f400d78fe0c4459864498b411f669 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 10 Dec 2023 02:02:24 -0800 Subject: [PATCH 25/28] Added search to metrics table. --- .../(main)/websites/[id]/WebsiteDetails.tsx | 4 +- ...ule.css => WebsiteExpandedView.module.css} | 0 ...teMenuView.tsx => WebsiteExpandedView.tsx} | 11 ++--- src/components/hooks/useDateRange.ts | 2 +- src/components/hooks/useFormat.ts | 11 ++++- src/components/layout/SideNav.tsx | 2 +- src/components/metrics/CitiesTable.tsx | 4 +- .../metrics/MetricsTable.module.css | 20 +++++++++ src/components/metrics/MetricsTable.tsx | 38 +++++++++++++--- src/components/metrics/OSTable.tsx | 9 ++-- src/components/metrics/PagesTable.tsx | 25 +++++------ .../metrics/QueryParametersTable.tsx | 45 +++++++++---------- 12 files changed, 111 insertions(+), 60 deletions(-) rename src/app/(main)/websites/[id]/{WebsiteMenuView.module.css => WebsiteExpandedView.module.css} (100%) rename src/app/(main)/websites/[id]/{WebsiteMenuView.tsx => WebsiteExpandedView.tsx} (93%) diff --git a/src/app/(main)/websites/[id]/WebsiteDetails.tsx b/src/app/(main)/websites/[id]/WebsiteDetails.tsx index 4d3a18e79..7d8d2d999 100644 --- a/src/app/(main)/websites/[id]/WebsiteDetails.tsx +++ b/src/app/(main)/websites/[id]/WebsiteDetails.tsx @@ -6,7 +6,7 @@ import FilterTags from 'components/metrics/FilterTags'; import useNavigation from 'components/hooks/useNavigation'; import { useWebsite } from 'components/hooks'; import WebsiteChart from './WebsiteChart'; -import WebsiteMenuView from './WebsiteMenuView'; +import WebsiteExpandedView from './WebsiteExpandedView'; import WebsiteHeader from './WebsiteHeader'; import WebsiteMetricsBar from './WebsiteMetricsBar'; import WebsiteTableView from './WebsiteTableView'; @@ -34,7 +34,7 @@ export default function WebsiteDetails({ websiteId }: { websiteId: string }) { {website && ( <> {!view && } - {view && } + {view && } )} diff --git a/src/app/(main)/websites/[id]/WebsiteMenuView.module.css b/src/app/(main)/websites/[id]/WebsiteExpandedView.module.css similarity index 100% rename from src/app/(main)/websites/[id]/WebsiteMenuView.module.css rename to src/app/(main)/websites/[id]/WebsiteExpandedView.module.css diff --git a/src/app/(main)/websites/[id]/WebsiteMenuView.tsx b/src/app/(main)/websites/[id]/WebsiteExpandedView.tsx similarity index 93% rename from src/app/(main)/websites/[id]/WebsiteMenuView.tsx rename to src/app/(main)/websites/[id]/WebsiteExpandedView.tsx index 670ea469d..e97cd002c 100644 --- a/src/app/(main)/websites/[id]/WebsiteMenuView.tsx +++ b/src/app/(main)/websites/[id]/WebsiteExpandedView.tsx @@ -15,7 +15,7 @@ import SideNav from 'components/layout/SideNav'; import useNavigation from 'components/hooks/useNavigation'; import useMessages from 'components/hooks/useMessages'; import LinkButton from 'components/common/LinkButton'; -import styles from './WebsiteMenuView.module.css'; +import styles from './WebsiteExpandedView.module.css'; const views = { url: PagesTable, @@ -33,7 +33,7 @@ const views = { query: QueryParametersTable, }; -export default function WebsiteMenuView({ +export default function WebsiteExpandedView({ websiteId, websiteDomain, }: { @@ -113,11 +113,11 @@ export default function WebsiteMenuView({ const DetailsComponent = views[view] || (() => null); - const handleChange = view => { + const handleChange = (view: any) => { router.push(makeUrl({ view })); }; - const renderValue = value => items.find(({ key }) => key === value)?.label; + const renderValue = (value: string) => items.find(({ key }) => key === value)?.label; return (
@@ -146,9 +146,10 @@ export default function WebsiteMenuView({ websiteDomain={websiteDomain} limit={false} animate={false} - showFilters={true} virtualize={true} itemCount={25} + allowFilter={true} + allowSearch={true} />
diff --git a/src/components/hooks/useDateRange.ts b/src/components/hooks/useDateRange.ts index d8a493316..efaa717fc 100644 --- a/src/components/hooks/useDateRange.ts +++ b/src/components/hooks/useDateRange.ts @@ -46,7 +46,7 @@ export function useDateRange(websiteId?: string) { }; return [dateRange, saveDateRange] as [ - { startDate: Date; endDate: Date }, + { startDate: Date; endDate: Date; modified?: number }, (value: string | DateRange) => void, ]; } diff --git a/src/components/hooks/useFormat.ts b/src/components/hooks/useFormat.ts index f804eb728..06585e497 100644 --- a/src/components/hooks/useFormat.ts +++ b/src/components/hooks/useFormat.ts @@ -18,14 +18,19 @@ export function useFormat() { }; const formatRegion = (value: string): string => { - return regions[value] ? regions[value] : value; + const [country] = value.split('-'); + return regions[value] ? `${regions[value]}, ${countryNames[country]}` : value; + }; + + const formatCity = (value: string, country?: string): string => { + return `${value}, ${countryNames[country]}`; }; const formatDevice = (value: string): string => { return formatMessage(labels[value] || labels.unknown); }; - const formatValue = (value: string, type: string): string => { + const formatValue = (value: string, type: string, data?: { [key: string]: any }): string => { switch (type) { case 'browser': return formatBrowser(value); @@ -33,6 +38,8 @@ export function useFormat() { return formatCountry(value); case 'region': return formatRegion(value); + case 'city': + return formatCity(value, data?.country); case 'device': return formatDevice(value); default: diff --git a/src/components/layout/SideNav.tsx b/src/components/layout/SideNav.tsx index f38bdba07..0b5c98562 100644 --- a/src/components/layout/SideNav.tsx +++ b/src/components/layout/SideNav.tsx @@ -9,7 +9,7 @@ export interface SideNavProps { items: any[]; shallow?: boolean; scroll?: boolean; - className?: boolean; + className?: string; onSelect?: () => void; } diff --git a/src/components/metrics/CitiesTable.tsx b/src/components/metrics/CitiesTable.tsx index 69b899628..067e07e93 100644 --- a/src/components/metrics/CitiesTable.tsx +++ b/src/components/metrics/CitiesTable.tsx @@ -11,8 +11,8 @@ export function CitiesTable(props: MetricsTableProps) { const countryNames = useCountryNames(locale); const renderLabel = (city: string, country: string) => { - const name = countryNames[country]; - return name ? `${city}, ${name}` : city; + const countryName = countryNames[country]; + return countryName ? `${city}, ${countryName}` : city; }; const renderLink = ({ x: city, country }) => { diff --git a/src/components/metrics/MetricsTable.module.css b/src/components/metrics/MetricsTable.module.css index c00e4356c..f04d9ae43 100644 --- a/src/components/metrics/MetricsTable.module.css +++ b/src/components/metrics/MetricsTable.module.css @@ -6,13 +6,33 @@ flex: 1; } +.actions { + display: flex; + gap: 20px; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +} + .footer { display: flex; justify-content: center; } +.search { + max-width: 300px; +} + @media only screen and (max-width: 992px) { .container { min-height: auto; } + + .actions { + flex-direction: column; + } + + .search { + max-width: 100%; + } } diff --git a/src/components/metrics/MetricsTable.tsx b/src/components/metrics/MetricsTable.tsx index d4ad793d6..48beac687 100644 --- a/src/components/metrics/MetricsTable.tsx +++ b/src/components/metrics/MetricsTable.tsx @@ -1,5 +1,5 @@ -import { useMemo } from 'react'; -import { Loading, Icon, Text } from 'react-basics'; +import { ReactNode, useMemo, useState } from 'react'; +import { Loading, Icon, Text, SearchField } from 'react-basics'; import classNames from 'classnames'; import useApi from 'components/hooks/useApi'; import { percentFilter } from 'lib/filters'; @@ -12,6 +12,7 @@ import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; import Icons from 'components/icons'; import useMessages from 'components/hooks/useMessages'; import useLocale from 'components/hooks/useLocale'; +import useFormat from 'components//hooks/useFormat'; import styles from './MetricsTable.module.css'; export interface MetricsTableProps extends ListTableProps { @@ -22,6 +23,9 @@ export interface MetricsTableProps extends ListTableProps { limit?: number; delay?: number; onDataLoad?: (data: any) => void; + onSearch?: (search: string) => void; + allowSearch?: boolean; + children?: ReactNode; } export function MetricsTable({ @@ -32,8 +36,12 @@ export function MetricsTable({ limit, onDataLoad, delay = null, + allowSearch = false, + children, ...props }: MetricsTableProps) { + const [search, setSearch] = useState(''); + const { formatValue } = useFormat(); const [{ startDate, endDate, modified }] = useDateRange(websiteId); const { makeUrl, @@ -42,7 +50,6 @@ export function MetricsTable({ const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); const { dir } = useLocale(); - const { data, isLoading, isFetched, error } = useQuery({ queryKey: [ 'websites:metrics', @@ -94,24 +101,43 @@ export function MetricsTable({ } } + if (search) { + items = items.filter(({ x, ...data }) => { + const value = formatValue(x, type, data); + + return value.toLowerCase().includes(search.toLowerCase()); + }); + } + items = percentFilter(items); if (limit) { - items = items.filter((e, i) => i < limit); + items = items.slice(0, limit - 1); } return items; } return []; - }, [data, error, dataFilter, limit]); + }, [data, dataFilter, search, limit, formatValue, type]); return (
- {!data && isLoading && !isFetched && } {error && } +
+ {allowSearch && ( + + )} + {children} +
{data && !error && ( )} + {!data && isLoading && !isFetched && }
{data && !error && limit && ( diff --git a/src/components/metrics/OSTable.tsx b/src/components/metrics/OSTable.tsx index c39cba226..102bafd36 100644 --- a/src/components/metrics/OSTable.tsx +++ b/src/components/metrics/OSTable.tsx @@ -1,4 +1,4 @@ -import MetricsTable from './MetricsTable'; +import MetricsTable, { MetricsTableProps } from './MetricsTable'; import FilterLink from 'components/common/FilterLink'; import useMessages from 'components/hooks/useMessages'; @@ -8,7 +8,7 @@ const names = { 'Sun OS': 'SunOS', }; -export function OSTable({ websiteId, limit }: { websiteId: string; limit?: number }) { +export function OSTable(props: MetricsTableProps) { const { formatMessage, labels } = useMessages(); function renderLink({ x: os }) { @@ -28,12 +28,11 @@ export function OSTable({ websiteId, limit }: { websiteId: string; limit?: numbe return ( ); } diff --git a/src/components/metrics/PagesTable.tsx b/src/components/metrics/PagesTable.tsx index 236764676..11379a2e8 100644 --- a/src/components/metrics/PagesTable.tsx +++ b/src/components/metrics/PagesTable.tsx @@ -6,10 +6,10 @@ import useNavigation from 'components/hooks/useNavigation'; import { emptyFilter } from 'lib/filters'; export interface PagesTableProps extends MetricsTableProps { - showFilters?: boolean; + allowFilter?: boolean; } -export function PagesTable({ showFilters, ...props }: PagesTableProps) { +export function PagesTable({ allowFilter, ...props }: PagesTableProps) { const { router, makeUrl, @@ -37,17 +37,16 @@ export function PagesTable({ showFilters, ...props }: PagesTableProps) { }; return ( - <> - {showFilters && } - - + + {allowFilter && } + ); } diff --git a/src/components/metrics/QueryParametersTable.tsx b/src/components/metrics/QueryParametersTable.tsx index 65cac6640..904894603 100644 --- a/src/components/metrics/QueryParametersTable.tsx +++ b/src/components/metrics/QueryParametersTable.tsx @@ -13,9 +13,9 @@ const filters = { }; export function QueryParametersTable({ - showFilters, + allowFilter, ...props -}: { showFilters: boolean } & MetricsTableProps) { +}: { allowFilter: boolean } & MetricsTableProps) { const [filter, setFilter] = useState(FILTER_COMBINED); const { formatMessage, labels } = useMessages(); @@ -28,27 +28,26 @@ export function QueryParametersTable({ ]; return ( - <> - {showFilters && } - - filter === FILTER_RAW ? ( - x - ) : ( -
-
{safeDecodeURI(p)}
-
{safeDecodeURI(v)}
-
- ) - } - delay={0} - /> - + + filter === FILTER_RAW ? ( + x + ) : ( +
+
{safeDecodeURI(p)}
+
{safeDecodeURI(v)}
+
+ ) + } + delay={0} + > + {allowFilter && } +
); } From 765874731d51337c7bdac449e11176e6bc8af2e0 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 10 Dec 2023 20:12:13 -0800 Subject: [PATCH 26/28] Added search to real-time activity log. --- .../[id]/realtime/RealtimeLog.module.css | 22 +++++++++ .../websites/[id]/realtime/RealtimeLog.tsx | 49 +++++++++++++++---- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeLog.module.css b/src/app/(main)/websites/[id]/realtime/RealtimeLog.module.css index f400cc1bc..e9c0fc1b5 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeLog.module.css +++ b/src/app/(main)/websites/[id]/realtime/RealtimeLog.module.css @@ -66,3 +66,25 @@ .row .link:hover { color: var(--primary400); } + +.search { + max-width: 300px; +} + +.actions { + display: flex; + gap: 20px; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +} + +@media only screen and (max-width: 992px) { + .actions { + flex-direction: column; + } + + .search { + max-width: 100%; + } +} diff --git a/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx b/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx index 7cae0abca..5293c1f06 100644 --- a/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx +++ b/src/app/(main)/websites/[id]/realtime/RealtimeLog.tsx @@ -1,18 +1,19 @@ import { useMemo, useState } from 'react'; -import { StatusLight, Icon, Text } from 'react-basics'; +import { StatusLight, Icon, Text, SearchField } from 'react-basics'; import { FixedSizeList } from 'react-window'; +import { format } from 'date-fns'; import thenby from 'thenby'; +import { safeDecodeURI } from 'next-basics'; import FilterButtons from 'components/common/FilterButtons'; import Empty from 'components/common/Empty'; import useLocale from 'components/hooks/useLocale'; import useCountryNames from 'components/hooks/useCountryNames'; +import Icons from 'components/icons'; +import useMessages from 'components/hooks/useMessages'; +import useFormat from 'components//hooks/useFormat'; import { BROWSERS } from 'lib/constants'; import { stringToColor } from 'lib/format'; -import { safeDecodeURI } from 'next-basics'; -import Icons from 'components/icons'; import styles from './RealtimeLog.module.css'; -import useMessages from 'components/hooks/useMessages'; -import { format } from 'date-fns'; const TYPE_ALL = 'all'; const TYPE_PAGEVIEW = 'pageview'; @@ -26,7 +27,9 @@ const icons = { }; export function RealtimeLog({ data, websiteDomain }) { + const [search, setSearch] = useState(''); const { formatMessage, labels, messages, FormattedMessage } = useMessages(); + const { formatValue } = useFormat(); const { locale } = useLocale(); const countryNames = useCountryNames(locale); const [filter, setFilter] = useState(TYPE_ALL); @@ -56,7 +59,15 @@ export function RealtimeLog({ data, websiteDomain }) { const getIcon = ({ __type }) => icons[__type]; - const getDetail = log => { + const getDetail = (log: { + __type: any; + eventName: any; + urlPath: any; + browser: any; + os: any; + country: any; + device: any; + }) => { const { __type, eventName, urlPath: url, browser, os, country, device } = log; if (__type === TYPE_EVENT) { @@ -130,18 +141,38 @@ export function RealtimeLog({ data, websiteDomain }) { } const { pageviews, visitors, events } = data; - const logs = [...pageviews, ...visitors, ...events].sort(thenby.firstBy('createdAt', -1)); + let logs = [...pageviews, ...visitors, ...events].sort(thenby.firstBy('createdAt', -1)); + + if (search) { + logs = logs.filter(({ eventName, urlPath, browser, os, country, device }) => { + return [ + eventName, + urlPath, + os, + formatValue(browser, 'browser'), + formatValue(country, 'country'), + formatValue(device, 'device'), + ] + .filter(n => n) + .map(n => n.toLowerCase()) + .join('') + .includes(search.toLowerCase()); + }); + } if (filter !== TYPE_ALL) { return logs.filter(({ __type }) => __type === filter); } return logs; - }, [data, filter]); + }, [data, filter, formatValue, search]); return (
- +
+ + +
{formatMessage(labels.activityLog)}
{logs?.length === 0 && } From a851ebf1247ac65e6246a20b72bafd07f31c61d2 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 11 Dec 2023 00:15:26 -0800 Subject: [PATCH 27/28] Bump version 2.9.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index db10755c9..0f437c355 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "2.8.0", + "version": "2.9.0", "description": "A simple, fast, privacy-focused alternative to Google Analytics.", "author": "Mike Cao ", "license": "MIT", From 907685b96e0caae51846fc6dfe55d9ddfe16d7cd Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 12 Dec 2023 19:00:44 -0800 Subject: [PATCH 28/28] Added limit to metrics queries. --- .../(main)/websites/[id]/WebsiteExpandedView.tsx | 1 - src/components/metrics/MetricsTable.tsx | 1 + src/lib/prisma.ts | 6 +++++- src/lib/types.ts | 1 + src/pages/api/websites/[id]/metrics.ts | 7 +++++-- .../analytics/pageviews/getPageviewMetrics.ts | 14 ++++++++++---- .../analytics/sessions/getSessionMetrics.ts | 14 ++++++++++---- 7 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/app/(main)/websites/[id]/WebsiteExpandedView.tsx b/src/app/(main)/websites/[id]/WebsiteExpandedView.tsx index e97cd002c..9fb1b3f6f 100644 --- a/src/app/(main)/websites/[id]/WebsiteExpandedView.tsx +++ b/src/app/(main)/websites/[id]/WebsiteExpandedView.tsx @@ -144,7 +144,6 @@ export default function WebsiteExpandedView({ relationalQuery(...args), @@ -13,7 +13,12 @@ export async function getPageviewMetrics( }); } -async function relationalQuery(websiteId: string, column: string, filters: QueryFilters) { +async function relationalQuery( + websiteId: string, + column: string, + filters: QueryFilters, + limit: number = 100, +) { const { rawQuery, parseFilters } = prisma; const { filterQuery, joinSession, params } = await parseFilters( websiteId, @@ -42,7 +47,7 @@ async function relationalQuery(websiteId: string, column: string, filters: Query ${filterQuery} group by 1 order by 2 desc - limit 100 + limit ${limit} `, params, ); @@ -52,6 +57,7 @@ async function clickhouseQuery( websiteId: string, column: string, filters: QueryFilters, + limit: number = 100, ): Promise<{ x: string; y: number }[]> { const { rawQuery, parseFilters } = clickhouse; const { filterQuery, params } = await parseFilters(websiteId, { @@ -75,7 +81,7 @@ async function clickhouseQuery( ${filterQuery} group by x order by y desc - limit 100 + limit ${limit} `, params, ).then(a => { diff --git a/src/queries/analytics/sessions/getSessionMetrics.ts b/src/queries/analytics/sessions/getSessionMetrics.ts index 3573ac1e8..c6877a3f1 100644 --- a/src/queries/analytics/sessions/getSessionMetrics.ts +++ b/src/queries/analytics/sessions/getSessionMetrics.ts @@ -5,7 +5,7 @@ import { EVENT_TYPE, SESSION_COLUMNS } from 'lib/constants'; import { QueryFilters } from 'lib/types'; export async function getSessionMetrics( - ...args: [websiteId: string, column: string, filters: QueryFilters] + ...args: [websiteId: string, column: string, filters: QueryFilters, limit?: number] ) { return runQuery({ [PRISMA]: () => relationalQuery(...args), @@ -13,7 +13,12 @@ export async function getSessionMetrics( }); } -async function relationalQuery(websiteId: string, column: string, filters: QueryFilters) { +async function relationalQuery( + websiteId: string, + column: string, + filters: QueryFilters, + limit: number = 100, +) { const { parseFilters, rawQuery } = prisma; const { filterQuery, joinSession, params } = await parseFilters( websiteId, @@ -42,7 +47,7 @@ async function relationalQuery(websiteId: string, column: string, filters: Query group by 1 ${includeCountry ? ', 3' : ''} order by 2 desc - limit 100`, + limit ${limit}`, params, ); } @@ -51,6 +56,7 @@ async function clickhouseQuery( websiteId: string, column: string, filters: QueryFilters, + limit: number = 100, ): Promise<{ x: string; y: number }[]> { const { parseFilters, rawQuery } = clickhouse; const { filterQuery, params } = await parseFilters(websiteId, { @@ -73,7 +79,7 @@ async function clickhouseQuery( group by x ${includeCountry ? ', country' : ''} order by y desc - limit 100 + limit ${limit} `, params, ).then(a => {