diff --git a/src/app/(main)/settings/websites/[id]/ShareUrl.tsx b/src/app/(main)/settings/websites/[id]/ShareUrl.tsx
index 7c9c96e6..38e03ef8 100644
--- a/src/app/(main)/settings/websites/[id]/ShareUrl.tsx
+++ b/src/app/(main)/settings/websites/[id]/ShareUrl.tsx
@@ -10,21 +10,20 @@ import {
} from 'react-basics';
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
import { getRandomChars } from 'next-basics';
-import { useApi } from 'components/hooks';
-import { useMessages } from 'components/hooks';
+import { useApi, useMessages } from 'components/hooks';
import SettingsContext from '../../SettingsContext';
const generateId = () => getRandomChars(16);
export function ShareUrl({ websiteId, data, onSave }) {
const ref = useRef(null);
- const { shareUrl, websitesUrl } = useContext(SettingsContext);
+ const { shareUrl } = 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(`${websitesUrl}/${websiteId}`, data),
+ mutationFn: (data: any) => post(`/websites/${websiteId}`, data),
});
const url = useMemo(
() => `${shareUrl}${process.env.basePath}/share/${id}/${encodeURIComponent(name)}`,
diff --git a/src/app/(main)/settings/websites/[id]/TrackingCode.tsx b/src/app/(main)/settings/websites/[id]/TrackingCode.tsx
index d13f0ddb..35882733 100644
--- a/src/app/(main)/settings/websites/[id]/TrackingCode.tsx
+++ b/src/app/(main)/settings/websites/[id]/TrackingCode.tsx
@@ -1,6 +1,5 @@
import { TextArea } from 'react-basics';
-import { useMessages } from 'components/hooks';
-import { useConfig } from 'components/hooks';
+import { useMessages, useConfig } from 'components/hooks';
import { useContext } from 'react';
import SettingsContext from '../../SettingsContext';
diff --git a/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx b/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx
index 6ed365c7..bef8eced 100644
--- a/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx
+++ b/src/app/(main)/settings/websites/[id]/WebsiteDeleteForm.tsx
@@ -1,16 +1,7 @@
-import {
- Button,
- Form,
- FormRow,
- FormButtons,
- FormInput,
- SubmitButton,
- TextField,
-} from 'react-basics';
-import { useApi } from 'components/hooks';
-import { useMessages } from 'components/hooks';
+import { useApi, useMessages } from 'components/hooks';
import { useContext } from 'react';
import SettingsContext from '../../SettingsContext';
+import TypeConfirmationForm from 'components/common/TypeConfirmationForm';
const CONFIRM_VALUE = 'DELETE';
@@ -23,40 +14,32 @@ export function WebsiteDeleteForm({
onSave?: () => void;
onClose?: () => void;
}) {
- const { formatMessage, labels, messages, FormattedMessage } = useMessages();
+ const { formatMessage, labels } = useMessages();
const { websitesUrl } = useContext(SettingsContext);
const { del, useMutation } = useApi();
- const { mutate, error } = useMutation({
+ const { mutate, isPending, error } = useMutation({
mutationFn: (data: any) => del(`${websitesUrl}/${websiteId}`, data),
});
- const handleSubmit = async (data: any) => {
- mutate(data, {
+ const handleConfirm = async () => {
+ mutate(null, {
onSuccess: async () => {
- onSave();
- onClose();
+ onSave?.();
+ onClose?.();
},
});
};
return (
-
+
);
}
diff --git a/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx b/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx
index 86077c1c..c43f3efb 100644
--- a/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx
+++ b/src/app/(main)/settings/websites/[id]/WebsiteResetForm.tsx
@@ -1,16 +1,5 @@
-import {
- Button,
- Form,
- FormRow,
- FormButtons,
- FormInput,
- SubmitButton,
- TextField,
-} from 'react-basics';
-import { useApi } from 'components/hooks';
-import { useMessages } from 'components/hooks';
-import { useContext } from 'react';
-import SettingsContext from '../../SettingsContext';
+import { useApi, useMessages } from 'components/hooks';
+import TypeConfirmationForm from 'components/common/TypeConfirmationForm';
const CONFIRM_VALUE = 'RESET';
@@ -23,40 +12,30 @@ export function WebsiteResetForm({
onSave?: () => void;
onClose?: () => void;
}) {
- const { formatMessage, labels, messages, FormattedMessage } = useMessages();
- const { websitesUrl } = useContext(SettingsContext);
+ const { formatMessage, labels } = useMessages();
const { post, useMutation } = useApi();
- const { mutate, error } = useMutation({
- mutationFn: (data: any) => post(`${websitesUrl}/${websiteId}/reset`, data),
+ const { mutate, isPending, error } = useMutation({
+ mutationFn: (data: any) => post(`/websites/${websiteId}/reset`, data),
});
- const handleSubmit = async (data: any) => {
- mutate(data, {
+ const handleConfirm = async () => {
+ mutate(null, {
onSuccess: async () => {
- onSave();
- onClose();
+ onSave?.();
+ onClose?.();
},
});
};
return (
-
+
);
}
diff --git a/src/app/(main)/teams/[id]/websites/TeamWebsites.tsx b/src/app/(main)/teams/[id]/websites/TeamWebsites.tsx
deleted file mode 100644
index 443e4b91..00000000
--- a/src/app/(main)/teams/[id]/websites/TeamWebsites.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-'use client';
-import { useContext } from 'react';
-import TeamsContext from 'app/(main)/teams/TeamsContext';
-import WebsitesDataTable from 'app/(main)/settings/websites/WebsitesDataTable';
-
-export default function TeamWebsites() {
- const team = useContext(TeamsContext);
-
- if (!team) {
- return null;
- }
-
- return ;
-}
diff --git a/src/app/(main)/teams/[id]/websites/page.tsx b/src/app/(main)/teams/[id]/websites/page.tsx
index 386c1354..aed17d84 100644
--- a/src/app/(main)/teams/[id]/websites/page.tsx
+++ b/src/app/(main)/teams/[id]/websites/page.tsx
@@ -1,11 +1,11 @@
-import TeamWebsites from './TeamWebsites';
+import WebsitesDataTable from 'app/(main)/settings/websites/WebsitesDataTable';
import WebsitesHeader from 'app/(main)/settings/websites/WebsitesHeader';
export default function TeamWebsitesPage({ params: { id } }: { params: { id: string } }) {
return (
<>
-
+
>
);
}
diff --git a/src/app/(main)/websites/WebsitesBrowse.tsx b/src/app/(main)/websites/WebsitesBrowse.tsx
index 612da39b..30e22618 100644
--- a/src/app/(main)/websites/WebsitesBrowse.tsx
+++ b/src/app/(main)/websites/WebsitesBrowse.tsx
@@ -1,9 +1,9 @@
'use client';
import WebsitesDataTable from '../settings/websites/WebsitesDataTable';
-import { useUser } from 'components/hooks';
+import { useLogin } from 'components/hooks';
export function WebsitesBrowse() {
- const { user } = useUser();
+ const { user } = useLogin();
const allowEdit = !process.env.cloudMode;
return ;
diff --git a/src/components/common/ConfirmDeleteForm.tsx b/src/components/common/ConfirmDeleteForm.tsx
deleted file mode 100644
index f6908a77..00000000
--- a/src/components/common/ConfirmDeleteForm.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { useState } from 'react';
-import { Button, LoadingButton, Form, FormButtons } from 'react-basics';
-import { useMessages } from 'components/hooks';
-
-export interface ConfirmDeleteFormProps {
- name: string;
- onConfirm?: () => void;
- onClose?: () => void;
-}
-
-export function ConfirmDeleteForm({ name, onConfirm, onClose }: ConfirmDeleteFormProps) {
- const [loading, setLoading] = useState(false);
- const { formatMessage, labels, messages, FormattedMessage } = useMessages();
-
- const handleConfirm = () => {
- setLoading(true);
- onConfirm();
- };
-
- return (
-
- );
-}
-
-export default ConfirmDeleteForm;
diff --git a/src/components/common/ConfirmationForm.tsx b/src/components/common/ConfirmationForm.tsx
new file mode 100644
index 00000000..26b4ff24
--- /dev/null
+++ b/src/components/common/ConfirmationForm.tsx
@@ -0,0 +1,39 @@
+import { ReactNode } from 'react';
+import { Button, LoadingButton, Form, FormButtons } from 'react-basics';
+import { useMessages } from 'components/hooks';
+
+export interface ConfirmationFormProps {
+ message: ReactNode;
+ buttonLabel?: ReactNode;
+ buttonVariant?: 'none' | 'primary' | 'secondary' | 'quiet' | 'danger';
+ isLoading?: boolean;
+ error?: string | Error;
+ onConfirm?: () => void;
+ onClose?: () => void;
+}
+
+export function ConfirmationForm({
+ message,
+ buttonLabel,
+ buttonVariant,
+ isLoading,
+ error,
+ onConfirm,
+ onClose,
+}: ConfirmationFormProps) {
+ const { formatMessage, labels } = useMessages();
+
+ return (
+
+ );
+}
+
+export default ConfirmationForm;
diff --git a/src/components/common/TypeConfirmationForm.tsx b/src/components/common/TypeConfirmationForm.tsx
new file mode 100644
index 00000000..2dfb2dff
--- /dev/null
+++ b/src/components/common/TypeConfirmationForm.tsx
@@ -0,0 +1,58 @@
+import {
+ Button,
+ Form,
+ FormButtons,
+ FormRow,
+ FormInput,
+ TextField,
+ SubmitButton,
+} from 'react-basics';
+import { useMessages } from 'components/hooks';
+
+export function TypeConfirmationForm({
+ confirmationValue,
+ buttonLabel,
+ buttonVariant,
+ isLoading,
+ error,
+ onConfirm,
+ onClose,
+}: {
+ confirmationValue: string;
+ buttonLabel?: string;
+ buttonVariant?: 'none' | 'primary' | 'secondary' | 'quiet' | 'danger';
+ isLoading?: boolean;
+ error?: string | Error;
+ onConfirm?: () => void;
+ onClose?: () => void;
+}) {
+ const { formatMessage, labels, messages, FormattedMessage } = useMessages();
+
+ if (!confirmationValue) {
+ return null;
+ }
+
+ return (
+
+ );
+}
+
+export default TypeConfirmationForm;
diff --git a/src/components/hooks/index.js b/src/components/hooks/index.js
index 3370d307..cdb05a6d 100644
--- a/src/components/hooks/index.js
+++ b/src/components/hooks/index.js
@@ -8,7 +8,9 @@ export * from './queries/useShareToken';
export * from './queries/useTeam';
export * from './queries/useTeamWebsites';
export * from './queries/useUser';
+export * from './queries/useUsers';
export * from './queries/useWebsite';
+export * from './queries/useWebsites';
export * from './useCountryNames';
export * from './useDateRange';
export * from './useDocumentClick';
diff --git a/src/components/hooks/queries/useLogin.ts b/src/components/hooks/queries/useLogin.ts
index ec8978b2..af9eba85 100644
--- a/src/components/hooks/queries/useLogin.ts
+++ b/src/components/hooks/queries/useLogin.ts
@@ -1,9 +1,11 @@
+import useStore, { setUser } from 'store/app';
import useApi from './useApi';
-import useUser from './useUser';
+
+const selector = (state: { user: any }) => state.user;
export function useLogin() {
const { get, useQuery } = useApi();
- const { user, setUser } = useUser();
+ const user = useStore(selector);
const query = useQuery({
queryKey: ['login'],
@@ -14,6 +16,7 @@ export function useLogin() {
return data;
},
+ enabled: !user,
});
return { user, ...query };
diff --git a/src/components/hooks/queries/useUser.ts b/src/components/hooks/queries/useUser.ts
index fb36d1bc..61c22ecd 100644
--- a/src/components/hooks/queries/useUser.ts
+++ b/src/components/hooks/queries/useUser.ts
@@ -1,11 +1,13 @@
-import useStore, { setUser } from 'store/app';
+import useApi from './useApi';
-const selector = (state: { user: any }) => state.user;
-
-export function useUser() {
- const user = useStore(selector);
-
- return { user, setUser };
+export function useUser(userId: string, options?: { [key: string]: any }) {
+ const { get, useQuery } = useApi();
+ return useQuery({
+ queryKey: ['users', userId],
+ queryFn: () => get(`/users/${userId}`),
+ enabled: !!userId,
+ ...options,
+ });
}
export default useUser;
diff --git a/src/components/hooks/queries/useUsers.ts b/src/components/hooks/queries/useUsers.ts
new file mode 100644
index 00000000..b1273814
--- /dev/null
+++ b/src/components/hooks/queries/useUsers.ts
@@ -0,0 +1,19 @@
+import useApi from './useApi';
+import useFilterQuery from './useFilterQuery';
+import useCache from 'store/cache';
+
+export function useUsers() {
+ const { get } = useApi();
+ const modified = useCache((state: any) => state?.users);
+
+ return useFilterQuery({
+ queryKey: ['users', { modified }],
+ queryFn: (params: any) => {
+ return get('/admin/users', {
+ ...params,
+ });
+ },
+ });
+}
+
+export default useUsers;
diff --git a/src/components/hooks/queries/useWebsite.ts b/src/components/hooks/queries/useWebsite.ts
index d18e96ba..386607eb 100644
--- a/src/components/hooks/queries/useWebsite.ts
+++ b/src/components/hooks/queries/useWebsite.ts
@@ -1,11 +1,12 @@
import useApi from './useApi';
-export function useWebsite(websiteId: string) {
+export function useWebsite(websiteId: string, options?: { [key: string]: any }) {
const { get, useQuery } = useApi();
return useQuery({
queryKey: ['websites', websiteId],
queryFn: () => get(`/websites/${websiteId}`),
enabled: !!websiteId,
+ ...options,
});
}
diff --git a/src/components/hooks/queries/useWebsites.ts b/src/components/hooks/queries/useWebsites.ts
index abb0ca4f..575ef236 100644
--- a/src/components/hooks/queries/useWebsites.ts
+++ b/src/components/hooks/queries/useWebsites.ts
@@ -9,7 +9,7 @@ export function useWebsites({ userId, teamId }: { userId?: string; teamId?: stri
return useFilterQuery({
queryKey: ['websites', { userId, teamId, modified }],
queryFn: (params: any) => {
- return get(teamId ? `/teams/${teamId}/websites` : '/websites', {
+ return get(teamId ? `/teams/${teamId}/websites` : `/users/${userId}/websites`, {
...params,
});
},
diff --git a/src/components/hooks/useDateRange.ts b/src/components/hooks/useDateRange.ts
index e7a31930..7e3b8bcb 100644
--- a/src/components/hooks/useDateRange.ts
+++ b/src/components/hooks/useDateRange.ts
@@ -4,8 +4,8 @@ import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE } from 'lib/constants';
import websiteStore, { setWebsiteDateRange } from 'store/websites';
import appStore, { setDateRange } from 'store/app';
import { DateRange } from 'lib/types';
-import useLocale from './useLocale';
-import { useApi } from 'components/hooks';
+import { useLocale } from './useLocale';
+import { useApi } from './queries/useApi';
export function useDateRange(websiteId?: string) {
const { get } = useApi();
diff --git a/src/components/input/ProfileButton.tsx b/src/components/input/ProfileButton.tsx
index 17d2b519..c79d4b6f 100644
--- a/src/components/input/ProfileButton.tsx
+++ b/src/components/input/ProfileButton.tsx
@@ -2,14 +2,14 @@ import { Icon, Button, PopupTrigger, Popup, Menu, Item, Text } from 'react-basic
import { useRouter } from 'next/navigation';
import Icons from 'components/icons';
import { useMessages } from 'components/hooks';
-import { useUser } from 'components/hooks';
+import { useLogin } from 'components/hooks';
import { useLocale } from 'components/hooks';
import { CURRENT_VERSION } from 'lib/constants';
import styles from './ProfileButton.module.css';
export function ProfileButton() {
const { formatMessage, labels } = useMessages();
- const { user } = useUser();
+ const { user } = useLogin();
const router = useRouter();
const { dir } = useLocale();
const cloudMode = Boolean(process.env.cloudMode);
diff --git a/src/components/messages.ts b/src/components/messages.ts
index 97cb4e1a..15d2c442 100644
--- a/src/components/messages.ts
+++ b/src/components/messages.ts
@@ -1,6 +1,7 @@
import { defineMessages } from 'react-intl';
export const labels = defineMessages({
+ ok: { id: 'label.ok', defaultMessage: 'OK' },
unknown: { id: 'label.unknown', defaultMessage: 'Unknown' },
required: { id: 'label.required', defaultMessage: 'Required' },
save: { id: 'label.save', defaultMessage: 'Save' },
@@ -50,6 +51,7 @@ export const labels = defineMessages({
websiteId: { id: 'label.website-id', defaultMessage: 'Website ID' },
resetWebsite: { id: 'label.reset-website', defaultMessage: 'Reset website' },
deleteWebsite: { id: 'label.delete-website', defaultMessage: 'Delete website' },
+ deleteReport: { id: 'label.delete-report', defaultMessage: 'Delete report' },
reset: { id: 'label.reset', defaultMessage: 'Reset' },
addWebsite: { id: 'label.add-website', defaultMessage: 'Add website' },
addMember: { id: 'label.add-member', defaultMessage: 'Add member' },
diff --git a/src/index.ts b/src/index.ts
index 7f83248f..9222e5bd 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -50,7 +50,7 @@ export * from 'app/(main)/settings/websites/WebsitesTable';
export * from 'app/(main)/settings/SettingsContext';
-export * from 'components/common/ConfirmDeleteForm';
+export * from 'components/common/TypeConfirmationForm';
export * from 'components/common/DataTable';
export * from 'components/common/Empty';
export * from 'components/common/ErrorBoundary';
diff --git a/src/pages/api/admin/users.ts b/src/pages/api/admin/users.ts
index d16895b7..3ded1167 100644
--- a/src/pages/api/admin/users.ts
+++ b/src/pages/api/admin/users.ts
@@ -44,7 +44,20 @@ export default async (
const { page, query, pageSize } = req.query;
- const users = await getUsers({ page, query, pageSize: +pageSize || undefined });
+ const users = await getUsers(
+ { page, query, pageSize: +pageSize || undefined },
+ {
+ include: {
+ _count: {
+ select: {
+ website: {
+ where: { deletedAt: null },
+ },
+ },
+ },
+ },
+ },
+ );
return ok(res, users);
}
diff --git a/src/queries/admin/user.ts b/src/queries/admin/user.ts
index 29d94ec8..51dda2d4 100644
--- a/src/queries/admin/user.ts
+++ b/src/queries/admin/user.ts
@@ -84,7 +84,7 @@ export async function getUsers(
...pageFilters,
...(options?.include && { include: options.include }),
})
- .then(a => {
+ .then((a: { [x: string]: any; password: any }[]) => {
return a.map(({ password, ...rest }) => rest);
});
@@ -163,7 +163,7 @@ export async function deleteUser(
User,
]
> {
- const { client } = prisma;
+ const { client, transaction } = prisma;
const cloudMode = process.env.CLOUD_MODE;
const websites = await client.website.findMany({
@@ -190,7 +190,7 @@ export async function deleteUser(
const teamIds = teams.map(a => a.id);
if (cloudMode) {
- return client.transaction([
+ return transaction([
client.website.updateMany({
data: {
deletedAt: new Date(),
@@ -209,70 +209,68 @@ export async function deleteUser(
]);
}
- return client
- .transaction([
- client.eventData.deleteMany({
- where: { websiteId: { in: websiteIds } },
- }),
- client.websiteEvent.deleteMany({
- where: { websiteId: { in: websiteIds } },
- }),
- client.session.deleteMany({
- where: { websiteId: { in: websiteIds } },
- }),
- client.teamUser.deleteMany({
- where: {
- OR: [
- {
- teamId: {
- in: teamIds,
- },
+ return transaction([
+ client.eventData.deleteMany({
+ where: { websiteId: { in: websiteIds } },
+ }),
+ client.websiteEvent.deleteMany({
+ where: { websiteId: { in: websiteIds } },
+ }),
+ client.session.deleteMany({
+ where: { websiteId: { in: websiteIds } },
+ }),
+ client.teamUser.deleteMany({
+ where: {
+ OR: [
+ {
+ teamId: {
+ in: teamIds,
},
- {
- userId,
- },
- ],
- },
- }),
- client.team.deleteMany({
- where: {
- id: {
- in: teamIds,
},
+ {
+ userId,
+ },
+ ],
+ },
+ }),
+ client.team.deleteMany({
+ where: {
+ id: {
+ in: teamIds,
},
- }),
- client.report.deleteMany({
- where: {
- OR: [
- {
- websiteId: {
- in: websiteIds,
- },
+ },
+ }),
+ client.report.deleteMany({
+ where: {
+ OR: [
+ {
+ websiteId: {
+ in: websiteIds,
},
- {
- userId,
- },
- ],
- },
- }),
- client.website.deleteMany({
- where: { id: { in: websiteIds } },
- }),
- client.user.delete({
- where: {
- id: userId,
- },
- }),
- ])
- .then(async (data: any) => {
- if (cache.enabled) {
- const ids = websites.map(a => a.id);
+ },
+ {
+ userId,
+ },
+ ],
+ },
+ }),
+ client.website.deleteMany({
+ where: { id: { in: websiteIds } },
+ }),
+ client.user.delete({
+ where: {
+ id: userId,
+ },
+ }),
+ ]).then(async (data: any) => {
+ if (cache.enabled) {
+ const ids = websites.map(a => a.id);
- for (let i = 0; i < ids.length; i++) {
- await cache.deleteWebsite(`website:${ids[i]}`);
- }
+ for (let i = 0; i < ids.length; i++) {
+ await cache.deleteWebsite(`website:${ids[i]}`);
}
+ }
- return data;
- });
+ return data;
+ });
}
diff --git a/src/store/cache.ts b/src/store/cache.ts
index b0ef4b81..0391455d 100644
--- a/src/store/cache.ts
+++ b/src/store/cache.ts
@@ -2,8 +2,12 @@ import { create } from 'zustand';
const store = create(() => ({}));
-export function setValue(key, value) {
+export function setValue(key: string, value: any) {
store.setState({ [key]: value });
}
+export function touch(key: string) {
+ setValue(key, Date.now());
+}
+
export default store;