Skip to content
Permalink
0334dc1678
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
396 lines (369 sloc) 10.5 KB
import { useLoaderData } from "@remix-run/react";
import {
Button,
Container,
Heading,
Input,
Link,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputStepper,
Table,
TableCaption,
TableContainer,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
useDisclosure,
useToast,
} from "@chakra-ui/react";
import { FormEvent, useState } from "react";
export async function loader({ context }: { context: RequestContext }) {
if (!context.data.current_user)
throw new Response(null, {
status: 401,
});
if (
![1 << 3, 1 << 4, 1 << 12].find(
(p) => context.data.current_user.permissions & p,
)
)
throw new Response(null, {
status: 403,
});
const etData = await context.env.D1.prepare(
"SELECT id, name, points, roblox_id FROM et_members;",
).all();
if (etData.error)
throw new Response(null, {
status: 500,
});
return { can_manage: true, members: etData.results } as {
can_manage: boolean;
members: { [k: string]: any }[];
};
}
export default function () {
const toast = useToast();
async function removeMember(id: string) {
const removeResp = await fetch("/api/events-team/team-members/user", {
body: JSON.stringify({ id }),
headers: {
"content-type": "application/json",
},
method: "DELETE",
});
if (!removeResp.ok) {
toast({
description: "Failed to remove member, try again later",
status: "error",
title: "Oops",
});
return;
}
toast({
description: "The member was removed from the roster",
status: "success",
title: "Member Removed",
});
setMemberData(memberData.filter((member) => member.id !== id));
}
async function addMember() {
const addResp = await fetch("/api/events-team/team-members/user", {
body: JSON.stringify({
id: addingMemberId,
name: addingMemberName,
roblox_username: addingMemberRoblox,
}),
headers: {
"content-type": "application/json",
},
method: "POST",
});
if (!addResp.ok) {
toast({
description: "Failed to add member, try again later",
status: "error",
title: "Oops",
});
return;
}
toast({
description: `Member ${addingMemberName} was added to the roster`,
status: "success",
title: "Member Added",
});
location.reload();
}
const data = useLoaderData<typeof loader>();
const [realtimePoints, setRealtimePoints] = useState(0);
const [currentModalMember, setModalMember] = useState("");
const [currentDelMember, setDelMember] = useState({ id: "", name: "" });
const [memberData, setMemberData] = useState(data.members);
const [addingMemberId, setAddingMemberId] = useState("");
const [addingMemberName, setAddingMemberName] = useState("");
const [addingMemberRoblox, setAddingMemberRoblox] = useState("");
const { isOpen, onClose, onOpen } = useDisclosure();
const {
isOpen: isDelConfirmOpen,
onClose: closeDelConfirm,
onOpen: openDelConfirm,
} = useDisclosure();
const {
isOpen: isAddMemberOpen,
onClose: closeAddMember,
onOpen: openAddMember,
} = useDisclosure();
const isManagement = data.can_manage;
async function updatePoints(id: string, points: number) {
const updateResp = await fetch(`/api/events-team/points/${id}`, {
body: JSON.stringify({ points }),
headers: {
"content-type": "application/json",
},
method: "POST",
});
if (!updateResp.ok) {
toast({
description: "Failed to update points",
status: "error",
title: "Oops!",
});
return;
}
toast({
description: `Point count changed to ${points}`,
status: "success",
title: "Points updated",
});
const newMemberData = memberData;
newMemberData[memberData.findIndex((m) => m.id === id)].points = points;
setMemberData(newMemberData);
onClose();
}
return (
<Container maxW="container.lg">
<Modal
isOpen={isOpen}
onClose={() => {
setRealtimePoints(0);
onClose();
}}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Modify Points</ModalHeader>
<ModalCloseButton />
<ModalBody>
<NumberInput
allowMouseWheel
defaultValue={realtimePoints}
onChange={(n) => setRealtimePoints(parseInt(n))}
mt="8px"
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</ModalBody>
{isManagement ? (
<ModalFooter>
<Button
onClick={() => {
setRealtimePoints(0);
onClose();
}}
>
Cancel
</Button>
<Button
colorScheme="blue"
onClick={async () =>
await updatePoints(currentModalMember, realtimePoints)
}
>
Update Points
</Button>
</ModalFooter>
) : null}
</ModalContent>
</Modal>
<Modal isOpen={isDelConfirmOpen} onClose={closeDelConfirm}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Remove Member</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Text>
You are about to remove {currentDelMember.name} from the Events
Team roster, this will clear all of their data. Are you sure you
want to do this?
</Text>
</ModalBody>
<ModalFooter>
<Button
colorScheme="blue"
onClick={() => {
setDelMember({ id: "", name: "" });
closeDelConfirm();
}}
>
No
</Button>
<Button
colorScheme="red"
onClick={async () => {
await removeMember(currentDelMember.id);
setDelMember({ id: "", name: "" });
closeDelConfirm();
}}
ml="8px"
>
Yes, Remove
</Button>
</ModalFooter>
</ModalContent>
</Modal>
<Modal isOpen={isAddMemberOpen} onClose={closeAddMember}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Add Member</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Heading size="xs">User ID</Heading>
<Input
maxLength={19}
onBeforeInput={(e) => {
const {
data,
}: { data?: string } & FormEvent<HTMLInputElement> = e;
if (data?.match(/\D/)) e.preventDefault();
}}
onChange={(e) => setAddingMemberId(e.target.value)}
mb="16px"
type="number"
/>
<Heading size="xs">Name</Heading>
<Input
maxLength={64}
onChange={(e) => setAddingMemberName(e.target.value)}
mb="16px"
/>
<Heading size="xs">Roblox Username (optional)</Heading>
<Input
maxLength={20}
onBeforeInput={(e) => {
const {
data,
}: { data?: string } & FormEvent<HTMLInputElement> = e;
if (!data) return;
if (
data.match(/\W/) ||
data.length > 20 ||
(data.match(/_/g)?.length || 0) > 1 ||
data.startsWith("_")
)
e.preventDefault();
}}
onChange={(e) => setAddingMemberRoblox(e.target.value)}
/>
</ModalBody>
<ModalFooter>
<Button onClick={closeAddMember}>Close</Button>
<Button
colorScheme="blue"
onClick={async () => await addMember()}
ml="8px"
>
Add
</Button>
</ModalFooter>
</ModalContent>
</Modal>
<Heading>Events Team Members</Heading>
<TableContainer mt="16px">
<Table variant="simple">
<TableCaption>
Click/tap on a user's points count to change their points, their
user id to see and manage strikes.
</TableCaption>
<Thead>
<Tr>
<Th>Discord ID</Th>
<Th>Name</Th>
<Th>Roblox ID</Th>
<Th>Points</Th>
<Th>Remove</Th>
</Tr>
</Thead>
<Tbody>
{memberData.map((member) => (
<Tr>
<Td>
{isManagement ? (
<Link
onClick={() =>
location.assign(`/et-members/strikes/${member.id}`)
}
>
{member.id}
</Link>
) : (
member.id
)}
</Td>
<Td>{member.name}</Td>
<Td>{member.roblox_id}</Td>
<Td>
{isManagement ? (
<Link
onClick={() => {
setModalMember(member.id);
onOpen();
}}
>
{member.points}
</Link>
) : (
member.points
)}
</Td>
<Td>
{isManagement ? (
<Link
onClick={() => {
setDelMember({ id: member.id, name: member.name });
openDelConfirm();
}}
>
Remove
</Link>
) : null}
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{isManagement ? (
<Link color="#646cff" onClick={openAddMember} mt="16px">
Add Member
</Link>
) : null}
</Container>
);
}