299 lines
7.8 KiB
TypeScript
299 lines
7.8 KiB
TypeScript
import {
|
|
Button,
|
|
Input,
|
|
Link,
|
|
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(true);
|
|
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);
|
|
}
|
|
|
|
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) =>
|
|
Object.defineProperty(actionMap, user, {
|
|
value: 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>
|
|
</ModalContent>
|
|
<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>
|
|
</Modal>
|
|
);
|
|
}
|