Skip to content

Commit 8a77916

Browse files
authored
fix(sdk): show teams in project member screen (#410)
* fix: refetch members list after adding to the team * feat: show project groups in member list table * feat: show team count in project list page * fix: hardcode team role in project
1 parent 623bfea commit 8a77916

File tree

7 files changed

+243
-111
lines changed

7 files changed

+243
-111
lines changed
Lines changed: 5 additions & 0 deletions
Loading

sdks/js/packages/core/react/components/organization/project/members/index.tsx

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,19 @@ import { AuthTooltipMessage } from '~/react/utils';
3535
import { useOrganizationTeams } from '~/react/hooks/useOrganizationTeams';
3636

3737
export type MembersProps = {
38+
teams?: V1Beta1Group[];
3839
members?: V1Beta1User[];
3940
memberRoles?: Record<string, Role[]>;
4041
isLoading?: boolean;
42+
refetch: () => void;
4143
};
4244

4345
export const Members = ({
44-
members,
46+
teams = [],
47+
members = [],
4548
memberRoles,
46-
isLoading: isMemberLoading
49+
isLoading: isMemberLoading,
50+
refetch
4751
}: MembersProps) => {
4852
const { projectId } = useParams({ from: '/projects/$projectId' });
4953

@@ -72,9 +76,13 @@ export const Members = ({
7276
};
7377
}, [permissions, resource]);
7478

75-
const tableStyle = members?.length
76-
? { width: '100%' }
77-
: { width: '100%', height: '100%' };
79+
const tableStyle = useMemo(
80+
() =>
81+
members?.length || teams?.length
82+
? { width: '100%' }
83+
: { width: '100%', height: '100%' },
84+
[members?.length, teams?.length]
85+
);
7886

7987
const isLoading = isMemberLoading || isPermissionsFetching;
8088

@@ -84,12 +92,13 @@ export const Members = ({
8492
);
8593

8694
const updatedUsers = useMemo(() => {
95+
const updatedTeams = teams.map(t => ({ ...t, isTeam: true }));
8796
return isLoading
8897
? ([{ id: 1 }, { id: 2 }, { id: 3 }] as any)
89-
: members?.length
90-
? members
98+
: members?.length || updatedTeams?.length
99+
? [...updatedTeams, ...members]
91100
: [];
92-
}, [members, isLoading]);
101+
}, [isLoading, members, teams]);
93102

94103
return (
95104
<Flex direction="column" style={{ paddingTop: '32px' }}>
@@ -116,7 +125,11 @@ export const Members = ({
116125
side="left"
117126
disabled={canUpdateProject}
118127
>
119-
<AddMemberDropdown canUpdateProject={canUpdateProject} />
128+
<AddMemberDropdown
129+
canUpdateProject={canUpdateProject}
130+
refetch={refetch}
131+
members={members}
132+
/>
120133
</Tooltip>
121134
)}
122135
</Flex>
@@ -129,11 +142,13 @@ export const Members = ({
129142
interface AddMemberDropdownProps {
130143
canUpdateProject: boolean;
131144
members?: V1Beta1User[];
145+
refetch?: () => void;
132146
}
133147

134148
const AddMemberDropdown = ({
135149
canUpdateProject,
136-
members
150+
members,
151+
refetch
137152
}: AddMemberDropdownProps) => {
138153
const { projectId } = useParams({ from: '/projects/$projectId' });
139154
const [orgMembers, setOrgMembers] = useState<V1Beta1User[]>([]);
@@ -226,14 +241,17 @@ const AddMemberDropdown = ({
226241
};
227242
await client?.frontierServiceCreatePolicy(policy);
228243
toast.success('member added');
244+
if (refetch) {
245+
refetch();
246+
}
229247
} catch ({ error }: any) {
230248
console.error(error);
231249
toast.error('Something went wrong', {
232250
description: error.message
233251
});
234252
}
235253
},
236-
[client, organization?.id, projectId]
254+
[client, organization?.id, projectId, refetch]
237255
);
238256

239257
const addTeam = useCallback(
@@ -250,14 +268,17 @@ const AddMemberDropdown = ({
250268
};
251269
await client?.frontierServiceCreatePolicy(policy);
252270
toast.success('team added');
271+
if (refetch) {
272+
refetch();
273+
}
253274
} catch ({ error }: any) {
254275
console.error(error);
255276
toast.error('Something went wrong', {
256277
description: error.message
257278
});
258279
}
259280
},
260-
[client, organization?.id, projectId]
281+
[client, organization?.id, projectId, refetch]
261282
);
262283

263284
return (

sdks/js/packages/core/react/components/organization/project/members/member.columns.tsx

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import { Avatar, Flex, Label, Text } from '@raystack/apsara';
22
import { ColumnDef } from '@tanstack/react-table';
33
import Skeleton from 'react-loading-skeleton';
4-
import { V1Beta1User } from '~/src';
4+
import { V1Beta1Group, V1Beta1User } from '~/src';
55
import { Role } from '~/src/types';
66
import { getInitials } from '~/utils';
7+
import teamIcon from '~/react/assets/users.svg';
8+
9+
type ColumnType = V1Beta1User & (V1Beta1Group & { isTeam: boolean });
10+
11+
const teamAvatarStyles = { height: '20px', width: '20px', padding: '6px' };
712

813
export const getColumns = (
914
memberRoles: Record<string, Role[]> = {},
1015
isLoading: boolean
11-
): ColumnDef<V1Beta1User, any>[] => [
16+
): ColumnDef<ColumnType, any>[] => [
1217
{
1318
header: '',
1419
accessorKey: 'image',
@@ -22,10 +27,17 @@ export const getColumns = (
2227
cell: isLoading
2328
? () => <Skeleton />
2429
: ({ row, getValue }) => {
30+
const avatarSrc = row.original?.isTeam ? teamIcon : getValue();
31+
const fallback = row.original?.isTeam
32+
? ''
33+
: getInitials(row.original?.title || row.original?.email);
34+
const imageProps = row.original?.isTeam ? teamAvatarStyles : {};
2535
return (
2636
<Avatar
27-
src={getValue()}
28-
fallback={getInitials(row.original?.title || row.original?.email)}
37+
src={avatarSrc}
38+
fallback={fallback}
39+
shape={'square'}
40+
imageProps={imageProps}
2941
// @ts-ignore
3042
style={{ marginRight: 'var(--mr-12)' }}
3143
/>
@@ -43,10 +55,15 @@ export const getColumns = (
4355
cell: isLoading
4456
? () => <Skeleton />
4557
: ({ row, getValue }) => {
58+
const label = row.original?.isTeam ? row.original.title : getValue();
59+
const subLabel = row.original?.isTeam
60+
? row.original.name
61+
: row.original.email;
62+
4663
return (
4764
<Flex direction="column" gap="extra-small">
48-
<Label style={{ fontWeight: '$500' }}>{getValue()}</Label>
49-
<Text>{row.original.email}</Text>
65+
<Label style={{ fontWeight: '$500' }}>{label}</Label>
66+
<Text>{subLabel}</Text>
5067
</Flex>
5168
);
5269
}
@@ -57,14 +74,15 @@ export const getColumns = (
5774
cell: isLoading
5875
? () => <Skeleton />
5976
: ({ row, getValue }) => {
60-
return (
61-
(row.original?.id &&
62-
memberRoles[row.original?.id] &&
63-
memberRoles[row.original?.id]
64-
.map((r: any) => r.title || r.name)
65-
.join(', ')) ??
66-
'Inherited role'
67-
);
77+
return row.original?.isTeam
78+
? // hardcoding roles as we dont have team roles and team are invited as viewer and we dont allow role change
79+
'Project Viewer'
80+
: (row.original?.id &&
81+
memberRoles[row.original?.id] &&
82+
memberRoles[row.original?.id]
83+
.map((r: any) => r.title || r.name)
84+
.join(', ')) ??
85+
'Inherited role';
6886
}
6987
}
7088
];

sdks/js/packages/core/react/components/organization/project/project.tsx

Lines changed: 82 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,27 @@ import {
77
useParams,
88
useRouterState
99
} from '@tanstack/react-router';
10-
import { useEffect, useMemo, useState } from 'react';
10+
import { useCallback, useEffect, useMemo, useState } from 'react';
1111
import { toast } from 'sonner';
1212
import backIcon from '~/react/assets/chevron-left.svg';
1313
import { useFrontier } from '~/react/contexts/FrontierContext';
14-
import { V1Beta1Project, V1Beta1User } from '~/src';
14+
import { V1Beta1Group, V1Beta1Project, V1Beta1User } from '~/src';
1515
import { Role } from '~/src/types';
1616
import { styles } from '../styles';
1717
import { General } from './general';
1818
import { Members } from './members';
1919

2020
export const ProjectPage = () => {
2121
let { projectId } = useParams({ from: '/projects/$projectId' });
22-
const [isLoading, setIsLoading] = useState(false);
22+
const [isProjectLoading, setIsProjectLoading] = useState(false);
2323
const [project, setProject] = useState<V1Beta1Project>();
2424
const [members, setMembers] = useState<V1Beta1User[]>([]);
2525
const [memberRoles, setMemberRoles] = useState<Record<string, Role[]>>({});
26+
const [isMembersLoading, setIsMembersLoading] = useState(false);
27+
28+
const [teams, setTeams] = useState<V1Beta1Group[]>([]);
29+
const [isTeamsLoading, setIsTeamsLoading] = useState(false);
30+
2631
const { client, activeOrganization: organization } = useFrontier();
2732
let navigate = useNavigate({ from: '/projects/$projectId' });
2833
const routeState = useRouterState();
@@ -33,46 +38,81 @@ export const ProjectPage = () => {
3338
);
3439
}, [routeState.matches]);
3540

36-
useEffect(() => {
37-
async function getProjectDetails() {
38-
if (!organization?.id || !projectId || isDeleteRoute) return;
39-
40-
try {
41-
setIsLoading(true);
41+
const getProjectTeams = useCallback(async () => {
42+
if (!organization?.id || !projectId || isDeleteRoute) return;
43+
try {
44+
setIsTeamsLoading(true);
45+
const result = await client?.frontierServiceListProjectGroups(projectId);
46+
if (result) {
4247
const {
43-
// @ts-ignore
44-
data: { project }
45-
} = await client?.frontierServiceGetProject(projectId);
46-
47-
const {
48-
// @ts-ignore
49-
data: { users, role_pairs }
50-
} = await client?.frontierServiceListProjectUsers(projectId, {
51-
withRoles: true
52-
});
53-
setProject(project);
54-
setMembers(users);
55-
setMemberRoles(
56-
role_pairs.reduce((previous: any, mr: any) => {
57-
return { ...previous, [mr.user_id]: mr.roles };
58-
}, {})
59-
);
60-
} catch ({ error }: any) {
61-
toast.error('Something went wrong', {
62-
description: error.message
63-
});
64-
} finally {
65-
setIsLoading(false);
48+
data: { groups = [] }
49+
} = result;
50+
setTeams(groups);
6651
}
52+
} catch (error: any) {
53+
toast.error('Something went wrong', {
54+
description: error?.message
55+
});
56+
} finally {
57+
setIsTeamsLoading(false);
6758
}
59+
}, [client, isDeleteRoute, organization?.id, projectId]);
60+
61+
const getProjectMembers = useCallback(async () => {
62+
if (!organization?.id || !projectId || isDeleteRoute) return;
63+
try {
64+
setIsMembersLoading(true);
65+
const {
66+
// @ts-ignore
67+
data: { users, role_pairs }
68+
} = await client?.frontierServiceListProjectUsers(projectId, {
69+
withRoles: true
70+
});
71+
setMembers(users);
72+
setMemberRoles(
73+
role_pairs.reduce((previous: any, mr: any) => {
74+
return { ...previous, [mr.user_id]: mr.roles };
75+
}, {})
76+
);
77+
} catch (error: any) {
78+
toast.error('Something went wrong', {
79+
description: error?.message
80+
});
81+
} finally {
82+
setIsMembersLoading(false);
83+
}
84+
}, [client, isDeleteRoute, organization?.id, projectId]);
85+
86+
const getProjectDetails = useCallback(async () => {
87+
if (!organization?.id || !projectId || isDeleteRoute) return;
88+
try {
89+
setIsProjectLoading(true);
90+
const {
91+
// @ts-ignore
92+
data: { project }
93+
} = await client?.frontierServiceGetProject(projectId);
94+
setProject(project);
95+
} catch (error: any) {
96+
toast.error('Something went wrong', {
97+
description: error?.message
98+
});
99+
} finally {
100+
setIsProjectLoading(false);
101+
}
102+
}, [client, isDeleteRoute, organization?.id, projectId]);
103+
104+
useEffect(() => {
68105
getProjectDetails();
69-
}, [
70-
client,
71-
organization?.id,
72-
projectId,
73-
routeState.location.state.key,
74-
isDeleteRoute
75-
]);
106+
getProjectMembers();
107+
getProjectTeams();
108+
}, [getProjectDetails, getProjectMembers, getProjectTeams]);
109+
110+
const isLoading = isProjectLoading || isTeamsLoading || isMembersLoading;
111+
112+
const refetchTeamAndMembers = useCallback(() => {
113+
getProjectMembers();
114+
getProjectTeams();
115+
}, [getProjectMembers, getProjectTeams]);
76116

77117
return (
78118
<Flex direction="column" style={{ width: '100%' }}>
@@ -99,14 +139,16 @@ export const ProjectPage = () => {
99139
<General
100140
organization={organization}
101141
project={project}
102-
isLoading={isLoading}
142+
isLoading={isProjectLoading}
103143
/>
104144
</Tabs.Content>
105145
<Tabs.Content value="members">
106146
<Members
107147
members={members}
108148
memberRoles={memberRoles}
109149
isLoading={isLoading}
150+
teams={teams}
151+
refetch={refetchTeamAndMembers}
110152
/>
111153
</Tabs.Content>
112154
</Tabs>

0 commit comments

Comments
 (0)