[feat] Group Admin can remove settle-up member.
This commit is contained in:
parent
92ae97a57b
commit
e9854e3cb7
@ -53,6 +53,14 @@ const BalancePage: NextPageWithUser<{
|
|||||||
const expensesQuery = api.group.getExpenses.useQuery({ groupId });
|
const expensesQuery = api.group.getExpenses.useQuery({ groupId });
|
||||||
const deleteGroupMutation = api.group.delete.useMutation();
|
const deleteGroupMutation = api.group.delete.useMutation();
|
||||||
const leaveGroupMutation = api.group.leaveGroup.useMutation();
|
const leaveGroupMutation = api.group.leaveGroup.useMutation();
|
||||||
|
const removeMemberMutation = api.group.removeMember.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
void groupDetailQuery.refetch();
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
toast.error(err.message ?? 'Failed to remove member');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const [isInviteCopied, setIsInviteCopied] = useState(false);
|
const [isInviteCopied, setIsInviteCopied] = useState(false);
|
||||||
const [showDeleteTrigger, setShowDeleteTrigger] = useState(false);
|
const [showDeleteTrigger, setShowDeleteTrigger] = useState(false);
|
||||||
@ -188,15 +196,54 @@ const BalancePage: NextPageWithUser<{
|
|||||||
<div className="">
|
<div className="">
|
||||||
<p className="font-semibold">Members</p>
|
<p className="font-semibold">Members</p>
|
||||||
<div className="mt-2 flex flex-col gap-2">
|
<div className="mt-2 flex flex-col gap-2">
|
||||||
{groupDetailQuery.data?.groupUsers.map((groupUser) => (
|
{groupDetailQuery.data?.groupUsers.map((groupUser) => {
|
||||||
<div
|
const hasBalance = groupDetailQuery.data?.groupBalances.some(
|
||||||
key={groupUser.userId}
|
(b) => b.userId === groupUser.userId && b.amount !== 0
|
||||||
className={clsx('flex items-center gap-2 rounded-md py-1.5')}
|
);
|
||||||
>
|
const canDeleteUser =
|
||||||
<UserAvatar user={groupUser.user} />
|
isAdmin && groupUser.userId !== user.id && !hasBalance;
|
||||||
<p>{groupUser.user.name ?? groupUser.user.email}</p>
|
|
||||||
</div>
|
return (
|
||||||
))}
|
<div
|
||||||
|
key={groupUser.userId}
|
||||||
|
className="flex items-center justify-between gap-2 rounded-md py-1.5"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<UserAvatar user={groupUser.user} />
|
||||||
|
<p>{groupUser.user.name ?? groupUser.user.email}</p>
|
||||||
|
</div>
|
||||||
|
{canDeleteUser && (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<button>
|
||||||
|
<Trash2 className="h-4 w-4 text-red-500 hover:opacity-70" />
|
||||||
|
</button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent className="max-w-xs rounded-lg">
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Remove member?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
This action cannot be undone. The user will be removed from this group.
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() =>
|
||||||
|
removeMemberMutation.mutate({ groupId, userId: groupUser.userId })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{groupDetailQuery?.data?.createdAt && (
|
{groupDetailQuery?.data?.createdAt && (
|
||||||
|
|||||||
@ -350,4 +350,47 @@ export const groupRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return group;
|
return group;
|
||||||
}),
|
}),
|
||||||
});
|
|
||||||
|
removeMember: groupProcedure
|
||||||
|
.input(z.object({ groupId: z.number(), userId: z.number() }))
|
||||||
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
const { groupId, userId } = input;
|
||||||
|
|
||||||
|
// 確保呼叫者是 group 擁有者
|
||||||
|
const group = await ctx.db.group.findUnique({
|
||||||
|
where: { id: groupId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (group?.userId !== ctx.session.user.id) {
|
||||||
|
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Only admin can remove members' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId === ctx.session.user.id) {
|
||||||
|
throw new TRPCError({ code: 'BAD_REQUEST', message: 'You cannot remove yourself' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const balance = await ctx.db.groupBalance.findFirst({
|
||||||
|
where: {
|
||||||
|
groupId,
|
||||||
|
userId,
|
||||||
|
amount: { not: 0 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (balance) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'BAD_REQUEST',
|
||||||
|
message: 'This member has unsettled balance and cannot be removed',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.db.groupUser.delete({
|
||||||
|
where: {
|
||||||
|
groupId_userId: { groupId, userId },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
}),
|
||||||
|
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user