mirror of
https://github.com/umami-software/umami.git
synced 2026-02-12 16:45:35 +01:00
New admin section.
This commit is contained in:
parent
cdf391d5c2
commit
b78ff3b477
28 changed files with 161 additions and 100 deletions
|
|
@ -138,6 +138,11 @@ const redirects = [
|
||||||
destination: '/teams/:id/settings/team',
|
destination: '/teams/:id/settings/team',
|
||||||
permanent: true,
|
permanent: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: '/admin',
|
||||||
|
destination: '/admin/users',
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Adding rewrites + headers for all alternative tracker script names.
|
// Adding rewrites + headers for all alternative tracker script names.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { useNavigation, useGlobalState } from '@/components/hooks';
|
||||||
|
|
||||||
export function MenuBar() {
|
export function MenuBar() {
|
||||||
const [isCollapsed, setCollapsed] = useGlobalState('sidenav-collapsed');
|
const [isCollapsed, setCollapsed] = useGlobalState('sidenav-collapsed');
|
||||||
const { websiteId } = useNavigation();
|
const { teamId, websiteId } = useNavigation();
|
||||||
|
|
||||||
const handleSelect = () => {};
|
const handleSelect = () => {};
|
||||||
|
|
||||||
|
|
@ -35,7 +35,12 @@ export function MenuBar() {
|
||||||
<Icon strokeColor="7" rotate={-25}>
|
<Icon strokeColor="7" rotate={-25}>
|
||||||
<Slash />
|
<Slash />
|
||||||
</Icon>
|
</Icon>
|
||||||
<WebsiteSelect variant="quiet" websiteId={websiteId} onSelect={handleSelect} />
|
<WebsiteSelect
|
||||||
|
variant="quiet"
|
||||||
|
websiteId={websiteId}
|
||||||
|
teamId={teamId}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
||||||
54
src/app/(main)/admin/AdminLayout.tsx
Normal file
54
src/app/(main)/admin/AdminLayout.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
'use client';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { Grid, Column } from '@umami/react-zen';
|
||||||
|
import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks';
|
||||||
|
import { SideMenu } from '@/components/common/SideMenu';
|
||||||
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
|
import { Panel } from '@/components/common/Panel';
|
||||||
|
import { PageBody } from '@/components/common/PageBody';
|
||||||
|
|
||||||
|
export function AdminLayout({ children }: { children: ReactNode }) {
|
||||||
|
const { user } = useLoginQuery();
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { pathname } = useNavigation();
|
||||||
|
|
||||||
|
if (!user.isAdmin) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
id: 'users',
|
||||||
|
label: formatMessage(labels.users),
|
||||||
|
url: '/admin/users',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'websites',
|
||||||
|
label: formatMessage(labels.websites),
|
||||||
|
url: '/admin/websites',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'teams',
|
||||||
|
label: formatMessage(labels.teams),
|
||||||
|
url: '/admin/teams',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const value = items.find(({ url }) => pathname.includes(url))?.id;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageBody>
|
||||||
|
<Column gap="6">
|
||||||
|
<PageHeader title={formatMessage(labels.admin)} />
|
||||||
|
<Grid columns="160px 1fr" gap>
|
||||||
|
<Column>
|
||||||
|
<SideMenu items={items} selectedKey={value} />
|
||||||
|
</Column>
|
||||||
|
<Column>
|
||||||
|
<Panel>{children}</Panel>
|
||||||
|
</Column>
|
||||||
|
</Grid>
|
||||||
|
</Column>
|
||||||
|
</PageBody>
|
||||||
|
);
|
||||||
|
}
|
||||||
17
src/app/(main)/admin/layout.tsx
Normal file
17
src/app/(main)/admin/layout.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
import { AdminLayout } from './AdminLayout';
|
||||||
|
|
||||||
|
export default function ({ children }) {
|
||||||
|
if (process.env.cloudMode) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <AdminLayout>{children}</AdminLayout>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: {
|
||||||
|
template: '%s | Admin | Umami',
|
||||||
|
default: 'Admin | Umami',
|
||||||
|
},
|
||||||
|
};
|
||||||
43
src/app/(main)/admin/users/UserDeleteForm.tsx
Normal file
43
src/app/(main)/admin/users/UserDeleteForm.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { AlertDialog, Row } from '@umami/react-zen';
|
||||||
|
import { useApi, useMessages, useModified } from '@/components/hooks';
|
||||||
|
|
||||||
|
export function UserDeleteForm({
|
||||||
|
userId,
|
||||||
|
username,
|
||||||
|
onSave,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
userId: string;
|
||||||
|
username: string;
|
||||||
|
onSave?: () => void;
|
||||||
|
onClose?: () => void;
|
||||||
|
}) {
|
||||||
|
const { messages, labels, formatMessage } = useMessages();
|
||||||
|
const { del, useMutation } = useApi();
|
||||||
|
const { mutate } = useMutation({ mutationFn: () => del(`/users/${userId}`) });
|
||||||
|
const { touch } = useModified();
|
||||||
|
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
mutate(null, {
|
||||||
|
onSuccess: async () => {
|
||||||
|
touch('users');
|
||||||
|
onSave?.();
|
||||||
|
onClose?.();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog
|
||||||
|
title={formatMessage(labels.delete)}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
onCancel={onClose}
|
||||||
|
confirmLabel={formatMessage(labels.delete)}
|
||||||
|
isDanger
|
||||||
|
>
|
||||||
|
<Row gap="1">
|
||||||
|
{formatMessage(messages.confirmDelete, { target: <b key={username}>{username}</b> })}
|
||||||
|
</Row>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
import { UsersDataTable } from './UsersDataTable';
|
import { UsersDataTable } from './UsersDataTable';
|
||||||
import { Column } from '@umami/react-zen';
|
import { Column } from '@umami/react-zen';
|
||||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||||
import { UserAddButton } from '@/app/(main)/settings/users/UserAddButton';
|
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
|
import { UserAddButton } from './UserAddButton';
|
||||||
|
|
||||||
export function UsersSettingsPage() {
|
export function UsersSettingsPage() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
@ -8,7 +8,6 @@ import {
|
||||||
MenuItem,
|
MenuItem,
|
||||||
MenuSeparator,
|
MenuSeparator,
|
||||||
Modal,
|
Modal,
|
||||||
Dialog,
|
|
||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { formatDistance } from 'date-fns';
|
import { formatDistance } from 'date-fns';
|
||||||
|
|
@ -17,7 +16,7 @@ import { Trash } from '@/components/icons';
|
||||||
import { useMessages, useLocale } from '@/components/hooks';
|
import { useMessages, useLocale } from '@/components/hooks';
|
||||||
import { Edit } from '@/components/icons';
|
import { Edit } from '@/components/icons';
|
||||||
import { MenuButton } from '@/components/input/MenuButton';
|
import { MenuButton } from '@/components/input/MenuButton';
|
||||||
import { UserDeleteForm } from '@/app/(main)/settings/users/UserDeleteForm';
|
import { UserDeleteForm } from './UserDeleteForm';
|
||||||
|
|
||||||
export function UsersTable({
|
export function UsersTable({
|
||||||
data = [],
|
data = [],
|
||||||
|
|
@ -29,13 +28,12 @@ export function UsersTable({
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { dateLocale } = useLocale();
|
const { dateLocale } = useLocale();
|
||||||
const [deleteUser, setDeleteUser] = useState(null);
|
const [deleteUser, setDeleteUser] = useState(null);
|
||||||
const handleDelete = () => {};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DataTable data={data}>
|
<DataTable data={data}>
|
||||||
<DataColumn id="username" label={formatMessage(labels.username)} width="2fr">
|
<DataColumn id="username" label={formatMessage(labels.username)} width="2fr">
|
||||||
{(row: any) => <Link href={`/settings/users/${row.id}`}>{row.username}</Link>}
|
{(row: any) => <Link href={`/admin/users/${row.id}`}>{row.username}</Link>}
|
||||||
</DataColumn>
|
</DataColumn>
|
||||||
<DataColumn id="role" label={formatMessage(labels.role)}>
|
<DataColumn id="role" label={formatMessage(labels.role)}>
|
||||||
{(row: any) =>
|
{(row: any) =>
|
||||||
|
|
@ -62,7 +60,7 @@ export function UsersTable({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuButton>
|
<MenuButton>
|
||||||
<MenuItem href={`/settings/users/${id}`} data-test="link-button-edit">
|
<MenuItem href={`/admin/users/${id}`} data-test="link-button-edit">
|
||||||
<Row alignItems="center" gap>
|
<Row alignItems="center" gap>
|
||||||
<Icon>
|
<Icon>
|
||||||
<Edit />
|
<Edit />
|
||||||
|
|
@ -90,19 +88,13 @@ export function UsersTable({
|
||||||
)}
|
)}
|
||||||
</DataTable>
|
</DataTable>
|
||||||
<Modal isOpen={!!deleteUser}>
|
<Modal isOpen={!!deleteUser}>
|
||||||
<Dialog title={formatMessage(labels.deleteUser)}>
|
<UserDeleteForm
|
||||||
{({ close }) => (
|
userId={deleteUser?.id}
|
||||||
<UserDeleteForm
|
username={deleteUser?.username}
|
||||||
userId={deleteUser?.id}
|
onClose={() => {
|
||||||
username={deleteUser?.username}
|
setDeleteUser(null);
|
||||||
onSave={handleDelete}
|
}}
|
||||||
onClose={() => {
|
/>
|
||||||
close();
|
|
||||||
setDeleteUser(null);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Dialog>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { WebsitesTable } from '@/app/(main)/settings/websites/WebsitesTable';
|
|
||||||
import { DataGrid } from '@/components/common/DataGrid';
|
import { DataGrid } from '@/components/common/DataGrid';
|
||||||
import { useWebsitesQuery } from '@/components/hooks';
|
import { useWebsitesQuery } from '@/components/hooks';
|
||||||
|
import { WebsitesTable } from '@/app/(main)/settings/websites/WebsitesTable';
|
||||||
|
|
||||||
export function UserWebsites({ userId }) {
|
export function UserWebsites({ userId }) {
|
||||||
const queryResult = useWebsitesQuery({ userId });
|
const queryResult = useWebsitesQuery({ userId });
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
import { useToast } from '@umami/react-zen';
|
|
||||||
import { useApi, useMessages, useModified } from '@/components/hooks';
|
|
||||||
import { ConfirmationForm } from '@/components/common/ConfirmationForm';
|
|
||||||
|
|
||||||
export function UserDeleteForm({ userId, username, onSave, onClose }) {
|
|
||||||
const { messages, labels, formatMessage } = useMessages();
|
|
||||||
const { del, useMutation } = useApi();
|
|
||||||
const { mutate, error, isPending } = useMutation({ mutationFn: () => del(`/users/${userId}`) });
|
|
||||||
const { touch } = useModified();
|
|
||||||
const { toast } = useToast();
|
|
||||||
|
|
||||||
const handleConfirm = async () => {
|
|
||||||
mutate(null, {
|
|
||||||
onSuccess: async () => {
|
|
||||||
touch('users');
|
|
||||||
toast(formatMessage(messages.successMessage));
|
|
||||||
onSave?.();
|
|
||||||
onClose?.();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ConfirmationForm
|
|
||||||
message={formatMessage(messages.confirmDelete, {
|
|
||||||
target: <b key={messages.confirmDelete.id}> {username}</b>,
|
|
||||||
})}
|
|
||||||
onConfirm={handleConfirm}
|
|
||||||
onClose={onClose}
|
|
||||||
buttonLabel={formatMessage(labels.delete)}
|
|
||||||
buttonVariant="danger"
|
|
||||||
isLoading={isPending}
|
|
||||||
error={error}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { ReactNode } from 'react';
|
import { WebsitesTable } from './WebsitesTable';
|
||||||
import { WebsitesTable } from '@/app/(main)/settings/websites/WebsitesTable';
|
|
||||||
import { DataGrid } from '@/components/common/DataGrid';
|
import { DataGrid } from '@/components/common/DataGrid';
|
||||||
import { useWebsitesQuery } from '@/components/hooks';
|
import { useWebsitesQuery } from '@/components/hooks';
|
||||||
|
|
||||||
|
|
@ -8,18 +7,16 @@ export function WebsitesDataTable({
|
||||||
allowEdit = true,
|
allowEdit = true,
|
||||||
allowView = true,
|
allowView = true,
|
||||||
showActions = true,
|
showActions = true,
|
||||||
children,
|
|
||||||
}: {
|
}: {
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
allowEdit?: boolean;
|
allowEdit?: boolean;
|
||||||
allowView?: boolean;
|
allowView?: boolean;
|
||||||
showActions?: boolean;
|
showActions?: boolean;
|
||||||
children?: ReactNode;
|
|
||||||
}) {
|
}) {
|
||||||
const queryResult = useWebsitesQuery({ teamId });
|
const queryResult = useWebsitesQuery({ teamId });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataGrid queryResult={queryResult} renderEmpty={() => children} allowSearch allowPaging>
|
<DataGrid queryResult={queryResult} allowSearch allowPaging>
|
||||||
{({ data }) => (
|
{({ data }) => (
|
||||||
<WebsitesTable
|
<WebsitesTable
|
||||||
teamId={teamId}
|
teamId={teamId}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
import { Column } from '@umami/react-zen';
|
||||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||||
import { WebsitesDataTable } from './WebsitesDataTable';
|
import { WebsitesDataTable } from './WebsitesDataTable';
|
||||||
import { ROLES } from '@/lib/constants';
|
import { ROLES } from '@/lib/constants';
|
||||||
import { WebsiteAddButton } from '@/app/(main)/settings/websites/WebsiteAddButton';
|
|
||||||
import { SectionHeader } from '@/components/common/SectionHeader';
|
import { SectionHeader } from '@/components/common/SectionHeader';
|
||||||
import { Column } from '@umami/react-zen';
|
import { WebsiteAddButton } from './WebsiteAddButton';
|
||||||
|
|
||||||
export function WebsitesSettingsPage({ teamId }: { teamId: string }) {
|
export function WebsitesSettingsPage({ teamId }: { teamId: string }) {
|
||||||
const { user } = useLoginQuery();
|
const { user } = useLoginQuery();
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { canViewTeam } from '@/lib/auth';
|
import { canViewTeam } from '@/lib/auth';
|
||||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
import { parseRequest } from '@/lib/request';
|
||||||
import { pagingParams } from '@/lib/schema';
|
import { pagingParams, searchParams } from '@/lib/schema';
|
||||||
import { getTeamWebsites } from '@/queries';
|
import { getTeamWebsites } from '@/queries';
|
||||||
|
|
||||||
export async function GET(request: Request, { params }: { params: Promise<{ teamId: string }> }) {
|
export async function GET(request: Request, { params }: { params: Promise<{ teamId: string }> }) {
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
...pagingParams,
|
...pagingParams,
|
||||||
|
...searchParams,
|
||||||
});
|
});
|
||||||
const { teamId } = await params;
|
const { teamId } = await params;
|
||||||
const { auth, query, error } = await parseRequest(request, schema);
|
const { auth, query, error } = await parseRequest(request, schema);
|
||||||
|
|
@ -20,9 +21,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ team
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters(query);
|
const websites = await getTeamWebsites(teamId, query);
|
||||||
|
|
||||||
const websites = await getTeamWebsites(teamId, filters);
|
|
||||||
|
|
||||||
return json(websites);
|
return json(websites);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { unauthorized, json } from '@/lib/response';
|
import { unauthorized, json } from '@/lib/response';
|
||||||
import { getUserWebsites } from '@/queries/prisma/website';
|
import { getUserWebsites } from '@/queries/prisma/website';
|
||||||
import { pagingParams } from '@/lib/schema';
|
import { pagingParams, searchParams } from '@/lib/schema';
|
||||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
import { parseRequest } from '@/lib/request';
|
||||||
|
|
||||||
export async function GET(request: Request, { params }: { params: Promise<{ userId: string }> }) {
|
export async function GET(request: Request, { params }: { params: Promise<{ userId: string }> }) {
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
...pagingParams,
|
...pagingParams,
|
||||||
|
...searchParams,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { auth, query, error } = await parseRequest(request, schema);
|
const { auth, query, error } = await parseRequest(request, schema);
|
||||||
|
|
@ -21,9 +22,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ user
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = await getQueryFilters(query);
|
const websites = await getUserWebsites(userId, query);
|
||||||
|
|
||||||
const websites = await getUserWebsites(userId, filters);
|
|
||||||
|
|
||||||
return json(websites);
|
return json(websites);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,9 @@ import { canCreateTeamWebsite, canCreateWebsite } from '@/lib/auth';
|
||||||
import { json, unauthorized } from '@/lib/response';
|
import { json, unauthorized } from '@/lib/response';
|
||||||
import { uuid } from '@/lib/crypto';
|
import { uuid } from '@/lib/crypto';
|
||||||
import { parseRequest } from '@/lib/request';
|
import { parseRequest } from '@/lib/request';
|
||||||
import { createWebsite, getUserWebsites } from '@/queries';
|
import { createWebsite } from '@/queries';
|
||||||
import { pagingParams } from '@/lib/schema';
|
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export { GET } from '@/app/api/users/[userId]/websites/route';
|
||||||
const schema = z.object({ ...pagingParams });
|
|
||||||
|
|
||||||
const { auth, query, error } = await parseRequest(request, schema);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return error();
|
|
||||||
}
|
|
||||||
|
|
||||||
const websites = await getUserWebsites(auth.user.id, query);
|
|
||||||
|
|
||||||
return json(websites);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ export function useTeamsQuery(userId: string) {
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['teams', { userId, modified }],
|
queryKey: ['teams', { userId, modified }],
|
||||||
queryFn: (params: any) => {
|
queryFn: () => {
|
||||||
return get(`/users/${userId}/teams`, params);
|
return get(`/users/${userId}/teams`, { userId });
|
||||||
},
|
},
|
||||||
enabled: !!userId,
|
enabled: !!userId,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,17 @@ export function TeamsButton({
|
||||||
}) {
|
}) {
|
||||||
const { user } = useLoginQuery();
|
const { user } = useLoginQuery();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { result } = useTeamsQuery(user.id);
|
const { data } = useTeamsQuery(user.id);
|
||||||
const { teamId } = useNavigation();
|
const { teamId } = useNavigation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const team = result?.data?.find(({ id }) => id === teamId);
|
const team = data?.data?.find(({ id }) => id === teamId);
|
||||||
const selectedKeys = new Set([teamId || user.id]);
|
const selectedKeys = new Set([teamId || user.id]);
|
||||||
|
|
||||||
const handleSelect = (id: Key) => {
|
const handleSelect = (id: Key) => {
|
||||||
router.push(id === user.id ? '/websites' : `/teams/${id}/websites`);
|
router.push(id === user.id ? '/websites' : `/teams/${id}/websites`);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!result?.count) {
|
if (!data?.count) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ export function TeamsButton({
|
||||||
</MenuSection>
|
</MenuSection>
|
||||||
<MenuSeparator />
|
<MenuSeparator />
|
||||||
<MenuSection title={formatMessage(labels.teams)}>
|
<MenuSection title={formatMessage(labels.teams)}>
|
||||||
{result?.data?.map(({ id, name }) => (
|
{data?.data?.map(({ id, name }) => (
|
||||||
<MenuItem key={id} id={id}>
|
<MenuItem key={id} id={id}>
|
||||||
<Icon size="sm">
|
<Icon size="sm">
|
||||||
<Users />
|
<Users />
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,6 @@ export function WebsiteSelect({
|
||||||
setSearch(value);
|
setSearch(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ export const filterParams = {
|
||||||
hostname: z.string().optional(),
|
hostname: z.string().optional(),
|
||||||
language: z.string().optional(),
|
language: z.string().optional(),
|
||||||
event: z.string().optional(),
|
event: z.string().optional(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const searchParams = {
|
||||||
search: z.string().optional(),
|
search: z.string().optional(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue