mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-29 16:19:43 +08:00
feat(admin): add ban user to admin panel (#10780)
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
|||||||
import type { UserType } from '@affine/graphql';
|
import type { UserType } from '@affine/graphql';
|
||||||
import { FeatureType } from '@affine/graphql';
|
import { FeatureType } from '@affine/graphql';
|
||||||
import type { ColumnDef } from '@tanstack/react-table';
|
import type { ColumnDef } from '@tanstack/react-table';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import {
|
import {
|
||||||
LockIcon,
|
LockIcon,
|
||||||
@@ -64,18 +65,31 @@ export const columns: ColumnDef<UserType>[] = [
|
|||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="flex flex-col gap-1 max-w-full overflow-hidden">
|
<div className="flex flex-col gap-1 max-w-full overflow-hidden">
|
||||||
<div className="text-sm font-medium max-w-full overflow-hidden gap-[6px]">
|
<div className="text-sm font-medium max-w-full overflow-hidden gap-[6px]">
|
||||||
<span>{row.original.name}</span>{' '}
|
<span>{row.original.name}</span>
|
||||||
{row.original.features.includes(FeatureType.Admin) && (
|
{row.original.features.includes(FeatureType.Admin) && (
|
||||||
<span
|
<span
|
||||||
className="rounded p-1 text-xs"
|
className="ml-2 rounded px-2 py-0.5 text-xs h-5 border"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'rgba(30, 150, 235, 0.20)',
|
borderRadius: '4px',
|
||||||
color: 'rgba(30, 150, 235, 1)',
|
backgroundColor: cssVarV2('chip/label/blue'),
|
||||||
|
borderColor: cssVarV2('layer/insideBorder/border'),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Admin
|
Admin
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{row.original.disabled && (
|
||||||
|
<span
|
||||||
|
className="ml-2 rounded px-2 py-0.5 text-xs h-5 border"
|
||||||
|
style={{
|
||||||
|
borderRadius: '4px',
|
||||||
|
backgroundColor: cssVarV2('chip/label/white'),
|
||||||
|
borderColor: cssVarV2('layer/insideBorder/border'),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Disabled
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs font-medium opacity-50 max-w-full overflow-hidden">
|
<div className="text-xs font-medium opacity-50 max-w-full overflow-hidden">
|
||||||
{row.original.email}
|
{row.original.email}
|
||||||
|
|||||||
+86
-14
@@ -7,20 +7,28 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@affine/admin/components/ui/dropdown-menu';
|
} from '@affine/admin/components/ui/dropdown-menu';
|
||||||
import {
|
import {
|
||||||
|
AccountBanIcon,
|
||||||
|
DeleteIcon,
|
||||||
|
EditIcon,
|
||||||
LockIcon,
|
LockIcon,
|
||||||
MoreVerticalIcon,
|
MoreHorizontalIcon,
|
||||||
SettingsIcon,
|
} from '@blocksuite/icons/rc';
|
||||||
TrashIcon,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
import { useRightPanel } from '../../panel/context';
|
import { useRightPanel } from '../../panel/context';
|
||||||
import type { UserType } from '../schema';
|
import type { UserType } from '../schema';
|
||||||
import { DeleteAccountDialog } from './delete-account';
|
import { DeleteAccountDialog } from './delete-account';
|
||||||
|
import { DisableAccountDialog } from './disable-account';
|
||||||
import { DiscardChanges } from './discard-changes';
|
import { DiscardChanges } from './discard-changes';
|
||||||
|
import { EnableAccountDialog } from './enable-account';
|
||||||
import { ResetPasswordDialog } from './reset-password';
|
import { ResetPasswordDialog } from './reset-password';
|
||||||
import { useDeleteUser, useResetUserPassword } from './use-user-management';
|
import {
|
||||||
|
useDeleteUser,
|
||||||
|
useDisableUser,
|
||||||
|
useEnableUser,
|
||||||
|
useResetUserPassword,
|
||||||
|
} from './use-user-management';
|
||||||
import { UpdateUserForm } from './user-form';
|
import { UpdateUserForm } from './user-form';
|
||||||
|
|
||||||
interface DataTableRowActionsProps {
|
interface DataTableRowActionsProps {
|
||||||
@@ -30,10 +38,14 @@ interface DataTableRowActionsProps {
|
|||||||
export function DataTableRowActions({ user }: DataTableRowActionsProps) {
|
export function DataTableRowActions({ user }: DataTableRowActionsProps) {
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const [resetPasswordDialogOpen, setResetPasswordDialogOpen] = useState(false);
|
const [resetPasswordDialogOpen, setResetPasswordDialogOpen] = useState(false);
|
||||||
|
const [disableDialogOpen, setDisableDialogOpen] = useState(false);
|
||||||
|
const [enableDialogOpen, setEnableDialogOpen] = useState(false);
|
||||||
const [discardDialogOpen, setDiscardDialogOpen] = useState(false);
|
const [discardDialogOpen, setDiscardDialogOpen] = useState(false);
|
||||||
const { openPanel, isOpen, closePanel, setPanelContent } = useRightPanel();
|
const { openPanel, isOpen, closePanel, setPanelContent } = useRightPanel();
|
||||||
|
|
||||||
const deleteUser = useDeleteUser();
|
const deleteUser = useDeleteUser();
|
||||||
|
const disableUser = useDisableUser();
|
||||||
|
const enableUser = useEnableUser();
|
||||||
const { resetPasswordLink, onResetPassword } = useResetUserPassword();
|
const { resetPasswordLink, onResetPassword } = useResetUserPassword();
|
||||||
|
|
||||||
const openResetPasswordDialog = useCallback(() => {
|
const openResetPasswordDialog = useCallback(() => {
|
||||||
@@ -56,25 +68,56 @@ export function DataTableRowActions({ user }: DataTableRowActionsProps) {
|
|||||||
});
|
});
|
||||||
}, [resetPasswordLink]);
|
}, [resetPasswordLink]);
|
||||||
|
|
||||||
const onDeleting = useCallback(() => {
|
const handleDeleting = useCallback(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
closePanel();
|
closePanel();
|
||||||
}
|
}
|
||||||
setDeleteDialogOpen(false);
|
setDeleteDialogOpen(false);
|
||||||
}, [closePanel, isOpen]);
|
}, [closePanel, isOpen]);
|
||||||
|
const handleDisabling = useCallback(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
closePanel();
|
||||||
|
}
|
||||||
|
setDisableDialogOpen(false);
|
||||||
|
}, [closePanel, isOpen]);
|
||||||
|
const handleEnabling = useCallback(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
closePanel();
|
||||||
|
}
|
||||||
|
setEnableDialogOpen(false);
|
||||||
|
}, [closePanel, isOpen]);
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useCallback(() => {
|
||||||
deleteUser(user.id, onDeleting);
|
deleteUser(user.id, handleDeleting);
|
||||||
}, [deleteUser, onDeleting, user.id]);
|
}, [deleteUser, handleDeleting, user.id]);
|
||||||
|
const handleDisable = useCallback(() => {
|
||||||
|
disableUser(user.id, handleDisabling);
|
||||||
|
}, [disableUser, handleDisabling, user.id]);
|
||||||
|
const handleEnable = useCallback(() => {
|
||||||
|
enableUser(user.id, handleEnabling);
|
||||||
|
}, [enableUser, handleEnabling, user.id]);
|
||||||
|
|
||||||
const openDeleteDialog = useCallback(() => {
|
const openDeleteDialog = useCallback(() => {
|
||||||
setDeleteDialogOpen(true);
|
setDeleteDialogOpen(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closeDeleteDialog = useCallback(() => {
|
const closeDeleteDialog = useCallback(() => {
|
||||||
setDeleteDialogOpen(false);
|
setDeleteDialogOpen(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const openDisableDialog = useCallback(() => {
|
||||||
|
setDisableDialogOpen(true);
|
||||||
|
}, []);
|
||||||
|
const closeDisableDialog = useCallback(() => {
|
||||||
|
setDisableDialogOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const openEnableDialog = useCallback(() => {
|
||||||
|
setEnableDialogOpen(true);
|
||||||
|
}, []);
|
||||||
|
const closeEnableDialog = useCallback(() => {
|
||||||
|
setEnableDialogOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleDiscardChangesCancel = useCallback(() => {
|
const handleDiscardChangesCancel = useCallback(() => {
|
||||||
setDiscardDialogOpen(false);
|
setDiscardDialogOpen(false);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -122,7 +165,7 @@ export function DataTableRowActions({ user }: DataTableRowActionsProps) {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
|
className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
|
||||||
>
|
>
|
||||||
<MoreVerticalIcon size={20} />
|
<MoreHorizontalIcon fontSize={20} />
|
||||||
<span className="sr-only">Open menu</span>
|
<span className="sr-only">Open menu</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@@ -135,21 +178,36 @@ export function DataTableRowActions({ user }: DataTableRowActionsProps) {
|
|||||||
className="px-2 py-[6px] text-sm font-medium gap-2 cursor-pointer"
|
className="px-2 py-[6px] text-sm font-medium gap-2 cursor-pointer"
|
||||||
onSelect={openResetPasswordDialog}
|
onSelect={openResetPasswordDialog}
|
||||||
>
|
>
|
||||||
<LockIcon size={16} /> Reset Password
|
<LockIcon fontSize={20} /> Reset Password
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onSelect={handleEdit}
|
onSelect={handleEdit}
|
||||||
className="px-2 py-[6px] text-sm font-medium gap-2 cursor-pointer"
|
className="px-2 py-[6px] text-sm font-medium gap-2 cursor-pointer"
|
||||||
>
|
>
|
||||||
<SettingsIcon size={16} /> Edit
|
<EditIcon fontSize={20} /> Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
{user.disabled && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="px-2 py-[6px] text-sm font-medium gap-2 cursor-pointer"
|
||||||
|
onSelect={openEnableDialog}
|
||||||
|
>
|
||||||
|
<AccountBanIcon fontSize={20} /> Enable Email
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
{!user.disabled && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="px-2 py-[6px] text-sm font-medium gap-2 text-red-500 cursor-pointer focus:text-red-500"
|
||||||
|
onSelect={openDisableDialog}
|
||||||
|
>
|
||||||
|
<AccountBanIcon fontSize={20} /> Disable & Delete data
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="px-2 py-[6px] text-sm font-medium gap-2 text-red-500 cursor-pointer focus:text-red-500"
|
className="px-2 py-[6px] text-sm font-medium gap-2 text-red-500 cursor-pointer focus:text-red-500"
|
||||||
onSelect={openDeleteDialog}
|
onSelect={openDeleteDialog}
|
||||||
>
|
>
|
||||||
<TrashIcon size={16} /> Delete
|
<DeleteIcon fontSize={20} /> Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
@@ -160,6 +218,20 @@ export function DataTableRowActions({ user }: DataTableRowActionsProps) {
|
|||||||
onOpenChange={setDeleteDialogOpen}
|
onOpenChange={setDeleteDialogOpen}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
/>
|
/>
|
||||||
|
<DisableAccountDialog
|
||||||
|
email={user.email}
|
||||||
|
open={disableDialogOpen}
|
||||||
|
onClose={closeDisableDialog}
|
||||||
|
onOpenChange={setDisableDialogOpen}
|
||||||
|
onDisable={handleDisable}
|
||||||
|
/>
|
||||||
|
<EnableAccountDialog
|
||||||
|
email={user.email}
|
||||||
|
open={enableDialogOpen}
|
||||||
|
onClose={closeEnableDialog}
|
||||||
|
onOpenChange={setEnableDialogOpen}
|
||||||
|
onConfirm={handleEnable}
|
||||||
|
/>
|
||||||
<ResetPasswordDialog
|
<ResetPasswordDialog
|
||||||
link={resetPasswordLink}
|
link={resetPasswordLink}
|
||||||
open={resetPasswordDialogOpen}
|
open={resetPasswordDialogOpen}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export const DeleteAccountDialog = ({
|
|||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
|
disabled={input !== email}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { Button } from '@affine/admin/components/ui/button';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@affine/admin/components/ui/dialog';
|
||||||
|
import { Input } from '@affine/admin/components/ui/input';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export const DisableAccountDialog = ({
|
||||||
|
email,
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onDisable,
|
||||||
|
onOpenChange,
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onDisable: () => void;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
const [input, setInput] = useState('');
|
||||||
|
const handleInput = useCallback(
|
||||||
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setInput(event.target.value);
|
||||||
|
},
|
||||||
|
[setInput]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) {
|
||||||
|
setInput('');
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="sm:max-w-[460px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Disable Account ?</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
The data associated with <span className="font-bold">{email}</span>{' '}
|
||||||
|
will be deleted and cannot be used for logging in. This operation is
|
||||||
|
irreversible. Please proceed with caution.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={input}
|
||||||
|
onChange={handleInput}
|
||||||
|
placeholder="Please type email to confirm"
|
||||||
|
className="placeholder:opacity-50"
|
||||||
|
/>
|
||||||
|
<DialogFooter>
|
||||||
|
<div className="flex justify-between items-center w-full">
|
||||||
|
<Button type="button" variant="outline" size="sm" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={onDisable}
|
||||||
|
disabled={input !== email}
|
||||||
|
size="sm"
|
||||||
|
variant="destructive"
|
||||||
|
>
|
||||||
|
Disable
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Button } from '@affine/admin/components/ui/button';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@affine/admin/components/ui/dialog';
|
||||||
|
|
||||||
|
export const EnableAccountDialog = ({
|
||||||
|
open,
|
||||||
|
email,
|
||||||
|
onClose,
|
||||||
|
onConfirm,
|
||||||
|
onOpenChange,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
email: string;
|
||||||
|
onClose: () => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="sm:w-[460px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="leading-7">Enable Account</DialogTitle>
|
||||||
|
<DialogDescription className="leading-6">
|
||||||
|
Are you sure you want to enable the account? After enabling the
|
||||||
|
account, the <span className="font-bold">{email}</span> email can be
|
||||||
|
used to log in.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<div className="flex justify-end items-center w-full space-x-4">
|
||||||
|
<Button type="button" onClick={onClose} variant="outline">
|
||||||
|
<span>Cancel</span>
|
||||||
|
</Button>
|
||||||
|
<Button type="button" onClick={onConfirm} variant="default">
|
||||||
|
<span>Enable</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
createChangePasswordUrlMutation,
|
createChangePasswordUrlMutation,
|
||||||
createUserMutation,
|
createUserMutation,
|
||||||
deleteUserMutation,
|
deleteUserMutation,
|
||||||
|
disableUserMutation,
|
||||||
|
enableUserMutation,
|
||||||
getUsersCountQuery,
|
getUsersCountQuery,
|
||||||
listUsersQuery,
|
listUsersQuery,
|
||||||
updateAccountFeaturesMutation,
|
updateAccountFeaturesMutation,
|
||||||
@@ -162,6 +164,55 @@ export const useDeleteUser = () => {
|
|||||||
return deleteById;
|
return deleteById;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useEnableUser = () => {
|
||||||
|
const { trigger: enableUserById } = useMutation({
|
||||||
|
mutation: enableUserMutation,
|
||||||
|
});
|
||||||
|
|
||||||
|
const revalidate = useMutateQueryResource();
|
||||||
|
|
||||||
|
const enableById = useAsyncCallback(
|
||||||
|
async (id: string, callback?: () => void) => {
|
||||||
|
await enableUserById({ id })
|
||||||
|
.then(async ({ enableUser }) => {
|
||||||
|
await revalidate(listUsersQuery);
|
||||||
|
toast(`User ${enableUser.email} enabled successfully`);
|
||||||
|
callback?.();
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
toast.error('Failed to enable user: ' + e.message);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[enableUserById, revalidate]
|
||||||
|
);
|
||||||
|
|
||||||
|
return enableById;
|
||||||
|
};
|
||||||
|
export const useDisableUser = () => {
|
||||||
|
const { trigger: disableUserById } = useMutation({
|
||||||
|
mutation: disableUserMutation,
|
||||||
|
});
|
||||||
|
|
||||||
|
const revalidate = useMutateQueryResource();
|
||||||
|
|
||||||
|
const disableById = useAsyncCallback(
|
||||||
|
async (id: string, callback?: () => void) => {
|
||||||
|
await disableUserById({ id })
|
||||||
|
.then(async ({ banUser }) => {
|
||||||
|
await revalidate(listUsersQuery);
|
||||||
|
toast(`User ${banUser.email} disabled successfully`);
|
||||||
|
callback?.();
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
toast.error('Failed to disable user: ' + e.message);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[disableUserById, revalidate]
|
||||||
|
);
|
||||||
|
|
||||||
|
return disableById;
|
||||||
|
};
|
||||||
|
|
||||||
export const useUserCount = () => {
|
export const useUserCount = () => {
|
||||||
const {
|
const {
|
||||||
data: { usersCount },
|
data: { usersCount },
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@affine/admin/components/ui/dropdown-menu';
|
} from '@affine/admin/components/ui/dropdown-menu';
|
||||||
import { CircleUser, MoreVertical } from 'lucide-react';
|
import { MoreVerticalIcon } from '@blocksuite/icons/rc';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { CircleUser } from 'lucide-react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
@@ -79,10 +81,11 @@ export function UserDropdown({ isCollapsed }: UserDropdownProps) {
|
|||||||
<span className="text-sm">{currentUser?.email.split('@')[0]}</span>
|
<span className="text-sm">{currentUser?.email.split('@')[0]}</span>
|
||||||
)}
|
)}
|
||||||
<span
|
<span
|
||||||
className="rounded p-1 text-xs"
|
className="ml-2 rounded px-2 py-0.5 text-xs h-5 border"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'rgba(30, 150, 235, 0.20)',
|
borderRadius: '4px',
|
||||||
color: 'rgba(30, 150, 235, 1)',
|
backgroundColor: cssVarV2('chip/label/blue'),
|
||||||
|
borderColor: cssVarV2('layer/insideBorder/border'),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Admin
|
Admin
|
||||||
@@ -91,7 +94,7 @@ export function UserDropdown({ isCollapsed }: UserDropdownProps) {
|
|||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" className="ml-2 p-1 h-6">
|
<Button variant="ghost" className="ml-2 p-1 h-6">
|
||||||
<MoreVertical size={20} />
|
<MoreVerticalIcon fontSize={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" side="right">
|
<DropdownMenuContent align="end" side="right">
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
mutation disableUser($id: String!) {
|
||||||
|
banUser(id: $id) {
|
||||||
|
email
|
||||||
|
disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
mutation enableUser($id: String!) {
|
||||||
|
enableUser(id: $id) {
|
||||||
|
email
|
||||||
|
disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -439,6 +439,17 @@ export const deleteWorkspaceMutation = {
|
|||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const disableUserMutation = {
|
||||||
|
id: 'disableUserMutation' as const,
|
||||||
|
op: 'disableUser',
|
||||||
|
query: `mutation disableUser($id: String!) {
|
||||||
|
banUser(id: $id) {
|
||||||
|
email
|
||||||
|
disabled
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|
||||||
export const getDocRolePermissionsQuery = {
|
export const getDocRolePermissionsQuery = {
|
||||||
id: 'getDocRolePermissionsQuery' as const,
|
id: 'getDocRolePermissionsQuery' as const,
|
||||||
op: 'getDocRolePermissions',
|
op: 'getDocRolePermissions',
|
||||||
@@ -465,6 +476,17 @@ export const getDocRolePermissionsQuery = {
|
|||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const enableUserMutation = {
|
||||||
|
id: 'enableUserMutation' as const,
|
||||||
|
op: 'enableUser',
|
||||||
|
query: `mutation enableUser($id: String!) {
|
||||||
|
enableUser(id: $id) {
|
||||||
|
email
|
||||||
|
disabled
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|
||||||
export const generateLicenseKeyMutation = {
|
export const generateLicenseKeyMutation = {
|
||||||
id: 'generateLicenseKeyMutation' as const,
|
id: 'generateLicenseKeyMutation' as const,
|
||||||
op: 'generateLicenseKey',
|
op: 'generateLicenseKey',
|
||||||
@@ -970,6 +992,7 @@ export const listUsersQuery = {
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
email
|
email
|
||||||
|
disabled
|
||||||
features
|
features
|
||||||
hasPassword
|
hasPassword
|
||||||
emailVerified
|
emailVerified
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ query listUsers($filter: ListUserInput!) {
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
email
|
email
|
||||||
|
disabled
|
||||||
features
|
features
|
||||||
hasPassword
|
hasPassword
|
||||||
emailVerified
|
emailVerified
|
||||||
|
|||||||
@@ -2597,6 +2597,15 @@ export type DeleteWorkspaceMutation = {
|
|||||||
deleteWorkspace: boolean;
|
deleteWorkspace: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DisableUserMutationVariables = Exact<{
|
||||||
|
id: Scalars['String']['input'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type DisableUserMutation = {
|
||||||
|
__typename?: 'Mutation';
|
||||||
|
banUser: { __typename?: 'UserType'; email: string; disabled: boolean };
|
||||||
|
};
|
||||||
|
|
||||||
export type GetDocRolePermissionsQueryVariables = Exact<{
|
export type GetDocRolePermissionsQueryVariables = Exact<{
|
||||||
workspaceId: Scalars['String']['input'];
|
workspaceId: Scalars['String']['input'];
|
||||||
docId: Scalars['String']['input'];
|
docId: Scalars['String']['input'];
|
||||||
@@ -2628,6 +2637,15 @@ export type GetDocRolePermissionsQuery = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type EnableUserMutationVariables = Exact<{
|
||||||
|
id: Scalars['String']['input'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type EnableUserMutation = {
|
||||||
|
__typename?: 'Mutation';
|
||||||
|
enableUser: { __typename?: 'UserType'; email: string; disabled: boolean };
|
||||||
|
};
|
||||||
|
|
||||||
export type CredentialsRequirementsFragment = {
|
export type CredentialsRequirementsFragment = {
|
||||||
__typename?: 'CredentialsRequirementType';
|
__typename?: 'CredentialsRequirementType';
|
||||||
password: {
|
password: {
|
||||||
@@ -3195,6 +3213,7 @@ export type ListUsersQuery = {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
disabled: boolean;
|
||||||
features: Array<FeatureType>;
|
features: Array<FeatureType>;
|
||||||
hasPassword: boolean | null;
|
hasPassword: boolean | null;
|
||||||
emailVerified: boolean;
|
emailVerified: boolean;
|
||||||
@@ -4152,6 +4171,16 @@ export type Mutations =
|
|||||||
variables: DeleteWorkspaceMutationVariables;
|
variables: DeleteWorkspaceMutationVariables;
|
||||||
response: DeleteWorkspaceMutation;
|
response: DeleteWorkspaceMutation;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
name: 'disableUserMutation';
|
||||||
|
variables: DisableUserMutationVariables;
|
||||||
|
response: DisableUserMutation;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: 'enableUserMutation';
|
||||||
|
variables: EnableUserMutationVariables;
|
||||||
|
response: EnableUserMutation;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
name: 'generateLicenseKeyMutation';
|
name: 'generateLicenseKeyMutation';
|
||||||
variables: GenerateLicenseKeyMutationVariables;
|
variables: GenerateLicenseKeyMutationVariables;
|
||||||
|
|||||||
Reference in New Issue
Block a user