Add strike creation capabilities
This commit is contained in:
parent
3e06541b30
commit
0334dc1678
@ -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>
|
||||
@ -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>
|
||||
|
240
app/routes/et-members_.strikes_.$uid.tsx
Normal file
240
app/routes/et-members_.strikes_.$uid.tsx
Normal file
@ -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>
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user