Skip to content

Commit

Permalink
feat: add UI for adding members to kits
Browse files Browse the repository at this point in the history
  • Loading branch information
tomcur committed Jan 16, 2024
1 parent 34d528d commit 4c6fa3f
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 2 deletions.
147 changes: 146 additions & 1 deletion astroplant-frontend/src/scenes/kit/access/components/Members.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import { DropdownDetails } from "~/Components/DropdownDetails";

import style from "./Members.module.css";
import clsx from "clsx";
import { IconCheck } from "@tabler/icons-react";
import { IconCheck, IconPlus, IconX } from "@tabler/icons-react";
import { ChangeEvent, useEffect, useMemo, useRef, useState } from "react";
import { ModalDialog } from "~/Components/ModalDialog";
import { Input } from "~/Components/Input";
import { useDebounce } from "~/hooks";

export type Props = {
kit: schemas["Kit"];
Expand All @@ -22,6 +23,8 @@ export default function Members({ kit }: Props) {
const [deleteKitMembership, { error: deleteKitMembershipError }] =
rtkApi.useDeleteKitMembershipMutation();

const [addingMember, setAddingMember] = useState(false);

useEffect(() => {
if (deleteKitMembershipError !== undefined) {
// TODO: use something other than an `alert`
Expand All @@ -42,6 +45,16 @@ export default function Members({ kit }: Props) {

return (
<>
<Button
variant="primary"
style={{ marginBottom: "1rem" }}
onClick={() => setAddingMember(true)}
>
Add a member
</Button>
<ModalDialog open={addingMember} onClose={() => setAddingMember(false)}>
<AddMember kit={kit} close={() => setAddingMember(false)} />
</ModalDialog>
<ul className={style.members}>
{data?.map((membership) => (
<li key={membership.id}>
Expand Down Expand Up @@ -251,3 +264,135 @@ function RoleSelector({
</DropdownDetails>
);
}

function AddMember({ kit, close }: { kit: schemas["Kit"]; close: () => void }) {
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 250);

const [selectedUser, setSelectedUser] = useState<schemas["User"] | null>(
null,
);
const [success, setSuccess] = useState(false);

const { data: suggestions } = rtkApi.useGetKitMemberSuggestionsQuery(
{
kitSerial: kit.serial,
query: { username: debouncedQuery },
},
{ skip: debouncedQuery === "" },
);
const [addKitMember] = rtkApi.useAddKitMemberMutation();

return (
<>
<p>
Find people to add to <strong>{kit.serial}</strong>
</p>
{selectedUser ? (
success ? (
<section>
<p>
User <strong>{selectedUser.displayName}</strong> has successfully
been added as member.
</p>
<Button
className={style.add}
variant="primary"
leftAdornment={<IconX />}
onClick={async () => {
setSelectedUser(null);
setSuccess(false);
setQuery("");
close();
}}
>
Close
</Button>
</section>
) : (
<section className={style.addMemberSelection}>
<div className={style.selectedMember}>
<Gravatar
identifier={selectedUser.gravatar}
size={32}
className={style.avatar}
/>
<div>
<strong>{selectedUser.displayName}</strong>
<br />
<span className={style.username}>{selectedUser.username}</span>
</div>
<div className={style.cancel}>
<Button variant="text" onClick={() => setSelectedUser(null)}>
<IconX />
</Button>
</div>
</div>
<p>
Do you want to add <strong>{selectedUser.displayName}</strong> to
this kit?
</p>
<div>
<Button
className={style.add}
leftAdornment={<IconPlus />}
variant="primary"
onClick={async () => {
const result = await addKitMember({
kitSerial: kit.serial,
member: {
username: selectedUser.username,
accessConfigure: false,
accessSuper: false,
},
});
if ("data" in result) {
setSuccess(true);
} else {
alert("An error occurred");
}
}}
>
Add {selectedUser.displayName}
</Button>
</div>
</section>
)
) : (
<section>
<Input
value={query}
onChange={(e) => setQuery(e.currentTarget.value)}
placeholder="Username"
/>
{suggestions !== undefined && (
<div style={{ marginTop: "1rem" }}>
{suggestions.length === 0 ? (
<p>
<strong>Could not find anyone with that username.</strong>
</p>
) : (
<ul className={style.memberSuggestions} role="listbox">
{suggestions.map((suggestion) => (
<li
key={suggestion.username}
role="option"
onClick={() => setSelectedUser(suggestion)}
>
<Gravatar
identifier={suggestion.gravatar}
size={32}
className={style.avatar}
/>{" "}
{suggestion.username}
</li>
))}
</ul>
)}
</div>
)}
</section>
)}
</>
);
}
37 changes: 36 additions & 1 deletion astroplant-frontend/src/services/astroplant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const baseQueryWithRetry = retry(baseQueryFn);
export const rtkApi = createApi({
reducerPath: "api",
baseQuery: baseQueryWithRetry,
tagTypes: ["Users", "KitMemberships"],
tagTypes: ["Users", "KitMemberships", "KitMemberSuggestions"],
endpoints: (build) => ({
listKits: build.query<schemas["Kit"][], void>({
query: () => ({ path: "/kits", method: "GET" }),
Expand Down Expand Up @@ -105,6 +105,37 @@ export const rtkApi = createApi({
{ type: "KitMemberships", id: result?.[0]?.kit.id },
],
}),
addKitMember: build.mutation<
schemas["KitMembership"],
{
kitSerial: string;
member: {
username: string;
accessConfigure: boolean;
accessSuper: boolean;
};
}
>({
query: ({ kitSerial, member }) => ({
path: `/kits/${encodeUri(kitSerial)}/members`,
method: "POST",
body: member,
}),
invalidatesTags: (result) => [
{ type: "KitMemberships", id: result?.kit.id },
],
}),
getKitMemberSuggestions: build.query<
Array<schemas["User"]>,
{ kitSerial: string; query: { username: string } }
>({
query: ({ kitSerial, query }) => ({
path: `/kits/${encodeUri(kitSerial)}/member-suggestions`,
method: "GET",
query,
}),
providesTags: [{ type: "KitMemberSuggestions" }],
}),
patchKitMembership: build.mutation<
schemas["KitMembership"],
{ kitMembershipId: number; patch: schemas["PatchKitMembership"] }
Expand All @@ -114,6 +145,9 @@ export const rtkApi = createApi({
method: "PATCH",
body: patch,
}),
invalidatesTags: (result) => [
{ type: "KitMemberships", id: result?.kit.id },
],
}),
/// `kitId` is required to be able to target the cache invalidation
deleteKitMembership: build.mutation<
Expand All @@ -126,6 +160,7 @@ export const rtkApi = createApi({
}),
invalidatesTags: (_result, _err, { kitId }) => [
{ type: "KitMemberships", id: kitId },
{ type: "KitMemberSuggestions" },
],
}),
getArchiveDownloadToken: build.query<
Expand Down

0 comments on commit 4c6fa3f

Please sign in to comment.