Skip to content

Commit 053ab7b

Browse files
authored
Merge branch 'main' into fix/dashboard-event-organizer-gatekeeper-form-crash
2 parents c21df79 + 5205c40 commit 053ab7b

11 files changed

Lines changed: 601 additions & 175 deletions

File tree

api/zenao/v1/zenao.proto

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ service ZenaoService {
4848
returns (GetCommunityAdministratorsResponse);
4949
rpc JoinCommunity(JoinCommunityRequest) returns (JoinCommunityResponse);
5050
rpc LeaveCommunity(LeaveCommunityRequest) returns (LeaveCommunityResponse);
51+
rpc RemoveCommunityMember(RemoveCommunityMemberRequest)
52+
returns (RemoveCommunityMemberResponse);
5153
rpc AddEventToCommunity(AddEventToCommunityRequest)
5254
returns (AddEventToCommunityResponse);
5355
rpc RemoveEventFromCommunity(RemoveEventFromCommunityRequest)
@@ -703,6 +705,12 @@ message JoinCommunityResponse {}
703705
message LeaveCommunityRequest { string community_id = 1; }
704706
message LeaveCommunityResponse {}
705707

708+
message RemoveCommunityMemberRequest {
709+
string community_id = 1;
710+
string user_id = 2;
711+
}
712+
message RemoveCommunityMemberResponse {}
713+
706714
message AddEventToCommunityRequest {
707715
string community_id = 1;
708716
string event_id = 2;
Lines changed: 102 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,114 @@
11
import { ColumnDef } from "@tanstack/react-table";
2+
import { Trash2 } from "lucide-react";
23
import { useMemo } from "react";
34
import { useTranslations } from "next-intl";
5+
import { Button } from "@/components/shadcn/button";
6+
import {
7+
Tooltip,
8+
TooltipContent,
9+
TooltipTrigger,
10+
} from "@/components/shadcn/tooltip";
411
import { UserAvatarWithName } from "@/components/features/user/user";
512
import { SafeCommunityEntityWithRoles } from "@/types/schemas";
613
import { DataTableColumnHeader } from "@/components/widgets/data-table/data-table-column-header";
714

8-
export const useMembersColumns =
9-
(): ColumnDef<SafeCommunityEntityWithRoles>[] => {
10-
const t = useTranslations("dashboard.membersTable");
15+
interface UseMembersColumnsProps {
16+
onDelete?: (userId: string) => void;
17+
}
1118

12-
const columns = useMemo<ColumnDef<SafeCommunityEntityWithRoles>[]>(
13-
() => [
14-
{
15-
id: "name",
16-
header: ({ column }) => (
17-
<DataTableColumnHeader column={column} title={t("name-column")} />
18-
),
19-
cell: ({ row }) => {
20-
if (row.original.entityType !== "user") {
21-
return <div className="px-4">Unknown Entity</div>;
22-
}
19+
export const useMembersColumns = (
20+
props?: UseMembersColumnsProps,
21+
): ColumnDef<SafeCommunityEntityWithRoles>[] => {
22+
const t = useTranslations("dashboard.membersTable");
2323

24-
return (
25-
<div className="px-4">
26-
<UserAvatarWithName
27-
userId={row.original.entityId}
28-
className="h-10"
29-
linkToProfile
30-
/>
31-
</div>
32-
);
33-
},
34-
enableHiding: false,
35-
enableSorting: true,
36-
},
37-
{
38-
accessorKey: "Roles",
39-
header: ({ column }) => (
40-
<DataTableColumnHeader column={column} title={t("roles-column")} />
41-
),
42-
cell: ({ row }) => (
43-
<div className="flex flex-wrap gap-1">
44-
{row.original.roles.map((role) => (
45-
<span
46-
key={role}
47-
className="rounded-md bg-secondary px-2 py-1 text-xs font-medium text-secondary-color"
48-
>
49-
{role}
50-
</span>
51-
))}
24+
const columns = useMemo<ColumnDef<SafeCommunityEntityWithRoles>[]>(
25+
() => [
26+
{
27+
id: "name",
28+
header: ({ column }) => (
29+
<DataTableColumnHeader column={column} title={t("name-column")} />
30+
),
31+
cell: ({ row }) => {
32+
if (row.original.entityType !== "user") {
33+
return <div className="px-4">Unknown Entity</div>;
34+
}
35+
36+
return (
37+
<div className="px-4">
38+
<UserAvatarWithName
39+
userId={row.original.entityId}
40+
className="h-10"
41+
linkToProfile
42+
/>
5243
</div>
53-
),
54-
enableSorting: true,
44+
);
5545
},
56-
],
57-
[t],
58-
);
46+
enableHiding: false,
47+
enableSorting: true,
48+
},
49+
{
50+
accessorKey: "Roles",
51+
header: ({ column }) => (
52+
<DataTableColumnHeader column={column} title={t("roles-column")} />
53+
),
54+
cell: ({ row }) => (
55+
<div className="flex flex-wrap gap-1">
56+
{row.original.roles.map((role) => (
57+
<span
58+
key={role}
59+
className="rounded-md bg-secondary px-2 py-1 text-xs font-medium text-secondary-color"
60+
>
61+
{role}
62+
</span>
63+
))}
64+
</div>
65+
),
66+
enableSorting: true,
67+
},
68+
...(props?.onDelete
69+
? [
70+
{
71+
id: "actions",
72+
header: () => <div>{t("actions-column")}</div>,
73+
cell: ({
74+
row,
75+
}: {
76+
row: { original: SafeCommunityEntityWithRoles };
77+
}) => {
78+
if (row.original.roles.includes("administrator")) {
79+
return null;
80+
}
81+
return (
82+
<Tooltip delayDuration={300}>
83+
<TooltipTrigger asChild>
84+
<Button
85+
variant="ghost"
86+
className="p-0 size-8"
87+
onClick={(e) => {
88+
e.stopPropagation();
89+
props.onDelete!(row.original.entityId);
90+
}}
91+
>
92+
<Trash2 className="text-muted-foreground w-4 h-4" />
93+
</Button>
94+
</TooltipTrigger>
95+
<TooltipContent>
96+
{t("action-tooltip-delete")}
97+
</TooltipContent>
98+
</Tooltip>
99+
);
100+
},
101+
enableHiding: false,
102+
enableSorting: false,
103+
meta: {
104+
className: "flex justify-end items-center px-4",
105+
},
106+
} satisfies ColumnDef<SafeCommunityEntityWithRoles>,
107+
]
108+
: []),
109+
],
110+
[t, props],
111+
);
59112

60-
return columns;
61-
};
113+
return columns;
114+
};

app/(dashboard)/dashboard/community/[id]/members/members-table.tsx

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import { parseAsInteger, useQueryStates } from "nuqs";
44
import { useSuspenseQuery } from "@tanstack/react-query";
55
import { useTranslations } from "next-intl";
6+
import { useCallback, useState } from "react";
7+
import { useAuth } from "@clerk/nextjs";
68
import { useMembersColumns } from "./columns";
79
import { DataTable as DataTableNew } from "@/components/widgets/data-table/data-table";
810
import { useDataTableInstance } from "@/hooks/use-data-table-instance";
@@ -13,14 +15,59 @@ import {
1315
import Text from "@/components/widgets/texts/text";
1416
import { DataTablePagination } from "@/components/widgets/data-table/data-table-pagination";
1517
import { useDashboardCommunityContext } from "@/components/providers/dashboard-community-context-provider";
18+
import { useRemoveCommunityMember } from "@/lib/mutations/community-remove-member";
19+
import { useToast } from "@/hooks/use-toast";
20+
import { captureException } from "@/lib/report";
21+
import ConfirmationDialog from "@/components/dialogs/confirmation-dialog";
1622

1723
export default function MembersTable() {
1824
const t = useTranslations("dashboard.communityDetails.members");
19-
const { communityId } = useDashboardCommunityContext();
25+
const { communityId, roles } = useDashboardCommunityContext();
26+
const { getToken } = useAuth();
27+
const { toast } = useToast();
28+
29+
const isAdmin = roles.includes("administrator");
30+
2031
const { data: members } = useSuspenseQuery(
2132
communityUsersWithRoles(communityId, ["member"]),
2233
);
2334

35+
const { removeCommunityMember, isPending } = useRemoveCommunityMember();
36+
const [pendingDeleteUserId, setPendingDeleteUserId] = useState<string | null>(
37+
null,
38+
);
39+
40+
const onDelete = useCallback((userId: string) => {
41+
setPendingDeleteUserId(userId);
42+
}, []);
43+
44+
const handleConfirmDelete = useCallback(async () => {
45+
if (!pendingDeleteUserId) return;
46+
try {
47+
await removeCommunityMember({
48+
communityId,
49+
userId: pendingDeleteUserId,
50+
getToken,
51+
});
52+
toast({ title: t("toast-remove-member-success") });
53+
} catch (err) {
54+
captureException(err);
55+
toast({
56+
variant: "destructive",
57+
title: t("toast-remove-member-error"),
58+
});
59+
} finally {
60+
setPendingDeleteUserId(null);
61+
}
62+
}, [
63+
communityId,
64+
getToken,
65+
pendingDeleteUserId,
66+
removeCommunityMember,
67+
t,
68+
toast,
69+
]);
70+
2471
const [tablePagination, setTablePagination] = useQueryStates(
2572
{
2673
page: parseAsInteger.withDefault(1),
@@ -32,7 +79,7 @@ export default function MembersTable() {
3279
},
3380
);
3481

35-
const columns = useMembersColumns();
82+
const columns = useMembersColumns(isAdmin ? { onDelete } : undefined);
3683
const table = useDataTableInstance({
3784
data: members,
3885
columns,
@@ -88,6 +135,15 @@ export default function MembersTable() {
88135
}}
89136
table={table}
90137
/>
138+
<ConfirmationDialog
139+
open={!!pendingDeleteUserId}
140+
onOpenChange={(open) => !open && setPendingDeleteUserId(null)}
141+
title={t("confirm-remove-member-title")}
142+
description={t("confirm-remove-member-description")}
143+
confirmText={t("confirm-remove-member-confirm")}
144+
onConfirm={handleConfirmDelete}
145+
isPending={isPending}
146+
/>
91147
</div>
92148
);
93149
}

app/gen/zenao/v1/zenao_pb.ts

Lines changed: 69 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/i18n/messages/en.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,9 @@
836836
},
837837
"membersTable": {
838838
"name-column": "Name",
839-
"roles-column": "Roles"
839+
"roles-column": "Roles",
840+
"actions-column": "Actions",
841+
"action-tooltip-delete": "Remove member"
840842
},
841843
"eventsTable": {
842844
"events": "Events",
@@ -896,7 +898,12 @@
896898
}
897899
},
898900
"members": {
899-
"no-members": "No members yet."
901+
"no-members": "No members yet.",
902+
"toast-remove-member-success": "Member removed successfully!",
903+
"toast-remove-member-error": "Error while removing member!",
904+
"confirm-remove-member-title": "Remove member",
905+
"confirm-remove-member-description": "Are you sure you want to remove this member from the community?",
906+
"confirm-remove-member-confirm": "Remove"
900907
},
901908
"events": {
902909
"no-events": "No events yet."

app/i18n/messages/es.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,9 @@
835835
},
836836
"membersTable": {
837837
"name-column": "Nombre",
838-
"roles-column": "Roles"
838+
"roles-column": "Roles",
839+
"actions-column": "Acciones",
840+
"action-tooltip-delete": "Eliminar miembro"
839841
},
840842
"eventsTable": {
841843
"events": "Eventos",
@@ -895,7 +897,12 @@
895897
}
896898
},
897899
"members": {
898-
"no-members": "Aún no hay miembros."
900+
"no-members": "Aún no hay miembros.",
901+
"toast-remove-member-success": "¡Miembro eliminado con éxito!",
902+
"toast-remove-member-error": "¡Error al eliminar el miembro!",
903+
"confirm-remove-member-title": "Eliminar miembro",
904+
"confirm-remove-member-description": "¿Estás seguro de que deseas eliminar este miembro de la comunidad?",
905+
"confirm-remove-member-confirm": "Eliminar"
899906
},
900907
"events": {
901908
"no-events": "Aún no hay eventos."

app/i18n/messages/fr.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,9 @@
836836
},
837837
"membersTable": {
838838
"name-column": "Nom",
839-
"roles-column": "Rôles"
839+
"roles-column": "Rôles",
840+
"actions-column": "Actions",
841+
"action-tooltip-delete": "Retirer le membre"
840842
},
841843
"eventsTable": {
842844
"events": "Événements",
@@ -896,7 +898,12 @@
896898
}
897899
},
898900
"members": {
899-
"no-members": "Aucun membre pour le moment."
901+
"no-members": "Aucun membre pour le moment.",
902+
"toast-remove-member-success": "Membre retiré avec succès !",
903+
"toast-remove-member-error": "Erreur lors du retrait du membre !",
904+
"confirm-remove-member-title": "Retirer le membre",
905+
"confirm-remove-member-description": "Êtes-vous sûr de vouloir retirer ce membre de la communauté ?",
906+
"confirm-remove-member-confirm": "Retirer"
900907
},
901908
"events": {
902909
"no-events": "Aucun événement pour le moment."

0 commit comments

Comments
 (0)