-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* members link * members page * title * users query * members table * add user btn * new endow admin endpoint * add wiring * new endow admin * delete endpoint * add delete loading * update message * include endow name * use is submitting instead
- Loading branch information
Showing
11 changed files
with
300 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { yupResolver } from "@hookform/resolvers/yup"; | ||
import { | ||
FormProvider, | ||
SubmitHandler, | ||
UseFormReturn, | ||
useForm, | ||
} from "react-hook-form"; | ||
import { object } from "yup"; | ||
import { useLazyProfileQuery } from "services/aws/aws"; | ||
import { useNewEndowAdminMutation } from "services/aws/users"; | ||
import { useModalContext } from "contexts/ModalContext"; | ||
import Modal from "components/Modal"; | ||
import Prompt from "components/Prompt"; | ||
import { Field } from "components/form"; | ||
import { requiredString } from "schemas/string"; | ||
|
||
export type Props = { | ||
endowID: number; | ||
added: string[]; | ||
}; | ||
|
||
export default function AddForm({ added, endowID }: Props) { | ||
const [addAdmin] = useNewEndowAdminMutation(); | ||
const [profile] = useLazyProfileQuery(); | ||
const { setModalOption, showModal } = useModalContext(); | ||
const methods = useForm({ | ||
resolver: yupResolver( | ||
object({ | ||
firstName: requiredString, | ||
lastName: requiredString, | ||
email: requiredString | ||
.email("invalid email") | ||
.notOneOf(added, "already a member"), | ||
}) | ||
), | ||
}); | ||
|
||
type FV = typeof methods extends UseFormReturn<infer U> ? U : never; | ||
const { | ||
handleSubmit, | ||
formState: { isSubmitting }, | ||
} = methods; | ||
|
||
const submit: SubmitHandler<FV> = async (fv) => { | ||
try { | ||
setModalOption("isDismissible", false); | ||
//get endowname | ||
const { data } = await profile({ id: endowID, fields: ["name"] }); | ||
|
||
await addAdmin({ | ||
firstName: fv.firstName, | ||
lastName: fv.lastName, | ||
email: fv.email, | ||
endowID, | ||
endowName: data?.name || `Endowment:${endowID}`, | ||
}).unwrap(); | ||
|
||
showModal(Prompt, { | ||
headline: "Success!", | ||
children: ( | ||
<p className="py-6"> | ||
User succesfully added!{" "} | ||
<span className="font-semibold">{fv.email}</span> should signin to | ||
apply new credentials. | ||
</p> | ||
), | ||
}); | ||
} catch (err) { | ||
showModal(Prompt, { | ||
headline: "Error.", | ||
children: ( | ||
<p className="py-6 text-red">Failed to add {fv.email} to members</p> | ||
), | ||
}); | ||
} | ||
}; | ||
|
||
return ( | ||
<Modal | ||
onSubmit={handleSubmit(submit)} | ||
as="form" | ||
className="p-6 fixed-center z-10 grid gap-4 text-gray-d2 dark:text-white bg-white dark:bg-blue-d4 sm:w-full w-[90vw] sm:max-w-lg rounded overflow-hidden" | ||
> | ||
<FormProvider {...methods}> | ||
<Field<FV> name="email" label="Email" required /> | ||
<Field<FV> name="firstName" label="First name" required /> | ||
<Field<FV> name="lastName" label="Last name" required /> | ||
</FormProvider> | ||
<button disabled={isSubmitting} type="submit" className="btn-orange mt-6"> | ||
Add member | ||
</button> | ||
</Modal> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { useAdminContext } from "pages/Admin/Context"; | ||
import { useDeleteEndowAdminMutation, useUsersQuery } from "services/aws/users"; | ||
import { useAuthenticatedUser } from "contexts/Auth"; | ||
import { useModalContext } from "contexts/ModalContext"; | ||
import ContentLoader from "components/ContentLoader"; | ||
import Icon from "components/Icon"; | ||
import QueryLoader from "components/QueryLoader"; | ||
import TableSection, { Cells } from "components/TableSection"; | ||
import AddForm from "./AddForm"; | ||
|
||
export default function List() { | ||
const { showModal } = useModalContext(); | ||
const { id } = useAdminContext(); | ||
|
||
const queryState = useUsersQuery(id); | ||
return ( | ||
<div> | ||
<button | ||
type="button" | ||
disabled={queryState.isLoading} | ||
className="justify-self-end btn-orange px-4 py-1.5 text-sm gap-2 mb-2" | ||
onClick={() => | ||
showModal(AddForm, { added: queryState.data || [], endowID: id }) | ||
} | ||
> | ||
<Icon type="Plus" /> | ||
<span>Invite user</span> | ||
</button> | ||
<QueryLoader | ||
queryState={queryState} | ||
messages={{ | ||
loading: <Skeleton />, | ||
error: "Failed to get members", | ||
empty: "No members found.", | ||
}} | ||
> | ||
{(members) => <Loaded members={members} />} | ||
</QueryLoader> | ||
</div> | ||
); | ||
} | ||
|
||
type LoadedProps = { | ||
classes?: string; | ||
members: string[]; | ||
}; | ||
function Loaded({ members, classes = "" }: LoadedProps) { | ||
const { email: user } = useAuthenticatedUser(); | ||
const { id } = useAdminContext(); | ||
const [removeUser, { isLoading }] = useDeleteEndowAdminMutation(); | ||
|
||
async function handleRemove(toRemove: string) { | ||
if (toRemove === user) return window.alert("Can't delete self"); | ||
if (!window.confirm(`Are you sure you want to remove ${toRemove}?`)) return; | ||
|
||
const result = await removeUser({ email: toRemove, endowID: id }); | ||
if ("error" in result) return window.alert("Failed to remove user"); | ||
} | ||
|
||
return ( | ||
<table | ||
className={`${classes} w-full text-sm rounded border border-separate border-spacing-0 border-prim`} | ||
> | ||
<TableSection | ||
type="thead" | ||
rowClass="bg-orange-l6 dark:bg-blue-d7 divide-x divide-prim" | ||
> | ||
<Cells | ||
type="th" | ||
cellClass="px-3 py-4 text-xs uppercase font-semibold text-left first:rounded-tl last:rounded-tr" | ||
> | ||
<td className="w-8" /> | ||
<>Email</> | ||
</Cells> | ||
</TableSection> | ||
<TableSection | ||
type="tbody" | ||
rowClass="even:bg-orange-l6 dark:odd:bg-blue-d6 dark:even:bg-blue-d7 divide-x divide-prim" | ||
selectedClass="bg-orange-l5 dark:bg-blue-d4" | ||
> | ||
{members.map((member) => ( | ||
<Cells | ||
key={member} | ||
type="td" | ||
cellClass="p-3 border-t border-prim max-w-[256px] truncate first:rounded-bl last:rounded-br" | ||
> | ||
<button | ||
disabled={isLoading} | ||
onClick={() => handleRemove(member)} | ||
type="button" | ||
className="text-red disabled:text-gray" | ||
> | ||
<Icon type="Dash" /> | ||
</button> | ||
<>{member}</> | ||
</Cells> | ||
))} | ||
</TableSection> | ||
</table> | ||
); | ||
} | ||
|
||
function Skeleton() { | ||
return ( | ||
<div className="grid gap-y-1 mt-2"> | ||
<ContentLoader className="h-12 w-full rounded" /> | ||
<ContentLoader className="h-12 w-full rounded" /> | ||
<ContentLoader className="h-12 w-full rounded" /> | ||
<ContentLoader className="h-12 w-full rounded" /> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import List from "./List"; | ||
|
||
export default function Members() { | ||
return ( | ||
<div className="grid content-start gap-y-6 @lg:gap-y-8 @container"> | ||
<h3 className="text-[2rem]">Manage Members</h3> | ||
<List /> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from "./Members"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { | ||
DeleteEndowAdminPayload, | ||
NewEndowAdminPayload, | ||
} from "types/aws/ap/users"; | ||
import { TEMP_JWT } from "constants/auth"; | ||
import { version as v } from "../helpers"; | ||
import { aws } from "./aws"; | ||
|
||
const users = aws.injectEndpoints({ | ||
endpoints: (builder) => ({ | ||
users: builder.query<string[], number>({ | ||
providesTags: ["users"], | ||
query: (endowID) => { | ||
return { | ||
url: `/${v(1)}/users/${endowID}`, | ||
headers: { Authorization: TEMP_JWT }, | ||
}; | ||
}, | ||
}), | ||
newEndowAdmin: builder.mutation<unknown, NewEndowAdminPayload>({ | ||
invalidatesTags: (_, error) => (error ? [] : ["users"]), | ||
query: ({ endowID, ...payload }) => { | ||
return { | ||
method: "POST", | ||
url: `/${v(1)}/users/${endowID}`, | ||
body: payload, | ||
headers: { Authorization: TEMP_JWT }, | ||
}; | ||
}, | ||
}), | ||
deleteEndowAdmin: builder.mutation<unknown, DeleteEndowAdminPayload>({ | ||
invalidatesTags: (_, error) => (error ? [] : ["users"]), | ||
query: ({ endowID, ...payload }) => { | ||
return { | ||
method: "DELETE", | ||
url: `/${v(1)}/users/${endowID}`, | ||
body: payload, | ||
headers: { Authorization: TEMP_JWT }, | ||
}; | ||
}, | ||
}), | ||
}), | ||
}); | ||
|
||
export const { | ||
useUsersQuery, | ||
useNewEndowAdminMutation, | ||
useDeleteEndowAdminMutation, | ||
} = users; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export type NewEndowAdminPayload = { | ||
endowID: number; | ||
firstName: string; | ||
lastName: string; | ||
email: string; | ||
endowName: string; | ||
}; | ||
|
||
export type DeleteEndowAdminPayload = { | ||
endowID: number; | ||
email: string; | ||
}; |