Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add strike creation capabilities
  • Loading branch information
regalijan committed Feb 28, 2024
1 parent 3e06541 commit 0334dc1
Show file tree
Hide file tree
Showing 2 changed files with 255 additions and 2 deletions.
17 changes: 15 additions & 2 deletions app/routes/et-members.tsx
Expand Up @@ -325,7 +325,8 @@ export default function () {
<TableContainer mt="16px">
<Table variant="simple">
<TableCaption>
Points are updated at the end of the month
Click/tap on a user's points count to change their points, their
user id to see and manage strikes.
</TableCaption>
<Thead>
<Tr>
Expand All @@ -339,7 +340,19 @@ export default function () {
<Tbody>
{memberData.map((member) => (
<Tr>
<Td>{member.id}</Td>
<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>
Expand Down
240 changes: 240 additions & 0 deletions app/routes/et-members_.strikes_.$uid.tsx
@@ -0,0 +1,240 @@
import {
Button,
Container,
Heading,
Link,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Table,
TableContainer,
Tbody,
Td,
Text,
Textarea,
Th,
Thead,
Tr,
useDisclosure,
useToast,
} from "@chakra-ui/react";
import { LoaderFunctionArgs } from "@remix-run/cloudflare";
import { useLoaderData } from "@remix-run/react";
import { useState } from "react";

export async function loader({
context,
params,
}: {
context: RequestContext;
params: LoaderFunctionArgs & { uid: string };
}) {
const { current_user: user } = context.data;

if (!user)
throw new Response(null, {
status: 401,
});

if (![1 << 3, 1 << 4, 1 << 12].find((p) => user.permissions & p))
throw new Response(null, {
status: 403,
});

const strikeData = await context.env.D1.prepare(
"SELECT * FROM et_strikes WHERE user = ?;",
)
.bind(params.uid)
.all();

return {
can_manage: Boolean([1 << 4, 1 << 12].find((p) => user.permissions & p)),
strikes: strikeData.results,
user: params.uid,
};
}

export default function () {
const { can_manage, strikes, user } = useLoaderData<typeof loader>();
const [strikeData, setStrikeData] = useState(strikes);
const toast = useToast();
const [rmStrikeId, setRmStrikeId] = useState("");
const [strikeReason, setStrikeReason] = useState("");

async function removeStrike(id: string) {
const removeResp = await fetch(`/api/events-team/strikes/${id}`, {
method: "DELETE",
});

if (!removeResp.ok) {
let msg = "Unknown error";

try {
msg = ((await removeResp.json()) as { error: string }).error;
} catch {}

toast({
description: msg,
status: "error",
title: "Failed to remove strike",
});

return;
}

toast({
description: `Strike ${id} was removed`,
status: "success",
title: "Strike Removed",
});

setStrikeData(strikeData.filter((strike) => strike.id !== id));
closeRmStrike();
}

async function addStrike() {
const addStrikeResp = await fetch("/api/events-team/strikes/new", {
body: JSON.stringify({
reason: strikeReason,
user,
}),
headers: {
"content-type": "application/json",
},
method: "POST",
});

if (!addStrikeResp.ok) {
let msg = "Unknown error";

try {
msg = ((await addStrikeResp.json()) as { error: string }).error;
} catch {}

toast({
description: msg,
status: "error",
title: "Failed to add strike",
});

return;
}

toast({
description: "Strike added",
status: "success",
title: "Success",
});

const newStrikeData = strikeData;

newStrikeData.push(await addStrikeResp.json());
setStrikeData(newStrikeData);
closeAddStrike();
}

const {
isOpen: rmStrikeOpen,
onClose: closeRmStrike,
onOpen: openRmStrike,
} = useDisclosure();
const {
isOpen: addStrikeOpen,
onClose: closeAddStrike,
onOpen: openAddStrike,
} = useDisclosure();

return (
<Container maxW="container.lg">
<Modal isOpen={rmStrikeOpen} onClose={closeRmStrike}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Remove Strike</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Text>Are you sure you want to remove this strike?</Text>
</ModalBody>
<ModalFooter>
<Button mr="8px">No</Button>
<Button
colorScheme="red"
onClick={async () => await removeStrike(rmStrikeId)}
>
Yes
</Button>
</ModalFooter>
</ModalContent>
</Modal>
<Modal isOpen={addStrikeOpen} onClose={closeAddStrike}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Add Strike</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Heading mb="8px" size="xs">
Reason
</Heading>
<Textarea
onChange={(e) => setStrikeReason(e.target.value)}
placeholder="Strike reason"
value={strikeReason}
/>
</ModalBody>
<ModalFooter>
<Button
mr="8px"
onClick={() => {
closeAddStrike();
setStrikeReason("");
}}
>
Cancel
</Button>
<Button colorScheme="red" onClick={async () => await addStrike()}>
Add Strike
</Button>
</ModalFooter>
</ModalContent>
</Modal>
<Heading my="16px">Strikes</Heading>
<TableContainer>
<Table variant="simple">
<Thead>
<Th>Time Added</Th>
<Th>Added By</Th>
<Th>Reason</Th>
<Th>Remove</Th>
</Thead>
<Tbody>
{strikeData.map((strike: { [k: string]: any }) => (
<Tr>
<Td>{new Date(strike.created_at).toUTCString()}</Td>
<Td>{strike.created_by}</Td>
<Td>{strike.reason}</Td>
<Td>
{can_manage ? (
<Link
onClick={() => {
setRmStrikeId(strike.id);
openRmStrike();
}}
>
Remove
</Link>
) : null}
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<Link color="#646cff" onClick={openAddStrike} py="16px">
Add Strike
</Link>
</Container>
);
}

0 comments on commit 0334dc1

Please sign in to comment.