import { Button, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Radio, RadioGroup, Table, TableContainer, Tbody, Text, Td, Th, Thead, Tr, VStack, useToast, } from "@chakra-ui/react"; import { useState } from "react"; export default function (props: { isOpen: boolean; onClose: () => void }) { const actionMap: { [k: string]: number } = {}; const [users, setUsers] = useState([] as string[]); const [loading, setLoading] = useState(false); const toast = useToast(); const fileTypes: { [k: string]: string } = { gif: "image/gif", heic: "image/heic", heif: "image/heif", jfif: "image/jpeg", jpeg: "image/jpeg", jpg: "image/jpg", m4v: "video/x-m4v", mkv: "video/x-matroska", mov: "video/mp4", mp4: "video/mp4", png: "image/png", webp: "image/webp", webm: "video/webm", wmv: "video/x-ms-wmv", }; function addUser(user: string) { (document.getElementById("username") as HTMLInputElement).value = ""; const newUsers = [...users]; if (newUsers.includes(user)) return; newUsers.push(user); setUsers(newUsers); } function removeUser(user: string) { const newUsers = [...users]; const userIdx = newUsers.indexOf(user); if (userIdx === -1) return; newUsers.splice(userIdx, 1); setUsers(newUsers); delete actionMap[user]; } function reset() { (document.getElementById("username") as HTMLInputElement).value = ""; (document.getElementById("evidence") as HTMLInputElement).value = ""; setUsers([]); Object.keys(actionMap).forEach((k) => delete actionMap[k]); props.onClose(); } async function submit() { setLoading(true); const actions: number[] = []; const usernames: string[] = []; for (const [u, a] of Object.entries(actionMap)) { actions.push(a); usernames.push(u); } if (!usernames.length || !actions.length) return; const files = (document.getElementById("evidence") as HTMLInputElement) .files; if (!files) { setLoading(false); return; } const [evidence] = files; const submitReq = await fetch("/api/reports/submit", { body: JSON.stringify({ actions, bypass: true, filename: evidence.name, filesize: evidence.size, usernames, }), headers: { "content-type": "application/json", }, method: "POST", }); if (!submitReq.ok) { setLoading(false); toast({ description: ((await submitReq.json()) as { error: string }).error, status: "error", title: "Failed to submit report", }); return; } const { id, upload_url }: { [k: string]: string } = await submitReq.json(); const fileUpload = await fetch(upload_url, { body: evidence, headers: { "content-type": evidence.type || fileTypes[ evidence.name.split(".")[evidence.name.split(".").length - 1] ], }, method: "PUT", }); if (!fileUpload.ok) { setLoading(false); await fetch("/api/reports/recall", { body: JSON.stringify({ id }), headers: { "content-type": "application/json", }, method: "POST", }); toast({ description: "Failed to upload file", status: "error", title: "Error", }); return; } await fetch("/api/reports/complete", { body: JSON.stringify({ id }), headers: { "content-type": "application/json", }, method: "POST", }); toast({ description: "User moderated", status: "success", title: "Success", }); setLoading(false); props.onClose(); } return ( <Modal isCentered isOpen={props.isOpen} onClose={props.onClose}> <ModalOverlay /> <ModalContent> <ModalHeader>New Game Ban</ModalHeader> <ModalCloseButton onClick={reset} /> <ModalBody> <Text>Username(s)</Text> <Input id="username" mb="8px" placeholder="builderman" /> <Button onClick={function () { const user = ( document.getElementById("username") as HTMLInputElement ).value; if ( !user || user.length < 3 || user.length > 20 || // @ts-expect-error user.match(/_/g)?.length > 1 || user.match(/\W/) ) { toast({ description: "Check the username and try again", duration: 5000, isClosable: true, status: "error", title: "Invalid Username", }); return; } addUser(user); }} > Add </Button> <br /> <br /> <TableContainer> <Table variant="simple"> <Thead> <Tr> <Th>Username</Th> <Th>Punishment</Th> <Th> </Th> </Tr> </Thead> <Tbody> {users.map((user) => ( <Tr key={user}> <Td>{user}</Td> <Td> <RadioGroup onChange={(val) => (actionMap[user] = parseInt(val))} > <VStack> <Radio value="0">Do Nothing</Radio> <Radio value="1">Hide from Leaderboards</Radio> <Radio value="2">Ban</Radio> </VStack> </RadioGroup> </Td> <Td> <Button onClick={() => removeUser(user)} variant="ghost"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" > <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6Z" /> <path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1ZM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118ZM2.5 3h11V2h-11v1Z" /> </svg> </Button> </Td> </Tr> ))} </Tbody> </Table> </TableContainer> <br /> <br /> <Text>Evidence</Text> <Button mr="8px" onClick={() => document.getElementById("evidence")?.click()} > Select Files </Button> <input id="evidence" type="file" /> </ModalBody> <ModalFooter> <Button onClick={reset}>Cancel</Button> <Button colorScheme="blue" disabled={ !( Object.entries(actionMap).length && (document.getElementById("evidence") as HTMLInputElement).files ?.length ) } ml="8px" onClick={async () => await submit()} isLoading={loading} loadingText="Submitting..." > Submit </Button> </ModalFooter> </ModalContent> </Modal> ); }