diff --git a/app/routes/me.tsx b/app/routes/me.tsx new file mode 100644 index 0000000..31b5eb8 --- /dev/null +++ b/app/routes/me.tsx @@ -0,0 +1,276 @@ +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 settledPromises.filter((p) => { + if (p.status === "fulfilled") return p.value.results; + + return null; + }) as any as ({ [k: string]: any }[] | null)[]; +} + +export default function () { + const data: ({ [k: string]: any }[] | null)[] = + useLoaderData(); + const timeStates: { + [k: number]: { data: string; set: Dispatch> }; + } = {}; + const toast = useToast(); + + for (const result of data) { + 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( + + View Appeal + + + Why were you banned? +
+ + {data.ban_reason} + +
+ +
+ Why should we unban you? +
+ + {data.reason_for_unban} + +
+ +
+ + What have you learned from your mistake? + +
+ + {data.learned} + +
+
, + ); + + break; + + case "inactivity": + setModalBody( + + View Inactivity Notice + + + Reason for Inactivity +
+ + {data.reason} + +
+ +
+ Start Date +
+ + {new Date(data.start).toLocaleDateString()} + +
+ +
+ End Date +
+ + {new Date(data.end).toLocaleDateString()} + +
+
, + ); + + break; + + case "report": + setModalBody( + + View Report + + + Username(s) +
+ + {data.target_usernames.toString()} + +
+ +
+ Description +
+ + {data.description ?? "No description"} + +
+ +
+ Media Links +
+ {data.resolved_attachments.map((attachment: string) => ( + + View media here + + ))} +
+
, + ); + + break; + + default: + setModalBody(); + + break; + } + } + + const { isOpen, onClose, onOpen } = useDisclosure(); + const [modalBody, setModalBody] = useState(); + + function resetModal() { + onClose(); + setModalBody(); + } + + return ( + + + + {modalBody} + + My Data +
+
+ Discord Appeals + + + + + + + + + + + + {data[0] + ? data[0].map((result) => { + return ( + + + + + + + ); + }) + : undefined} + +
DateIDStatusView
{timeStates[result.created_at].data}{result.id} + {result.open + ? "Pending" + : result.approved + ? "Accepted" + : "Denied"} + + +
+
+
+ ); +}