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 === "number" ? `${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" : result.approved ? "Approved" : "Denied"} </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> ); }