2023-10-23 20:46:54 -04:00

356 lines
9.3 KiB
TypeScript

import {
Button,
Container,
Divider,
Heading,
Link,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
Table,
TableContainer,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
useDisclosure,
useToast,
} from "@chakra-ui/react";
import { type Dispatch, type SetStateAction, useEffect, useState } from "react";
import { useLoaderData } from "@remix-run/react";
export async function loader({ context }: { context: RequestContext }) {
const { current_user: currentUser } = context.data;
if (!currentUser) throw new Response(null, { status: 401 });
const d1Promises = [];
for (const itemType of ["appeals", "inactivity_notices", "reports"])
d1Promises.push(
context.env.D1.prepare(
`SELECT *
FROM ${itemType}
WHERE user = ?
ORDER BY created_at DESC;`,
)
.bind(currentUser.id)
.all(),
);
const settledPromises = await Promise.allSettled(d1Promises);
return {
items: settledPromises.map((p) => {
if (p.status === "fulfilled") return p.value.results;
return null;
}) as any as ({ [k: string]: any }[] | null)[],
permissions: currentUser.permissions as number,
};
}
export default function () {
const data: {
items: ({ [k: string]: any }[] | null)[];
permissions: number;
} = useLoaderData<typeof loader>();
const timeStates: {
[k: number]: { data: string; set: Dispatch<SetStateAction<string>> };
} = {};
const toast = useToast();
for (const result of data.items) {
if (!result) continue;
for (const row of result) {
const [data, set] = useState(new Date(row.created_at).toUTCString());
timeStates[row.created_at] = {
data,
set,
};
useEffect(() => {
timeStates[row.created_at].set(
new Date(row.created_at).toLocaleString(),
);
}, [row.created_at]);
}
}
async function fetchItem(id: string, type: string) {
const itemResp = await fetch(`/api/me/items/${type}/${id}`);
if (!itemResp.ok) {
let error: string;
try {
error = ((await itemResp.json()) as { error: string }).error;
} catch {
error = "Unknown error";
}
toast({
description: error,
isClosable: true,
status: "error",
title: "Oops",
});
return;
}
const data: { [k: string]: any } = await itemResp.json();
switch (type) {
case "appeal":
setModalBody(
<ModalContent>
<ModalHeader>View Appeal</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Heading size="lg">Why were you banned?</Heading>
<br />
<Text>
<i>{data.ban_reason}</i>
</Text>
<br />
<Divider />
<br />
<Heading size="lg">Why should we unban you?</Heading>
<br />
<Text>
<i>{data.reason_for_unban}</i>
</Text>
<br />
<Divider />
<br />
<Heading size="lg">
What have you learned from your mistake?
</Heading>
<br />
<Text>
<i>{data.learned}</i>
</Text>
</ModalBody>
</ModalContent>,
);
break;
case "inactivity":
setModalBody(
<ModalContent>
<ModalHeader>View Inactivity Notice</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Heading size="lg">Reason for Inactivity</Heading>
<br />
<Text>
<i>{data.reason}</i>
</Text>
<br />
<Divider />
<br />
<Heading size="lg">Start Date</Heading>
<br />
<Text>
<i>{new Date(data.start).toLocaleDateString()}</i>
</Text>
<br />
<Divider />
<br />
<Heading size="lg">End Date</Heading>
<br />
<Text>
<i>{new Date(data.end).toLocaleDateString()}</i>
</Text>
</ModalBody>
</ModalContent>,
);
break;
case "report":
setModalBody(
<ModalContent>
<ModalHeader>View Report</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Heading size="lg">Username(s)</Heading>
<br />
<Text>
<i>{data.target_usernames.toString()}</i>
</Text>
<br />
<Divider />
<br />
<Heading size="lg">Description</Heading>
<br />
<Text>
<i>{data.description ?? "No description"}</i>
</Text>
<br />
<Divider />
<br />
<Heading size="lg">Media Links</Heading>
<br />
{data.resolved_attachments.map((attachment: string) => (
<Link color="#646cff" href={attachment} target="_blank">
View media here
</Link>
))}
</ModalBody>
</ModalContent>,
);
break;
default:
setModalBody(<ModalContent></ModalContent>);
break;
}
onOpen();
}
const { isOpen, onClose, onOpen } = useDisclosure();
const [modalBody, setModalBody] = useState(<ModalContent></ModalContent>);
function resetModal() {
onClose();
setModalBody(<ModalContent></ModalContent>);
}
return (
<Container maxW="container.lg">
<Modal isCentered isOpen={isOpen} onClose={resetModal} size="lg">
<ModalOverlay />
{modalBody}
</Modal>
<Heading mb={8}>My Data</Heading>
<br />
<br />
<Heading size="lg">Discord Appeals</Heading>
<TableContainer mb="16px">
<Table variant="simple">
<Thead>
<Tr>
<Th>Date</Th>
<Th>ID</Th>
<Th>Status</Th>
<Th>View</Th>
</Tr>
</Thead>
<Tbody>
{data.items[0]?.map((result) => {
return (
<Tr>
<Td>{timeStates[result.created_at].data}</Td>
<Td>{result.id}</Td>
<Td>
{result.open
? "Pending"
: typeof result.approved === "boolean"
? `${result.approved ? "Accepted" : "Denied"}`
: "Unknown"}
</Td>
<Td>
<Button
onClick={async () => await fetchItem(result.id, "appeal")}
>
View
</Button>
</Td>
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
<br />
{[1 << 2, 1 << 3, 1 << 9, 1 << 10].find((p) => data.permissions & p) ? (
<>
<Heading size="lg">Inactivity Notices</Heading>
<TableContainer mb="16px">
<Table variant="simple">
<Thead>
<Tr>
<Th>Date</Th>
<Th>ID</Th>
<Th>Status</Th>
<Th>View</Th>
</Tr>
</Thead>
<Tbody>
{data.items[1]?.map((result) => {
return (
<Tr>
<Td>{timeStates[result.created_at].data}</Td>
<Td>{result.id}</Td>
<Td>
{result.open
? "Pending"
: Object.values(result.decisions).find((d) => !d)
? "Denied"
: "Approved"}
</Td>
<Td>
<Button
onClick={async () =>
await fetchItem(result.id, "inactivity")
}
>
View
</Button>
</Td>
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
<br />
</>
) : null}
<Heading size="lg">Reports</Heading>
<TableContainer>
<Table variant="simple">
<Thead>
<Tr>
<Th>Date</Th>
<Th>ID</Th>
<Th>Status</Th>
<Th>View</Th>
</Tr>
</Thead>
<Tbody>
{data.items[2]?.map((result) => {
return (
<Tr>
<Td>{timeStates[result.created_at].data}</Td>
<Td>{result.id}</Td>
<Td>{result.open ? "Pending" : "Reviewed"}</Td>
<Td>
<Button
onClick={async () => await fetchItem(result.id, "report")}
>
View
</Button>
</Td>
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
</Container>
);
}