Skip to content

Commit bf757e1

Browse files
committed
messy
1 parent f6b7650 commit bf757e1

File tree

2 files changed

+176
-73
lines changed

2 files changed

+176
-73
lines changed

site/src/api/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ class ApiMethods {
558558
};
559559

560560
removeOrganizationMember = async (organizationId: string, userId: string) => {
561-
const response = await this.axios.delete<TypesGen.OrganizationMember>(
561+
await this.axios.delete(
562562
`/api/v2/organizations/${organizationId}/members/${userId}`,
563563
);
564564
};
Lines changed: 175 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,49 @@
1-
import { useTheme } from "@emotion/react";
2-
import { Tooltip } from "@mui/material";
1+
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
32
import Table from "@mui/material/Table";
4-
import { UserAvatar } from "components/UserAvatar/UserAvatar";
5-
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
6-
import { AvatarData } from "components/AvatarData/AvatarData";
73
import TableBody from "@mui/material/TableBody";
84
import TableCell from "@mui/material/TableCell";
95
import TableContainer from "@mui/material/TableContainer";
106
import TableHead from "@mui/material/TableHead";
117
import TableRow from "@mui/material/TableRow";
12-
import type { FC } from "react";
8+
import Tooltip from "@mui/material/Tooltip";
9+
import PersonAdd from "@mui/icons-material/PersonAdd";
10+
import { type FC, useState } from "react";
1311
import { useMutation, useQuery, useQueryClient } from "react-query";
1412
import { useParams } from "react-router-dom";
1513
import {
1614
addOrganizationMember,
1715
organizationMembers,
1816
removeOrganizationMember,
1917
} from "api/queries/organizations";
20-
import { roles } from "api/queries/roles";
21-
import type { OrganizationMemberWithUserData } from "api/typesGenerated";
18+
import type { OrganizationMemberWithUserData, User } from "api/typesGenerated";
2219
import { ErrorAlert } from "components/Alert/ErrorAlert";
20+
import { AvatarData } from "components/AvatarData/AvatarData";
21+
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
2322
import { Pill } from "components/Pill/Pill";
24-
import { useOrganizationSettings } from "./ManagementSettingsLayout";
23+
import { UserAvatar } from "components/UserAvatar/UserAvatar";
24+
import {
25+
MoreMenu,
26+
MoreMenuTrigger,
27+
MoreMenuContent,
28+
MoreMenuItem,
29+
ThreeDotsButton,
30+
} from "components/MoreMenu/MoreMenu";
31+
import Divider from "@mui/material/Divider";
32+
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
33+
import { displayError } from "components/GlobalSnackbar/utils";
34+
import { getErrorMessage } from "api/errors";
35+
import LoadingButton from "@mui/lab/LoadingButton";
36+
import { Stack } from "components/Stack/Stack";
37+
38+
function doNothingTemporarily() {
39+
console.log("ok");
40+
}
2541

2642
const OrganizationMembersPage: FC = () => {
2743
const queryClient = useQueryClient();
44+
const theme = useTheme();
2845
const { organization } = useParams() as { organization: string };
2946

30-
const rolesQuery = useQuery(roles());
3147
const membersQuery = useQuery(organizationMembers(organization));
3248
const addMemberMutation = useMutation(
3349
addOrganizationMember(queryClient, organization),
@@ -36,76 +52,113 @@ const OrganizationMembersPage: FC = () => {
3652
removeOrganizationMember(queryClient, organization),
3753
);
3854

39-
const { currentOrganizationId, organizations } = useOrganizationSettings();
40-
41-
const error = rolesQuery.error ?? membersQuery.error;
42-
43-
const theme = useTheme();
55+
const error =
56+
membersQuery.error ?? addMemberMutation.error ?? removeMemberMutation.error;
4457

4558
return (
4659
<div>
4760
<PageHeader>
4861
<PageHeaderTitle>Organization members</PageHeaderTitle>
4962
</PageHeader>
5063

51-
{Boolean(error) && (
52-
<div css={{ marginBottom: 32 }}>
53-
<ErrorAlert error={error} />
54-
</div>
55-
)}
56-
57-
<TableContainer>
58-
<Table>
59-
<TableHead>
60-
<TableRow>
61-
<TableCell width="50%">User</TableCell>
62-
<TableCell width="49%">Roles</TableCell>
63-
<TableCell width="1%"></TableCell>
64-
</TableRow>
65-
</TableHead>
66-
<TableBody>
67-
{membersQuery.data?.map((member) => (
68-
<TableRow key={member.user_id}>
69-
<TableCell>
70-
<AvatarData
71-
avatar={
72-
<UserAvatar
73-
username={member.username}
74-
avatarURL={member.avatar_url}
75-
/>
76-
}
77-
title={member.name}
78-
subtitle={member.username}
79-
/>
80-
</TableCell>
81-
<TableCell>
82-
{getMemberRoles(member).map((role) => (
83-
<Pill
84-
key={role.name}
85-
css={{
86-
backgroundColor: role.global
87-
? theme.roles.info.background
88-
: theme.roles.inactive.background,
89-
borderColor: role.global
90-
? theme.roles.info.outline
91-
: theme.roles.inactive.outline,
92-
}}
93-
>
94-
{role.name}
95-
{role.global && (
96-
<Tooltip title="This user has blah blah permissions for all organziations.">
97-
<span>*</span>
98-
</Tooltip>
99-
)}
100-
</Pill>
101-
))}
102-
</TableCell>
103-
<TableCell></TableCell>
64+
<Stack>
65+
{Boolean(error) && <ErrorAlert error={error} />}
66+
67+
<AddGroupMember
68+
isLoading={addMemberMutation.isLoading}
69+
onSubmit={async (user) => {
70+
try {
71+
await addMemberMutation.mutateAsync(user.id);
72+
void membersQuery.refetch();
73+
} catch (error) {
74+
displayError(getErrorMessage(error, "Failed to add member."));
75+
}
76+
}}
77+
/>
78+
79+
<TableContainer>
80+
<Table>
81+
<TableHead>
82+
<TableRow>
83+
<TableCell width="50%">User</TableCell>
84+
<TableCell width="49%">Roles</TableCell>
85+
<TableCell width="1%"></TableCell>
10486
</TableRow>
105-
))}
106-
</TableBody>
107-
</Table>
108-
</TableContainer>
87+
</TableHead>
88+
<TableBody>
89+
{membersQuery.data?.map((member) => (
90+
<TableRow key={member.user_id}>
91+
<TableCell>
92+
<AvatarData
93+
avatar={
94+
<UserAvatar
95+
username={member.username}
96+
avatarURL={member.avatar_url}
97+
/>
98+
}
99+
title={member.name}
100+
subtitle={member.username}
101+
/>
102+
</TableCell>
103+
<TableCell>
104+
{getMemberRoles(member).map((role) => (
105+
<Pill
106+
key={role.name}
107+
css={{
108+
backgroundColor: role.global
109+
? theme.roles.info.background
110+
: theme.roles.inactive.background,
111+
borderColor: role.global
112+
? theme.roles.info.outline
113+
: theme.roles.inactive.outline,
114+
}}
115+
>
116+
{role.global ? (
117+
<Tooltip title="This user has this role for all organziations.">
118+
<span>{role.name}*</span>
119+
</Tooltip>
120+
) : (
121+
role.name
122+
)}
123+
</Pill>
124+
))}
125+
</TableCell>
126+
<TableCell>
127+
<MoreMenu>
128+
<MoreMenuTrigger>
129+
<ThreeDotsButton />
130+
</MoreMenuTrigger>
131+
<MoreMenuContent>
132+
<MoreMenuItem onClick={() => doNothingTemporarily()}>
133+
View workspaces
134+
</MoreMenuItem>
135+
<MoreMenuItem onClick={() => doNothingTemporarily()}>
136+
View activity
137+
</MoreMenuItem>
138+
<MoreMenuItem onClick={() => doNothingTemporarily()}>
139+
Reset password&hellip;
140+
</MoreMenuItem>
141+
<Divider />
142+
<MoreMenuItem
143+
onClick={async () => {
144+
await removeMemberMutation.mutateAsync(
145+
member.user_id,
146+
);
147+
membersQuery.refetch();
148+
}}
149+
danger
150+
>
151+
Delete&hellip;
152+
</MoreMenuItem>
153+
</MoreMenuContent>
154+
</MoreMenu>
155+
</TableCell>
156+
</TableRow>
157+
))}
158+
</TableBody>
159+
</Table>
160+
</TableContainer>
161+
</Stack>
109162
</div>
110163
);
111164
};
@@ -133,3 +186,53 @@ function getMemberRoles(member: OrganizationMemberWithUserData) {
133186
}
134187

135188
export default OrganizationMembersPage;
189+
190+
interface AddGroupMemberProps {
191+
isLoading: boolean;
192+
onSubmit: (user: User) => void;
193+
}
194+
195+
const AddGroupMember: FC<AddGroupMemberProps> = ({ isLoading, onSubmit }) => {
196+
const [selectedUser, setSelectedUser] = useState<User | null>(null);
197+
198+
return (
199+
<form
200+
onSubmit={async (e) => {
201+
e.preventDefault();
202+
203+
if (selectedUser) {
204+
try {
205+
await onSubmit(selectedUser);
206+
setSelectedUser(null);
207+
} catch {}
208+
}
209+
}}
210+
>
211+
<Stack direction="row" alignItems="center" spacing={1}>
212+
<UserAutocomplete
213+
css={styles.autoComplete}
214+
value={selectedUser}
215+
onChange={(newValue) => {
216+
setSelectedUser(newValue);
217+
}}
218+
/>
219+
220+
<LoadingButton
221+
loadingPosition="start"
222+
disabled={!selectedUser}
223+
type="submit"
224+
startIcon={<PersonAdd />}
225+
loading={isLoading}
226+
>
227+
Add user
228+
</LoadingButton>
229+
</Stack>
230+
</form>
231+
);
232+
};
233+
234+
const styles = {
235+
autoComplete: {
236+
width: 300,
237+
},
238+
} satisfies Record<string, Interpolation<Theme>>;

0 commit comments

Comments
 (0)