Skip to main content
Glama
columns.tsx8.68 kB
import { useMutation, useQuery } from '@tanstack/react-query'; import { ColumnDef } from '@tanstack/react-table'; import { t } from 'i18next'; import { ChevronDown, Info, Trash2 } from 'lucide-react'; import { toast } from 'sonner'; import { PermissionNeededTooltip } from '@/components/custom/permission-needed-tooltip'; import { ConfirmationDeleteDialog } from '@/components/delete-dialog'; import { Button } from '@/components/ui/button'; import { RowDataWithActions } from '@/components/ui/data-table'; import { DataTableColumnHeader } from '@/components/ui/data-table/data-table-column-header'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { internalErrorToast } from '@/components/ui/sonner'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@/components/ui/tooltip'; import { UserAvatar } from '@/components/ui/user-avatar'; import { projectMembersApi } from '@/features/members/lib/project-members-api'; import { userInvitationApi } from '@/features/members/lib/user-invitation'; import { projectRoleApi } from '@/features/platform-admin/lib/project-role-api'; import { useAuthorization } from '@/hooks/authorization-hooks'; import { projectHooks } from '@/hooks/project-hooks'; import { ProjectMemberWithUser } from '@activepieces/ee-shared'; import { Permission, UserInvitation } from '@activepieces/shared'; export type MemberRowData = | { id: string; type: 'member'; data: ProjectMemberWithUser; } | { id: string; type: 'invitation'; data: UserInvitation; }; type MembersTableColumnsProps = { refetch: () => void; }; const RoleCell = ({ row, refetch, }: { row: { original: MemberRowData }; refetch: () => void; }) => { const { data: rolesData } = useQuery({ queryKey: ['project-roles'], queryFn: () => projectRoleApi.list(), }); const roles = rolesData?.data ?? []; const { checkAccess } = useAuthorization(); const { project } = projectHooks.useCurrentProject(); const userHasPermissionToUpdateRole = checkAccess(Permission.WRITE_PROJECT_MEMBER) && row.original.type === 'member'; const isOwner = row.original.type === 'member' && project.ownerId === row.original.data.userId; const { mutate } = useMutation({ mutationFn: (newRole: string) => { if (row.original.type === 'member') { return projectMembersApi.update(row.original.data.id, { role: newRole, }); } return Promise.resolve(); }, onSuccess: () => { toast.success(t('Role updated successfully')); refetch(); }, onError: () => { internalErrorToast(); }, }); const handleValueChange = (value: string) => { mutate(value); }; const roleName = row.original.type === 'member' ? row.original.data.projectRole.name : row.original.data.projectRole?.name ?? ''; if (isOwner) { return <span className="text-sm">{roleName}</span>; } if (row.original.type === 'invitation') { return ( <div className="relative"> <TooltipProvider> <Tooltip> <TooltipTrigger asChild> <Info className="h-4 w-4 text-orange-700 absolute left-0 top-1/2 -translate-y-1/2 -translate-x-6" /> </TooltipTrigger> <TooltipContent> <p>{t('Pending Invitation')}</p> </TooltipContent> </Tooltip> </TooltipProvider> <Button variant="outline" className="w-[150px] justify-between cursor-not-allowed" disabled={true} > <span>{roleName}</span> <ChevronDown className="ml-2 h-4 w-4 opacity-50" /> </Button> </div> ); } return ( <PermissionNeededTooltip hasPermission={userHasPermissionToUpdateRole}> <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="outline" className="w-[150px] justify-between" disabled={!userHasPermissionToUpdateRole} > <span>{roleName}</span> <ChevronDown className="ml-2 h-4 w-4 opacity-50" /> </Button> </DropdownMenuTrigger> <DropdownMenuContent align="end" className="w-[150px]"> {roles.map((role) => ( <DropdownMenuItem key={role.name} onClick={(e) => { e.stopPropagation(); handleValueChange(role.name); }} > {role.name} </DropdownMenuItem> ))} </DropdownMenuContent> </DropdownMenu> </PermissionNeededTooltip> ); }; const ActionsCell = ({ row, refetch, }: { row: { original: MemberRowData }; refetch: () => void; }) => { const { checkAccess } = useAuthorization(); const { project } = projectHooks.useCurrentProject(); const userHasPermissionToDelete = row.original.type === 'member' ? checkAccess(Permission.WRITE_PROJECT_MEMBER) : checkAccess(Permission.WRITE_INVITATION); const isOwner = row.original.type === 'member' && project.ownerId === row.original.data.userId; const deleteMember = async () => { if (row.original.type === 'member') { await projectMembersApi.delete(row.original.data.id); } else { await userInvitationApi.delete(row.original.data.id); } refetch(); }; if (isOwner) { return null; } return ( <PermissionNeededTooltip hasPermission={userHasPermissionToDelete}> <ConfirmationDeleteDialog title={ row.original.type === 'invitation' ? t('Remove {email}', { email: row.original.data.email }) : `${t('Remove')} ${row.original.data.user.firstName} ${ row.original.data.user.lastName }` } message={ row.original.type === 'invitation' ? t('Are you sure you want to remove this invitation?') : t('Are you sure you want to remove this member?') } mutationFn={() => deleteMember()} entityName={ row.original.type === 'invitation' ? row.original.data.email : `${row.original.data.user.firstName} ${row.original.data.user.lastName}` } > <Button variant="ghost" size="sm" disabled={!userHasPermissionToDelete} className="h-8 w-8 p-0" > <Trash2 className="h-4 w-4 text-destructive" /> </Button> </ConfirmationDeleteDialog> </PermissionNeededTooltip> ); }; export const membersTableColumns = ({ refetch, }: MembersTableColumnsProps): (ColumnDef<RowDataWithActions<MemberRowData>> & { accessorKey: string; })[] => [ { accessorKey: 'member', header: ({ column }) => ( <DataTableColumnHeader column={column} title={t('User Name')} /> ), cell: ({ row }) => { if (row.original.type === 'invitation') { const email = row.original.data.email; return ( <div className="flex items-center space-x-4"> <UserAvatar name={email} email={email} size={32} disableTooltip={true} /> <div className="flex flex-col gap-1"> <p className="text-sm text-orange-700">{email}</p> </div> </div> ); } const email = row.original.data.user.email; const name = `${row.original.data.user.firstName} ${row.original.data.user.lastName}`; return ( <div className="flex items-center space-x-4"> <UserAvatar name={name} email={email} size={32} disableTooltip={true} /> <div className="flex flex-col gap-1"> <p className="text-sm font-medium leading-none">{name}</p> <p className="text-sm text-muted-foreground">{email}</p> </div> </div> ); }, }, { accessorKey: 'role', header: ({ column }) => ( <DataTableColumnHeader column={column} title={t('Role')} /> ), cell: ({ row }) => { return ( <div onClick={(e) => e.stopPropagation()}> <RoleCell row={row} refetch={refetch} /> </div> ); }, }, { accessorKey: 'actions', header: ({ column }) => <DataTableColumnHeader column={column} title="" />, cell: ({ row }) => { return ( <div onClick={(e) => e.stopPropagation()}> <ActionsCell row={row} refetch={refetch} /> </div> ); }, }, ];

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/activepieces/activepieces'

If you have feedback or need assistance with the MCP directory API, please join our Discord server