diff --git a/src/components/common/DataTable.js b/src/components/common/DataTable.js
index 2662fa2c..94b27281 100644
--- a/src/components/common/DataTable.js
+++ b/src/components/common/DataTable.js
@@ -1,46 +1,46 @@
-import { createContext } from 'react';
-import { SearchField } from 'react-basics';
-import { useDataTable } from 'components/hooks/useDataTable';
+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 classNames from 'classnames';
-const DEFAULT_SEARCH_DELAY = 1000;
+const DEFAULT_SEARCH_DELAY = 600;
export const DataTableStyles = styles;
-export const DataTableContext = createContext(null);
-
export function DataTable({
+ data = {},
+ params = {},
+ setParams,
+ isLoading,
+ error,
searchDelay,
showSearch = true,
showPaging = true,
children,
- onChange,
}) {
const { formatMessage, labels, messages } = useMessages();
- const dataTable = useDataTable();
- const { query, setQuery, data, pageInfo, setPageInfo } = dataTable;
- const { page, pageSize, count } = pageInfo || {};
- const noResults = Boolean(query && data?.length === 0);
+ const { pageSize, count } = data;
+ const { query, page } = params;
+ const hasData = Boolean(!isLoading && data?.data?.length);
+ const noResults = Boolean(!isLoading && query && !hasData);
- const handleChange = value => {
- onChange?.({ query: value, page });
- };
-
- const handleSearch = value => {
- setQuery(value);
- handleChange(value);
+ const handleSearch = query => {
+ setParams({ ...params, query });
};
const handlePageChange = page => {
- setPageInfo(state => ({ ...state, page }));
+ setParams({ ...params, page });
};
+ if (error) {
+ return {formatMessage(messages.error)};
+ }
+
return (
-
- {showSearch && (
+ <>
+ {(hasData || query || isLoading) && showSearch && (
)}
- {noResults && }
- {children}
+
+ {hasData && typeof children === 'function' ? children(data) : children}
+ {isLoading && }
+ {!isLoading && !hasData && !query && (
+
+ )}
+ {noResults && }
+
{showPaging && (
)}
-
+ >
);
}
diff --git a/src/components/common/DataTable.module.css b/src/components/common/DataTable.module.css
index 883110da..b7426a7c 100644
--- a/src/components/common/DataTable.module.css
+++ b/src/components/common/DataTable.module.css
@@ -1,3 +1,12 @@
+.table {
+ grid-template-rows: repeat(auto-fit, max-content);
+}
+
+.table td {
+ align-items: center;
+ max-height: max-content;
+}
+
.search {
max-width: 300px;
margin: 20px 0;
@@ -8,10 +17,22 @@
gap: 5px;
}
+.body {
+ display: flex;
+ position: relative;
+}
+
.body td {
align-items: center;
}
.pager {
- margin-top: 20px;
+ margin: 20px 0;
+}
+
+.status {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 200px;
}
diff --git a/src/components/common/Pager.js b/src/components/common/Pager.js
index 3f94edb0..f35c2ab0 100644
--- a/src/components/common/Pager.js
+++ b/src/components/common/Pager.js
@@ -1,5 +1,5 @@
import classNames from 'classnames';
-import { Button, Flexbox, Icon, Icons } from 'react-basics';
+import { Button, Icon, Icons } from 'react-basics';
import useMessages from 'components/hooks/useMessages';
import styles from './Pager.module.css';
@@ -25,21 +25,25 @@ export function Pager({ page, pageSize, count, onPageChange, className }) {
}
return (
-
-
-
- {formatMessage(labels.pageOf, { current: page, total: maxPage })}
-
-
-
+
+
{formatMessage(labels.numberOfRecords, { x: count })}
+
+
+
+ {formatMessage(labels.pageOf, { current: page, total: maxPage })}
+
+
+
+
+
);
}
diff --git a/src/components/common/Pager.module.css b/src/components/common/Pager.module.css
index 9c22f597..0ed5e1f4 100644
--- a/src/components/common/Pager.module.css
+++ b/src/components/common/Pager.module.css
@@ -1,4 +1,27 @@
+.pager {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ align-items: center;
+}
+
+.nav {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
.text {
font-size: var(--font-size-md);
margin: 0 16px;
+ justify-content: center;
+}
+
+@media only screen and (max-width: 992px) {
+ .pager {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .nav {
+ justify-content: end;
+ }
}
diff --git a/src/components/hooks/useDataTable.js b/src/components/hooks/useDataTable.js
deleted file mode 100644
index 83aa3d68..00000000
--- a/src/components/hooks/useDataTable.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { useState } from 'react';
-import { usePaging } from 'components/hooks/usePaging';
-
-export function useDataTable(config = {}) {
- const { initialData, initialQuery, initialPageInfo } = config;
- const [data, setData] = useState(initialData ?? null);
- const [query, setQuery] = useState(initialQuery ?? '');
- const { pageInfo, setPageInfo } = usePaging(initialPageInfo);
-
- return { data, setData, query, setQuery, pageInfo, setPageInfo };
-}
-
-export default useDataTable;
diff --git a/src/components/hooks/useFilterQuery.js b/src/components/hooks/useFilterQuery.js
new file mode 100644
index 00000000..5dd9b3df
--- /dev/null
+++ b/src/components/hooks/useFilterQuery.js
@@ -0,0 +1,16 @@
+import { useState } from 'react';
+import { useApi } from 'components/hooks/useApi';
+
+export function useFilterQuery(key, fn, options) {
+ const [params, setParams] = useState({
+ query: '',
+ page: 1,
+ });
+ const { useQuery } = useApi();
+
+ const result = useQuery([...key, params], fn.bind(null, params), options);
+
+ return { ...result, params, setParams };
+}
+
+export default useFilterQuery;
diff --git a/src/components/hooks/usePaging.js b/src/components/hooks/usePaging.js
deleted file mode 100644
index 17c23153..00000000
--- a/src/components/hooks/usePaging.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { useState } from 'react';
-
-const DEFAULT_PAGE_INFO = { page: 1, pageSize: 10, total: 0 };
-
-export function usePaging(initialPageInfo) {
- const [pageInfo, setPageInfo] = useState(initialPageInfo ?? { ...DEFAULT_PAGE_INFO });
-
- return { pageInfo, setPageInfo };
-}
diff --git a/src/components/messages.js b/src/components/messages.js
index 7f432eb3..04a29a4c 100644
--- a/src/components/messages.js
+++ b/src/components/messages.js
@@ -193,6 +193,10 @@ export const labels = defineMessages({
pageOf: { id: 'label.page-of', defaultMessage: 'Page {current} of {total}' },
create: { id: 'label.create', defaultMessage: 'Create' },
search: { id: 'label.search', defaultMessage: 'Search' },
+ numberOfRecords: {
+ id: 'label.number-of-records',
+ defaultMessage: '{x} {x, plural, one {record} other {records}}',
+ },
});
export const messages = defineMessages({
diff --git a/src/components/pages/reports/ReportsPage.js b/src/components/pages/reports/ReportsPage.js
index bbb15a36..9a48d780 100644
--- a/src/components/pages/reports/ReportsPage.js
+++ b/src/components/pages/reports/ReportsPage.js
@@ -1,28 +1,39 @@
-import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
-import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader';
-import { useMessages, useReports } from 'components/hooks';
+import { useMessages, useApi } from 'components/hooks';
import Link from 'next/link';
import { Button, Icon, Icons, Text } from 'react-basics';
import ReportsTable from './ReportsTable';
+import useFilterQuery from 'components/hooks/useFilterQuery';
+import DataTable from 'components/common/DataTable';
+
+function useReports() {
+ const { get, del, useMutation } = useApi();
+ const { mutate } = useMutation(reportId => del(`/reports/${reportId}`));
+ const reports = useFilterQuery(['reports'], params => get(`/reports`, params));
+
+ const deleteReport = id => {
+ mutate(id, {
+ onSuccess: () => {
+ reports.refetch();
+ },
+ });
+ };
+
+ return { reports, deleteReport };
+}
export function ReportsPage() {
const { formatMessage, labels } = useMessages();
- const {
- reports,
- error,
- isLoading,
- deleteReport,
- filter,
- handleFilterChange,
- handlePageChange,
- handlePageSizeChange,
- } = useReports();
+ const { reports, deleteReport } = useReports();
- const hasData = (reports && reports?.data.length !== 0) || filter;
+ const handleDelete = async (id, callback) => {
+ await deleteReport(id);
+ await reports.refetch();
+ callback?.();
+ };
return (
-
+ <>
-
- {hasData && (
-
- )}
- {!hasData && }
-
+
+ {({ data }) => }
+
+ >
);
}
diff --git a/src/components/pages/reports/ReportsTable.js b/src/components/pages/reports/ReportsTable.js
index 52488c11..72b0c273 100644
--- a/src/components/pages/reports/ReportsTable.js
+++ b/src/components/pages/reports/ReportsTable.js
@@ -1,96 +1,74 @@
import ConfirmDeleteForm from 'components/common/ConfirmDeleteForm';
import LinkButton from 'components/common/LinkButton';
-import SettingsTable from 'components/common/SettingsTable';
import { useMessages } from 'components/hooks';
import useUser from 'components/hooks/useUser';
-import { useState } from 'react';
-import { Button, Flexbox, Icon, Icons, Modal, Text } from 'react-basics';
+import {
+ Button,
+ Flexbox,
+ GridColumn,
+ GridTable,
+ Icon,
+ Icons,
+ Modal,
+ ModalTrigger,
+ Text,
+} from 'react-basics';
import { REPORT_TYPES } from 'lib/constants';
-export function ReportsTable({
- data = [],
- onDelete = () => {},
- filterValue,
- onFilterChange,
- onPageChange,
- onPageSizeChange,
- showDomain,
-}) {
- const [report, setReport] = useState(null);
+export function ReportsTable({ data = [], onDelete, showDomain }) {
const { formatMessage, labels } = useMessages();
const { user } = useUser();
- const domainColumn = [
- {
- name: 'domain',
- label: formatMessage(labels.domain),
- },
- ];
-
- const columns = [
- { name: 'name', label: formatMessage(labels.name) },
- { name: 'description', label: formatMessage(labels.description) },
- { name: 'type', label: formatMessage(labels.type) },
- ...(showDomain ? domainColumn : []),
- { name: 'action', label: ' ' },
- ];
-
- const cellRender = (row, data, key) => {
- if (key === 'type') {
- return formatMessage(
- labels[Object.keys(REPORT_TYPES).find(key => REPORT_TYPES[key] === row.type)],
- );
- }
- return data[key];
- };
-
- const handleConfirm = () => {
- onDelete(report.id);
+ const handleConfirm = (id, callback) => {
+ onDelete?.(id, callback);
};
return (
- <>
-
+
+
+
+
{row => {
- const { id, userId: reportOwnerId, website } = row;
- if (showDomain) {
- row.domain = website.domain;
- }
-
+ return formatMessage(
+ labels[Object.keys(REPORT_TYPES).find(key => REPORT_TYPES[key] === row.type)],
+ );
+ }}
+
+ {showDomain && (
+
+ {row => row.website.domain}
+
+ )}
+
+ {row => {
+ const { id, name, userId, website } = row;
return (
{formatMessage(labels.view)}
- {!showDomain || user.id === reportOwnerId || user.id === website?.userId}
-
+ {(user.id === userId || user.id === website?.userId) && (
+
+
+
+ {close => (
+
+ )}
+
+
+ )}
);
}}
-
- {report && (
-
- setReport(null)}
- />
-
- )}
- >
+
+
);
}
diff --git a/src/components/pages/settings/websites/WebsitesList.js b/src/components/pages/settings/websites/WebsitesList.js
index 4761ad0a..0dd3aa77 100644
--- a/src/components/pages/settings/websites/WebsitesList.js
+++ b/src/components/pages/settings/websites/WebsitesList.js
@@ -1,13 +1,13 @@
-import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader';
import WebsiteAddForm from 'components/pages/settings/websites/WebsiteAddForm';
import WebsitesTable from 'components/pages/settings/websites/WebsitesTable';
-import useApi from 'components/hooks/useApi';
import useMessages from 'components/hooks/useMessages';
import useUser from 'components/hooks/useUser';
import { ROLES } from 'lib/constants';
import { Button, Icon, Icons, Modal, ModalTrigger, Text, useToasts } from 'react-basics';
-import { useRef, useState } from 'react';
+import useApi from 'components/hooks/useApi';
+import DataTable from 'components/common/DataTable';
+import useFilterQuery from 'components/hooks/useFilterQuery';
export function WebsitesList({
showTeam,
@@ -18,13 +18,10 @@ export function WebsitesList({
}) {
const { formatMessage, labels, messages } = useMessages();
const { user } = useUser();
- const [params, setParams] = useState({});
- const { get, useQuery } = useApi();
- const count = useRef(0);
- const q = useQuery(
- ['websites', includeTeams, onlyTeams, params],
- () => {
- count.current += 1;
+ const { get } = useApi();
+ const filterQuery = useFilterQuery(
+ ['websites', { includeTeams, onlyTeams }],
+ params => {
return get(`/users/${user?.id}/websites`, {
includeTeams,
onlyTeams,
@@ -33,46 +30,41 @@ export function WebsitesList({
},
{ enabled: !!user },
);
- const { data, refetch, isLoading, error } = q;
+ const { refetch } = filterQuery;
const { showToast } = useToasts();
- const handleChange = params => {
- setParams(params);
- };
-
const handleSave = async () => {
await refetch();
showToast({ message: formatMessage(messages.saved), variant: 'success' });
};
const addButton = (
- <>
- {user.role !== ROLES.viewOnly && (
-
-
-
- {close => }
-
-
- )}
- >
+
+
+
+ {close => }
+
+
);
return (
-
- {showHeader && {addButton}}
-
-
+ <>
+ {showHeader && (
+
+ {user.role !== ROLES.viewOnly && addButton}
+
+ )}
+
+ {({ data }) => (
+
+ )}
+
+ >
);
}
diff --git a/src/components/pages/settings/websites/WebsitesTable.js b/src/components/pages/settings/websites/WebsitesTable.js
index 12f94200..3739de64 100644
--- a/src/components/pages/settings/websites/WebsitesTable.js
+++ b/src/components/pages/settings/websites/WebsitesTable.js
@@ -1,154 +1,58 @@
import Link from 'next/link';
-import { Button, Text, Icon, Icons, GridTable, GridColumn } from 'react-basics';
-import SettingsTable from 'components/common/SettingsTable';
-import Empty from 'components/common/Empty';
+import { Button, Text, Icon, Icons, GridTable, GridColumn, Flexbox } from 'react-basics';
import useMessages from 'components/hooks/useMessages';
import useUser from 'components/hooks/useUser';
-import DataTable, { DataTableStyles } from 'components/common/DataTable';
-export function WebsitesTable({
- data = [],
- showTeam,
- showEditButton,
- openExternal = false,
- onChange,
-}) {
+export function WebsitesTable({ data = [], showTeam, showEditButton }) {
const { formatMessage, labels } = useMessages();
const { user } = useUser();
- const showTable = data.length !== 0;
-
return (
-
- {showTable && (
-
-
-
- {showTeam && (
-
- {row => row.teamWebsite[0]?.team.name}
-
- )}
- {showTeam && (
-
- {row => row.user.username}
-
- )}
-
- {row => {
- const {
- id,
- user: { id: ownerId },
- } = row;
-
- return (
- <>
- {showEditButton && (!showTeam || ownerId === user.id) && (
-
-
-
- )}
-
-
-
- >
- );
- }}
-
-
+
+
+
+ {showTeam && (
+
+ {row => row.teamWebsite[0]?.team.name}
+
)}
-
- );
-}
+ {showTeam && (
+
+ {row => row.user.username}
+
+ )}
+
+ {row => {
+ const {
+ id,
+ user: { id: ownerId },
+ } = row;
-export function WebsitesTable2({
- data = [],
- filterValue,
- onFilterChange,
- onPageChange,
- onPageSizeChange,
- showTeam,
- showEditButton,
- openExternal = false,
-}) {
- const { formatMessage, labels } = useMessages();
- const { user } = useUser();
-
- const showTable = data && (filterValue || data?.data?.length !== 0);
-
- const teamColumns = [
- { name: 'teamName', label: formatMessage(labels.teamName) },
- { name: 'owner', label: formatMessage(labels.owner) },
- ];
-
- const columns = [
- { name: 'name', label: formatMessage(labels.name) },
- { name: 'domain', label: formatMessage(labels.domain) },
- ...(showTeam ? teamColumns : []),
- { name: 'action', label: ' ' },
- ];
-
- return (
- <>
- {showTable && (
-
- {row => {
- const {
- id,
- teamWebsite,
- user: { username, id: ownerId },
- } = row;
- if (showTeam) {
- row.teamName = teamWebsite[0]?.team.name;
- row.owner = username;
- }
-
- return (
- <>
- {showEditButton && (!showTeam || ownerId === user.id) && (
-
-
-
- )}
-
+ return (
+
+ {showEditButton && (!showTeam || ownerId === user.id) && (
+
- >
- );
- }}
-
- )}
- {!showTable && }
- >
+ )}
+
+
+
+
+ );
+ }}
+
+
);
}
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index 888c1484..9ea76d93 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -19,6 +19,7 @@ export const DEFAULT_ANIMATION_DURATION = 300;
export const DEFAULT_DATE_RANGE = '24hour';
export const DEFAULT_WEBSITE_LIMIT = 10;
export const DEFAULT_RESET_DATE = '2000-01-01';
+export const DEFAULT_PAGE_SIZE = 10;
export const REALTIME_RANGE = 30;
export const REALTIME_INTERVAL = 5000;
@@ -30,22 +31,6 @@ export const FILTER_RANGE = 'filter-range';
export const FILTER_REFERRERS = 'filter-referrers';
export const FILTER_PAGES = 'filter-pages';
-export const USER_FILTER_TYPES = {
- all: 'All',
- username: 'Username',
-} as const;
-export const WEBSITE_FILTER_TYPES = { all: 'All', name: 'Name', domain: 'Domain' } as const;
-export const TEAM_FILTER_TYPES = { all: 'All', name: 'Name', 'user:username': 'Owner' } as const;
-export const REPORT_FILTER_TYPES = {
- all: 'All',
- name: 'Name',
- description: 'Description',
- type: 'Type',
- 'user:username': 'Username',
- 'website:name': 'Website Name',
- 'website:domain': 'Website Domain',
-} as const;
-
export const EVENT_COLUMNS = ['url', 'referrer', 'title', 'query', 'event'];
export const SESSION_COLUMNS = [
diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts
index 59638dbd..f75ea1fe 100644
--- a/src/lib/prisma.ts
+++ b/src/lib/prisma.ts
@@ -1,11 +1,11 @@
+import { Prisma } from '@prisma/client';
import prisma from '@umami/prisma-client';
import moment from 'moment-timezone';
import { MYSQL, POSTGRESQL, getDatabaseType } from 'lib/db';
-import { FILTER_COLUMNS, SESSION_COLUMNS, OPERATORS } from './constants';
+import { FILTER_COLUMNS, SESSION_COLUMNS, OPERATORS, DEFAULT_PAGE_SIZE } from './constants';
import { loadWebsite } from './load';
import { maxDate } from './date';
import { QueryFilters, QueryOptions, SearchFilter } from './types';
-import { Prisma } from '@prisma/client';
const MYSQL_DATE_FORMATS = {
minute: '%Y-%m-%d %H:%i:00',
@@ -171,7 +171,7 @@ async function rawQuery(sql: string, data: object): Promise {
return prisma.rawQuery(query, params);
}
-function getPageFilters(filters: SearchFilter): [
+function getPageFilters(filters: SearchFilter): [
{
orderBy: {
[x: string]: string;
@@ -185,7 +185,7 @@ function getPageFilters(filters: SearchFilter): [
orderBy: string;
},
] {
- const { pageSize = 10, page = 1, orderBy } = filters || {};
+ const { page = 1, pageSize = DEFAULT_PAGE_SIZE, orderBy } = filters || {};
return [
{
@@ -198,7 +198,7 @@ function getPageFilters(filters: SearchFilter): [
],
}),
},
- { pageSize, page: +page, orderBy },
+ { page: +page, pageSize, orderBy },
];
}
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 58e6aa9e..98fbc29b 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -5,12 +5,8 @@ import {
EVENT_TYPE,
KAFKA_TOPIC,
PERMISSIONS,
- REPORT_FILTER_TYPES,
REPORT_TYPES,
ROLES,
- TEAM_FILTER_TYPES,
- USER_FILTER_TYPES,
- WEBSITE_FILTER_TYPES,
} from './constants';
import * as yup from 'yup';
import { TIME_UNIT } from './date';
@@ -27,46 +23,42 @@ export type DynamicDataType = ObjectValues;
export type KafkaTopic = ObjectValues;
export type ReportType = ObjectValues;
-export type ReportSearchFilterType = ObjectValues;
-export type UserSearchFilterType = ObjectValues;
-export type WebsiteSearchFilterType = ObjectValues;
-export type TeamSearchFilterType = ObjectValues;
-
-export interface WebsiteSearchFilter extends SearchFilter {
+export interface WebsiteSearchFilter extends SearchFilter {
userId?: string;
teamId?: string;
includeTeams?: boolean;
onlyTeams?: boolean;
}
-export interface UserSearchFilter extends SearchFilter {
+export interface UserSearchFilter extends SearchFilter {
teamId?: string;
}
-export interface TeamSearchFilter extends SearchFilter {
+export interface TeamSearchFilter extends SearchFilter {
userId?: string;
}
-export interface ReportSearchFilter extends SearchFilter {
+export interface ReportSearchFilter extends SearchFilter {
userId?: string;
websiteId?: string;
includeTeams?: boolean;
}
-export interface SearchFilter {
+export interface SearchFilter {
query?: string;
page?: number;
pageSize?: number;
orderBy?: string;
- data?: T;
+ sortDescending?: boolean;
}
export interface FilterResult {
data: T;
count: number;
- pageSize: number;
page: number;
+ pageSize: number;
orderBy?: string;
+ sortDescending?: boolean;
}
export interface DynamicData {
diff --git a/src/pages/api/me/teams.ts b/src/pages/api/me/teams.ts
index 131cb262..14602157 100644
--- a/src/pages/api/me/teams.ts
+++ b/src/pages/api/me/teams.ts
@@ -1,12 +1,12 @@
import { useCors, useValidate } from 'lib/middleware';
-import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types';
+import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
import { pageInfo } from 'lib/schema';
import { NextApiResponse } from 'next';
import { methodNotAllowed } from 'next-basics';
import userTeams from 'pages/api/users/[id]/teams';
import * as yup from 'yup';
-export interface MyTeamsRequestQuery extends SearchFilter {
+export interface MyTeamsRequestQuery extends SearchFilter {
id: string;
}
diff --git a/src/pages/api/me/websites.ts b/src/pages/api/me/websites.ts
index 749af316..ec6a5556 100644
--- a/src/pages/api/me/websites.ts
+++ b/src/pages/api/me/websites.ts
@@ -1,12 +1,12 @@
import { useAuth, useCors, useValidate } from 'lib/middleware';
-import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types';
+import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
import { pageInfo } from 'lib/schema';
import { NextApiResponse } from 'next';
import { methodNotAllowed } from 'next-basics';
import userWebsites from 'pages/api/users/[id]/websites';
import * as yup from 'yup';
-export interface MyWebsitesRequestQuery extends SearchFilter {
+export interface MyWebsitesRequestQuery extends SearchFilter {
id: string;
}
diff --git a/src/pages/api/reports/index.ts b/src/pages/api/reports/index.ts
index 3c975b76..911d729c 100644
--- a/src/pages/api/reports/index.ts
+++ b/src/pages/api/reports/index.ts
@@ -1,13 +1,13 @@
import { uuid } from 'lib/crypto';
import { useAuth, useCors, useValidate } from 'lib/middleware';
-import { NextApiRequestQueryBody, ReportSearchFilterType, SearchFilter } from 'lib/types';
+import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
import { pageInfo } from 'lib/schema';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok } from 'next-basics';
import { createReport, getReportsByUserId } from 'queries';
import * as yup from 'yup';
-export interface ReportsRequestQuery extends SearchFilter {}
+export interface ReportsRequestQuery extends SearchFilter {}
export interface ReportRequestBody {
websiteId: string;
@@ -52,12 +52,11 @@ export default async (
} = req.auth;
if (req.method === 'GET') {
- const { page, filter, pageSize } = req.query;
+ const { page, query } = req.query;
const data = await getReportsByUserId(userId, {
page,
- filter,
- pageSize: +pageSize || undefined,
+ query,
includeTeams: true,
});
diff --git a/src/pages/api/scripts/telemetry.js b/src/pages/api/scripts/telemetry.js
index 954d5058..6a249de0 100644
--- a/src/pages/api/scripts/telemetry.js
+++ b/src/pages/api/scripts/telemetry.js
@@ -1,18 +1,23 @@
+import { ok } from 'next-basics';
import { CURRENT_VERSION, TELEMETRY_PIXEL } from 'lib/constants';
export default function handler(req, res) {
- res.setHeader('content-type', 'text/javascript');
+ if (process.env.NODE_ENV === 'production') {
+ res.setHeader('content-type', 'text/javascript');
- if (process.env.DISABLE_TELEMETRY) {
- return res.send('/* telemetry disabled */');
- }
+ if (process.env.DISABLE_TELEMETRY) {
+ return res.send('/* telemetry disabled */');
+ }
- const script = `
+ const script = `
(()=>{const i=document.createElement('img');
i.setAttribute('src','${TELEMETRY_PIXEL}?v=${CURRENT_VERSION}');
i.setAttribute('style','width:0;height:0;position:absolute;pointer-events:none;');
document.body.appendChild(i);})();
`;
- return res.send(script.replace(/\s\s+/g, ''));
+ return res.send(script.replace(/\s\s+/g, ''));
+ }
+
+ return ok(res);
}
diff --git a/src/pages/api/teams/[id]/users/index.ts b/src/pages/api/teams/[id]/users/index.ts
index d0efba25..1c9e8352 100644
--- a/src/pages/api/teams/[id]/users/index.ts
+++ b/src/pages/api/teams/[id]/users/index.ts
@@ -1,11 +1,11 @@
import { canViewTeam } from 'lib/auth';
import { useAuth } from 'lib/middleware';
-import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types';
+import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getUsersByTeamId } from 'queries';
-export interface TeamUserRequestQuery extends SearchFilter {
+export interface TeamUserRequestQuery extends SearchFilter {
id: string;
}
@@ -27,12 +27,11 @@ export default async (
return unauthorized(res);
}
- const { page, filter, pageSize } = req.query;
+ const { query, page } = req.query;
const users = await getUsersByTeamId(teamId, {
+ query,
page,
- filter,
- pageSize: +pageSize || undefined,
});
return ok(res, users);
diff --git a/src/pages/api/teams/[id]/websites/index.ts b/src/pages/api/teams/[id]/websites/index.ts
index 23c7390b..4d14c4e9 100644
--- a/src/pages/api/teams/[id]/websites/index.ts
+++ b/src/pages/api/teams/[id]/websites/index.ts
@@ -1,14 +1,14 @@
import * as yup from 'yup';
import { canViewTeam } from 'lib/auth';
import { useAuth, useValidate } from 'lib/middleware';
-import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types';
+import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
import { pageInfo } from 'lib/schema';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getWebsitesByTeamId } from 'queries';
import { createTeamWebsites } from 'queries/admin/teamWebsite';
-export interface TeamWebsiteRequestQuery extends SearchFilter {
+export interface TeamWebsiteRequestQuery extends SearchFilter {
id: string;
}
@@ -43,13 +43,7 @@ export default async (
return unauthorized(res);
}
- const { page, filter, pageSize } = req.query;
-
- const websites = await getWebsitesByTeamId(teamId, {
- page,
- filter,
- pageSize: +pageSize || undefined,
- });
+ const websites = await getWebsitesByTeamId(teamId, { ...req.query });
return ok(res, websites);
}
diff --git a/src/pages/api/teams/index.ts b/src/pages/api/teams/index.ts
index 084d09a2..74cb532e 100644
--- a/src/pages/api/teams/index.ts
+++ b/src/pages/api/teams/index.ts
@@ -2,19 +2,19 @@ import { Team } from '@prisma/client';
import { canCreateTeam } from 'lib/auth';
import { uuid } from 'lib/crypto';
import { useAuth, useValidate } from 'lib/middleware';
-import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types';
+import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
import { pageInfo } from 'lib/schema';
import { NextApiResponse } from 'next';
import { getRandomChars, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createTeam, getTeamsByUserId } from 'queries';
import * as yup from 'yup';
-export interface TeamsRequestQuery extends SearchFilter {}
+export interface TeamsRequestQuery extends SearchFilter {}
export interface TeamsRequestBody {
name: string;
}
-export interface MyTeamsRequestQuery extends SearchFilter {}
+export interface MyTeamsRequestQuery extends SearchFilter {}
const schema = {
GET: yup.object().shape({
diff --git a/src/pages/api/users/[id]/teams.ts b/src/pages/api/users/[id]/teams.ts
index 34a31a0e..f9d7f5ea 100644
--- a/src/pages/api/users/[id]/teams.ts
+++ b/src/pages/api/users/[id]/teams.ts
@@ -1,12 +1,12 @@
import * as yup from 'yup';
import { useAuth, useCors, useValidate } from 'lib/middleware';
-import { NextApiRequestQueryBody, SearchFilter, TeamSearchFilterType } from 'lib/types';
+import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
import { pageInfo } from 'lib/schema';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getTeamsByUserId } from 'queries';
-export interface UserTeamsRequestQuery extends SearchFilter {
+export interface UserTeamsRequestQuery extends SearchFilter {
id: string;
}
diff --git a/src/pages/api/users/[id]/websites.ts b/src/pages/api/users/[id]/websites.ts
index cc264e7d..227d1c98 100644
--- a/src/pages/api/users/[id]/websites.ts
+++ b/src/pages/api/users/[id]/websites.ts
@@ -1,12 +1,12 @@
import { useAuth, useCors, useValidate } from 'lib/middleware';
-import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types';
+import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
import { pageInfo } from 'lib/schema';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getWebsitesByUserId } from 'queries';
import * as yup from 'yup';
-export interface UserWebsitesRequestQuery extends SearchFilter {
+export interface UserWebsitesRequestQuery extends SearchFilter {
id: string;
includeTeams?: boolean;
onlyTeams?: boolean;
@@ -32,7 +32,7 @@ export default async (
await useValidate(req, res);
const { user } = req.auth;
- const { id: userId, page, pageSize, query, includeTeams, onlyTeams } = req.query;
+ const { id: userId, page, query, includeTeams, onlyTeams } = req.query;
if (req.method === 'GET') {
if (!user.isAdmin && user.id !== userId) {
@@ -40,9 +40,8 @@ export default async (
}
const websites = await getWebsitesByUserId(userId, {
- query,
- page,
- pageSize: +pageSize || undefined,
+ page: +page,
+ query: query as string,
includeTeams,
onlyTeams,
});
diff --git a/src/pages/api/users/index.ts b/src/pages/api/users/index.ts
index d37add2f..670ddd5d 100644
--- a/src/pages/api/users/index.ts
+++ b/src/pages/api/users/index.ts
@@ -2,13 +2,13 @@ import { canCreateUser, canViewUsers } from 'lib/auth';
import { ROLES } from 'lib/constants';
import { uuid } from 'lib/crypto';
import { useAuth, useValidate } from 'lib/middleware';
-import { NextApiRequestQueryBody, Role, SearchFilter, User, UserSearchFilterType } from 'lib/types';
+import { NextApiRequestQueryBody, Role, SearchFilter, User } from 'lib/types';
import { pageInfo } from 'lib/schema';
import { NextApiResponse } from 'next';
import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createUser, getUserByUsername, getUsers } from 'queries';
-export interface UsersRequestQuery extends SearchFilter {}
+export interface UsersRequestQuery extends SearchFilter {}
export interface UsersRequestBody {
username: string;
password: string;
@@ -46,9 +46,9 @@ export default async (
return unauthorized(res);
}
- const { page, filter, pageSize } = req.query;
+ const { page, query } = req.query;
- const users = await getUsers({ page, filter, pageSize: pageSize ? +pageSize : null });
+ const users = await getUsers({ page, query });
return ok(res, users);
}
diff --git a/src/pages/api/websites/[id]/reports.ts b/src/pages/api/websites/[id]/reports.ts
index 2c7707e8..ec8109f8 100644
--- a/src/pages/api/websites/[id]/reports.ts
+++ b/src/pages/api/websites/[id]/reports.ts
@@ -1,11 +1,11 @@
import { canViewWebsite } from 'lib/auth';
import { useAuth, useCors, useValidate } from 'lib/middleware';
-import { NextApiRequestQueryBody, ReportSearchFilterType, SearchFilter } from 'lib/types';
+import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { getReportsByWebsiteId } from 'queries';
-export interface ReportsRequestQuery extends SearchFilter {
+export interface ReportsRequestQuery extends SearchFilter {
id: string;
}
@@ -33,12 +33,11 @@ export default async (
return unauthorized(res);
}
- const { page, filter, pageSize } = req.query;
+ const { page, query } = req.query;
const data = await getReportsByWebsiteId(websiteId, {
page,
- filter,
- pageSize: +pageSize || undefined,
+ query,
});
return ok(res, data);
diff --git a/src/pages/api/websites/index.ts b/src/pages/api/websites/index.ts
index a90f8e46..dc9ec36d 100644
--- a/src/pages/api/websites/index.ts
+++ b/src/pages/api/websites/index.ts
@@ -1,7 +1,7 @@
import { canCreateWebsite } from 'lib/auth';
import { uuid } from 'lib/crypto';
import { useAuth, useCors, useValidate } from 'lib/middleware';
-import { NextApiRequestQueryBody, SearchFilter, WebsiteSearchFilterType } from 'lib/types';
+import { NextApiRequestQueryBody, SearchFilter } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { createWebsite } from 'queries';
@@ -9,7 +9,7 @@ import userWebsites from 'pages/api/users/[id]/websites';
import * as yup from 'yup';
import { pageInfo } from 'lib/schema';
-export interface WebsitesRequestQuery extends SearchFilter {}
+export interface WebsitesRequestQuery extends SearchFilter {}
export interface WebsitesRequestBody {
name: string;
diff --git a/src/queries/admin/report.ts b/src/queries/admin/report.ts
index 59eb7035..2f987681 100644
--- a/src/queries/admin/report.ts
+++ b/src/queries/admin/report.ts
@@ -1,5 +1,4 @@
import { Prisma, Report } from '@prisma/client';
-import { REPORT_FILTER_TYPES } from 'lib/constants';
import prisma from 'lib/prisma';
import { FilterResult, ReportSearchFilter } from 'lib/types';
@@ -27,27 +26,21 @@ export async function deleteReport(reportId: string): Promise {
}
export async function getReports(
- ReportSearchFilter: ReportSearchFilter,
+ params: ReportSearchFilter,
options?: { include?: Prisma.ReportInclude },
): Promise> {
- const {
- userId,
- websiteId,
- includeTeams,
- filter,
- filterType = REPORT_FILTER_TYPES.all,
- } = ReportSearchFilter;
+ const { query, userId, websiteId, includeTeams } = params;
const mode = prisma.getSearchMode();
const where: Prisma.ReportWhereInput = {
- ...(userId && { userId: userId }),
- ...(websiteId && { websiteId: websiteId }),
+ userId,
+ websiteId,
AND: [
{
OR: [
{
- ...(userId && { userId: userId }),
+ userId,
},
{
...(includeTeams && {
@@ -71,71 +64,53 @@ export async function getReports(
{
OR: [
{
- ...((filterType === REPORT_FILTER_TYPES.all ||
- filterType === REPORT_FILTER_TYPES.name) && {
+ name: {
+ contains: query,
+ ...mode,
+ },
+ },
+ {
+ description: {
+ contains: query,
+ ...mode,
+ },
+ },
+ {
+ type: {
+ contains: query,
+ ...mode,
+ },
+ },
+ {
+ user: {
+ username: {
+ contains: query,
+ ...mode,
+ },
+ },
+ },
+ {
+ website: {
name: {
- startsWith: filter,
+ contains: query,
...mode,
},
- }),
+ },
},
{
- ...((filterType === REPORT_FILTER_TYPES.all ||
- filterType === REPORT_FILTER_TYPES.description) && {
- description: {
- startsWith: filter,
+ website: {
+ domain: {
+ contains: query,
...mode,
},
- }),
- },
- {
- ...((filterType === REPORT_FILTER_TYPES.all ||
- filterType === REPORT_FILTER_TYPES.type) && {
- type: {
- startsWith: filter,
- ...mode,
- },
- }),
- },
- {
- ...((filterType === REPORT_FILTER_TYPES.all ||
- filterType === REPORT_FILTER_TYPES['user:username']) && {
- user: {
- username: {
- startsWith: filter,
- ...mode,
- },
- },
- }),
- },
- {
- ...((filterType === REPORT_FILTER_TYPES.all ||
- filterType === REPORT_FILTER_TYPES['website:name']) && {
- website: {
- name: {
- startsWith: filter,
- ...mode,
- },
- },
- }),
- },
- {
- ...((filterType === REPORT_FILTER_TYPES.all ||
- filterType === REPORT_FILTER_TYPES['website:domain']) && {
- website: {
- domain: {
- startsWith: filter,
- ...mode,
- },
- },
- }),
+ },
},
],
},
],
};
- const [pageFilters, getParameters] = prisma.getPageFilters(ReportSearchFilter);
+ const [pageFilters, pageInfo] = prisma.getPageFilters(params);
const reports = await prisma.client.report.findMany({
where,
@@ -150,13 +125,13 @@ export async function getReports(
return {
data: reports,
count,
- ...getParameters,
+ ...pageInfo,
};
}
export async function getReportsByUserId(
userId: string,
- filter: ReportSearchFilter,
+ filter?: ReportSearchFilter,
): Promise> {
return getReports(
{ userId, ...filter },
diff --git a/src/queries/admin/website.ts b/src/queries/admin/website.ts
index f4444b53..0e7f5124 100644
--- a/src/queries/admin/website.ts
+++ b/src/queries/admin/website.ts
@@ -72,10 +72,10 @@ export async function getWebsites(
OR: query
? [
{
- name: { startsWith: query, ...mode },
+ name: { contains: query, ...mode },
},
{
- domain: { startsWith: query, ...mode },
+ domain: { contains: query, ...mode },
},
]
: [],