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>
);
},
},
];