Skip to content

Commit e8047b6

Browse files
[WEB-5671] chore: settings workspace members enhancements #8346
1 parent fa63964 commit e8047b6

File tree

7 files changed

+133
-76
lines changed

7 files changed

+133
-76
lines changed

apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/members/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const WorkspaceMembersSettingsPage = observer(function WorkspaceMembersSettingsP
4242
const {
4343
workspace: { workspaceMemberIds, inviteMembersToWorkspace, filtersStore },
4444
} = useMember();
45-
const { currentWorkspace } = useWorkspace();
45+
const { currentWorkspace, mutateWorkspaceMembersActivity } = useWorkspace();
4646
const { t } = useTranslation();
4747

4848
// derived values
@@ -55,6 +55,7 @@ const WorkspaceMembersSettingsPage = observer(function WorkspaceMembersSettingsP
5555
const handleWorkspaceInvite = async (data: IWorkspaceBulkInviteFormData) => {
5656
try {
5757
await inviteMembersToWorkspace(workspaceSlug, data);
58+
void mutateWorkspaceMembersActivity(workspaceSlug);
5859

5960
setInviteModal(false);
6061

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// store
2+
import { BaseWorkspaceRootStore } from "@/store/workspace";
3+
import type { RootStore } from "@/plane-web/store/root.store";
4+
5+
export class WorkspaceRootStore extends BaseWorkspaceRootStore {
6+
constructor(_rootStore: RootStore) {
7+
super(_rootStore);
8+
}
9+
10+
// actions
11+
/**
12+
* Mutate workspace members activity
13+
* @param workspaceSlug
14+
*/
15+
mutateWorkspaceMembersActivity = async (_workspaceSlug: string) => {
16+
// No-op in default/CE version
17+
};
18+
}

apps/web/core/components/workspace/settings/invitations-list-item.tsx

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { ConfirmWorkspaceMemberRemove } from "@/components/workspace/confirm-wor
1616
import { captureClick } from "@/helpers/event-tracker.helper";
1717
import { useMember } from "@/hooks/store/use-member";
1818
import { useUserPermissions } from "@/hooks/store/user";
19+
import { useWorkspace } from "@/hooks/store/use-workspace";
1920

2021
type Props = {
2122
invitationId: string;
@@ -31,6 +32,7 @@ export const WorkspaceInvitationsListItem = observer(function WorkspaceInvitatio
3132
const { t } = useTranslation();
3233
// store hooks
3334
const { allowPermissions, workspaceInfoBySlug } = useUserPermissions();
35+
const { mutateWorkspaceMembersActivity } = useWorkspace();
3436
const {
3537
workspace: { updateMemberInvitation, deleteMemberInvitation, getWorkspaceInvitationDetails },
3638
} = useMember();
@@ -50,36 +52,36 @@ export const WorkspaceInvitationsListItem = observer(function WorkspaceInvitatio
5052
);
5153

5254
const handleRemoveInvitation = async () => {
53-
if (!workspaceSlug || !invitationDetails) return;
55+
try {
56+
if (!workspaceSlug || !invitationDetails) return;
5457

55-
await deleteMemberInvitation(workspaceSlug.toString(), invitationDetails.id)
56-
.then(() => {
57-
setToast({
58-
type: TOAST_TYPE.SUCCESS,
59-
title: "Success!",
60-
message: "Invitation removed successfully.",
61-
});
62-
})
63-
.catch((err) =>
64-
setToast({
65-
type: TOAST_TYPE.ERROR,
66-
title: "Error!",
67-
message: err?.error || "Something went wrong. Please try again.",
68-
})
69-
);
58+
await deleteMemberInvitation(workspaceSlug.toString(), invitationDetails.id);
59+
setToast({
60+
type: TOAST_TYPE.SUCCESS,
61+
title: "Success!",
62+
message: "Invitation removed successfully.",
63+
});
64+
void mutateWorkspaceMembersActivity(workspaceSlug);
65+
} catch (err: unknown) {
66+
const error = err as { error?: string };
67+
setToast({
68+
type: TOAST_TYPE.ERROR,
69+
title: "Error!",
70+
message: error?.error || "Something went wrong. Please try again.",
71+
});
72+
}
7073
};
7174

7275
if (!invitationDetails || !currentWorkspaceMemberInfo) return null;
7376

74-
const handleCopyText = () => {
77+
const handleCopyText = async () => {
7578
try {
7679
const inviteLink = new URL(invitationDetails.invite_link, window.location.origin).href;
77-
copyTextToClipboard(inviteLink).then(() => {
78-
setToast({
79-
type: TOAST_TYPE.SUCCESS,
80-
title: t("common.link_copied"),
81-
message: t("entity.link_copied_to_clipboard", { entity: t("common.invite") }),
82-
});
80+
await copyTextToClipboard(inviteLink);
81+
setToast({
82+
type: TOAST_TYPE.SUCCESS,
83+
title: t("common.link_copied"),
84+
message: t("entity.link_copied_to_clipboard", { entity: t("common.invite") }),
8385
});
8486
} catch (error) {
8587
console.error("Error generating invite link:", error);
@@ -89,7 +91,7 @@ export const WorkspaceInvitationsListItem = observer(function WorkspaceInvitatio
8991
const MENU_ITEMS: TContextMenuItem[] = [
9092
{
9193
key: "copy-link",
92-
action: handleCopyText,
94+
action: () => void handleCopyText(),
9395
title: t("common.actions.copy_link"),
9496
icon: LinkIcon,
9597
shouldRender: !!invitationDetails.invite_link,
@@ -157,7 +159,8 @@ export const WorkspaceInvitationsListItem = observer(function WorkspaceInvitatio
157159

158160
updateMemberInvitation(workspaceSlug.toString(), invitationDetails.id, {
159161
role: value,
160-
}).catch((error) => {
162+
}).catch((err: unknown) => {
163+
const error = err as { error?: string };
161164
setToast({
162165
type: TOAST_TYPE.ERROR,
163166
title: "Error!",
@@ -169,7 +172,11 @@ export const WorkspaceInvitationsListItem = observer(function WorkspaceInvitatio
169172
placement="bottom-end"
170173
>
171174
{Object.keys(ROLE).map((key) => {
172-
if (currentWorkspaceRole && currentWorkspaceRole !== 20 && currentWorkspaceRole < parseInt(key))
175+
if (
176+
currentWorkspaceRole &&
177+
Number(currentWorkspaceRole) !== 20 &&
178+
Number(currentWorkspaceRole) < parseInt(key)
179+
)
173180
return null;
174181

175182
return (

apps/web/core/components/workspace/settings/member-columns.tsx

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { getFileURL } from "@plane/utils";
1616
// hooks
1717
import { useMember } from "@/hooks/store/use-member";
1818
import { useUser, useUserPermissions } from "@/hooks/store/user";
19+
import { useWorkspace } from "@/hooks/store/use-workspace";
1920
// plane web constants
2021

2122
export interface RowData {
@@ -45,7 +46,7 @@ export function NameColumn(props: NameProps) {
4546

4647
return (
4748
<Disclosure>
48-
{({}) => (
49+
{() => (
4950
<div className="relative group">
5051
<div className="flex items-center gap-x-4 gap-y-2 w-72 justify-between">
5152
<div className="flex items-center gap-x-2 gap-y-2 flex-1">
@@ -83,8 +84,16 @@ export function NameColumn(props: NameProps) {
8384
buttonClassName="outline-none origin-center rotate-90 size-8 aspect-square flex-shrink-0 grid place-items-center opacity-0 group-hover:opacity-100 transition-opacity"
8485
render={() => (
8586
<div
87+
role="button"
88+
tabIndex={0}
8689
className="flex items-center gap-x-3 cursor-pointer"
8790
onClick={() => setRemoveMemberModal(rowData)}
91+
onKeyDown={(e) => {
92+
if (e.key === "Enter" || e.key === " ") {
93+
e.preventDefault();
94+
setRemoveMemberModal(rowData);
95+
}
96+
}}
8897
data-ph-element={MEMBER_TRACKER_ELEMENTS.WORKSPACE_MEMBER_TABLE_CONTEXT_MENU}
8998
>
9099
<Trash2 className="size-3.5 align-middle" /> {id === currentUser?.id ? "Leave " : "Remove "}
@@ -112,6 +121,7 @@ export const AccountTypeColumn = observer(function AccountTypeColumn(props: Acco
112121
const {
113122
workspace: { updateMember },
114123
} = useMember();
124+
const { mutateWorkspaceMembersActivity } = useWorkspace();
115125
const { data: currentUser } = useUser();
116126

117127
// derived values
@@ -139,22 +149,24 @@ export const AccountTypeColumn = observer(function AccountTypeColumn(props: Acco
139149
rules={{ required: "Role is required." }}
140150
render={({ field: { value } }) => (
141151
<CustomSelect
142-
value={value}
143-
onChange={(value: EUserPermissions) => {
152+
value={value as EUserPermissions}
153+
onChange={async (value: EUserPermissions) => {
144154
if (!workspaceSlug) return;
145-
updateMember(workspaceSlug.toString(), rowData.member.id, {
146-
role: value as unknown as EUserPermissions, // Cast value to unknown first, then to EUserPermissions
147-
}).catch((err) => {
148-
console.log(err, "err");
149-
const error = err.error;
150-
const errorString = Array.isArray(error) ? error[0] : error;
155+
try {
156+
await updateMember(workspaceSlug.toString(), rowData.member.id, {
157+
role: value as unknown as EUserPermissions,
158+
});
159+
void mutateWorkspaceMembersActivity(workspaceSlug);
160+
} catch (err: unknown) {
161+
const error = err as { error?: string | string[] };
162+
const errorString = Array.isArray(error?.error) ? error.error[0] : error?.error;
151163

152164
setToast({
153165
type: TOAST_TYPE.ERROR,
154166
title: "Error!",
155167
message: errorString ?? "An error occurred while updating member role. Please try again.",
156168
});
157-
});
169+
}
158170
}}
159171
label={
160172
<div className="flex ">

apps/web/core/components/workspace/settings/members-list-item.tsx

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Table } from "@plane/ui";
99
// components
1010
import { MembersLayoutLoader } from "@/components/ui/loader/layouts/members-layout-loader";
1111
import { ConfirmWorkspaceMemberRemove } from "@/components/workspace/confirm-workspace-member-remove";
12+
import type { RowData } from "@/components/workspace/settings/member-columns";
1213
// helpers
1314
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
1415
// hooks
@@ -34,51 +35,56 @@ export const WorkspaceMembersListItem = observer(function WorkspaceMembersListIt
3435
workspace: { removeMemberFromWorkspace },
3536
} = useMember();
3637
const { leaveWorkspace } = useUserPermissions();
37-
const { getWorkspaceRedirectionUrl } = useWorkspace();
38+
const { getWorkspaceRedirectionUrl, mutateWorkspaceMembersActivity } = useWorkspace();
3839
const { fetchCurrentUserSettings } = useUserSettings();
3940
const { t } = useTranslation();
4041
// derived values
4142

4243
const handleLeaveWorkspace = async () => {
4344
if (!workspaceSlug || !currentUser) return;
4445

45-
await leaveWorkspace(workspaceSlug.toString())
46-
.then(async () => {
47-
await fetchCurrentUserSettings();
48-
router.push(getWorkspaceRedirectionUrl());
49-
captureSuccess({
50-
eventName: MEMBER_TRACKER_EVENTS.workspace.leave,
51-
payload: {
52-
workspace: workspaceSlug,
53-
},
54-
});
55-
})
56-
.catch((err: any) => {
57-
captureError({
58-
eventName: MEMBER_TRACKER_EVENTS.workspace.leave,
59-
payload: {
60-
workspace: workspaceSlug,
61-
},
62-
error: err,
63-
});
64-
setToast({
65-
type: TOAST_TYPE.ERROR,
66-
title: "Error!",
67-
message: err?.error || t("something_went_wrong_please_try_again"),
68-
});
46+
try {
47+
await leaveWorkspace(workspaceSlug.toString());
48+
await fetchCurrentUserSettings();
49+
router.push(getWorkspaceRedirectionUrl());
50+
captureSuccess({
51+
eventName: MEMBER_TRACKER_EVENTS.workspace.leave,
52+
payload: {
53+
workspace: workspaceSlug,
54+
},
6955
});
56+
} catch (err: unknown) {
57+
const error = err as { error?: string };
58+
const errorForCapture: Error | string = err instanceof Error ? err : String(err);
59+
captureError({
60+
eventName: MEMBER_TRACKER_EVENTS.workspace.leave,
61+
payload: {
62+
workspace: workspaceSlug,
63+
},
64+
error: errorForCapture,
65+
});
66+
setToast({
67+
type: TOAST_TYPE.ERROR,
68+
title: "Error!",
69+
message: error?.error || t("something_went_wrong_please_try_again"),
70+
});
71+
}
7072
};
7173

7274
const handleRemoveMember = async (memberId: string) => {
7375
if (!workspaceSlug || !memberId) return;
7476

75-
await removeMemberFromWorkspace(workspaceSlug.toString(), memberId).catch((err) =>
77+
try {
78+
await removeMemberFromWorkspace(workspaceSlug.toString(), memberId);
79+
void mutateWorkspaceMembersActivity(workspaceSlug);
80+
} catch (err: unknown) {
81+
const error = err as { error?: string };
7682
setToast({
7783
type: TOAST_TYPE.ERROR,
7884
title: "Error!",
79-
message: err?.error || t("something_went_wrong_please_try_again"),
80-
})
81-
);
85+
message: error?.error || t("something_went_wrong_please_try_again"),
86+
});
87+
}
8288
};
8389

8490
const handleRemove = async (memberId: string) => {
@@ -109,9 +115,11 @@ export const WorkspaceMembersListItem = observer(function WorkspaceMembersListIt
109115
onSubmit={() => handleRemove(removeMemberModal.member.id)}
110116
/>
111117
)}
112-
<Table
118+
<Table<RowData>
113119
columns={columns ?? []}
114-
data={(memberDetails?.filter((member): member is IWorkspaceMember => member !== null) ?? []) as any}
120+
data={
121+
(memberDetails?.filter((member): member is IWorkspaceMember => member !== null) ?? []) as unknown as RowData[]
122+
}
115123
keyExtractor={(rowData) => rowData?.member.id ?? ""}
116124
tHeadClassName="border-b border-subtle"
117125
thClassName="text-left font-medium divide-x-0 text-placeholder"

apps/web/core/store/root.store.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { IPowerKStore } from "@/plane-web/store/power-k.store";
1313
import type { RootStore } from "@/plane-web/store/root.store";
1414
import type { IStateStore } from "@/plane-web/store/state.store";
1515
import { StateStore } from "@/plane-web/store/state.store";
16+
import { WorkspaceRootStore } from "@/plane-web/store/workspace";
1617
// stores
1718
import type { ICycleStore } from "./cycle.store";
1819
import { CycleStore } from "./cycle.store";
@@ -61,7 +62,6 @@ import { ThemeStore } from "./theme.store";
6162
import type { IUserStore } from "./user";
6263
import { UserStore } from "./user";
6364
import type { IWorkspaceRootStore } from "./workspace";
64-
import { WorkspaceRootStore } from "./workspace";
6565

6666
enableStaticRendering(typeof window === "undefined");
6767

@@ -102,7 +102,7 @@ export class CoreRootStore {
102102
this.instance = new InstanceStore();
103103
this.user = new UserStore(this as unknown as RootStore);
104104
this.theme = new ThemeStore();
105-
this.workspaceRoot = new WorkspaceRootStore(this);
105+
this.workspaceRoot = new WorkspaceRootStore(this as unknown as RootStore);
106106
this.projectRoot = new ProjectRootStore(this);
107107
this.memberRoot = new MemberRootStore(this as unknown as RootStore);
108108
this.cycle = new CycleStore(this);
@@ -136,7 +136,7 @@ export class CoreRootStore {
136136
this.commandPalette = new CommandPaletteStore();
137137
this.instance = new InstanceStore();
138138
this.user = new UserStore(this as unknown as RootStore);
139-
this.workspaceRoot = new WorkspaceRootStore(this);
139+
this.workspaceRoot = new WorkspaceRootStore(this as unknown as RootStore);
140140
this.projectRoot = new ProjectRootStore(this);
141141
this.memberRoot = new MemberRootStore(this as unknown as RootStore);
142142
this.cycle = new CycleStore(this);

0 commit comments

Comments
 (0)