Skip to content
Permalink
84e7df6c30
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
443 lines (412 sloc) 13.1 KB
import {
Button,
Container,
Divider,
Heading,
Link,
ListItem,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
Stack,
Table,
TableCaption,
TableContainer,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
UnorderedList,
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 json_extract(user, '$.id') = ?
ORDER BY created_at DESC;`,
)
.bind(currentUser.id)
.all(),
);
const settledPromises = await Promise.allSettled(d1Promises);
let etData: { [k: string]: any } | null = null;
if (currentUser.permissions & (1 << 3)) {
etData = await context.env.D1.prepare(
"SELECT name, points, roblox_id FROM et_members WHERE id = ?;",
)
.bind(currentUser.id)
.first();
}
return {
etData,
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: {
etData: { [k: string]: any } | null;
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>
<br />
<Heading size="lg">Decisions</Heading>
<br />
<UnorderedList>
{data.departments.map((d: string) => {
const dept = d as "DM" | "ET" | "FM" | "WM";
return (
<ListItem>
<Stack alignItems="center" direction="row">
<Text>{d}:&nbsp;</Text>
{typeof data.decisions[dept] === "boolean" ? (
data.decisions[dept] ? (
<svg
fill="currentColor"
height="16"
viewBox="0 0 16 16"
width="16"
>
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z" />
</svg>
) : (
<svg
fill="currentColor"
height="16"
viewBox="0 0 16 16"
width="16"
>
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z" />
</svg>
)
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16"
>
<path d="M2.5 15a.5.5 0 1 1 0-1h1v-1a4.5 4.5 0 0 1 2.557-4.06c.29-.139.443-.377.443-.59v-.7c0-.213-.154-.451-.443-.59A4.5 4.5 0 0 1 3.5 3V2h-1a.5.5 0 0 1 0-1h11a.5.5 0 0 1 0 1h-1v1a4.5 4.5 0 0 1-2.557 4.06c-.29.139-.443.377-.443.59v.7c0 .213.154.451.443.59A4.5 4.5 0 0 1 12.5 13v1h1a.5.5 0 0 1 0 1zm2-13v1c0 .537.12 1.045.337 1.5h6.326c.216-.455.337-.963.337-1.5V2zm3 6.35c0 .701-.478 1.236-1.011 1.492A3.5 3.5 0 0 0 4.5 13s.866-1.299 3-1.48zm1 0v3.17c2.134.181 3 1.48 3 1.48a3.5 3.5 0 0 0-1.989-3.158C8.978 9.586 8.5 9.052 8.5 8.351z" />
</svg>
)}
</Stack>
</ListItem>
);
})}
</UnorderedList>
</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 />
{data.permissions & (1 << 3) ? (
<>
<Heading size="lg">Events Team Info</Heading>
<TableContainer mb="16px">
<Table variant="simple">
<TableCaption>
Reach out to ETM if this info is incorrect
</TableCaption>
<Thead>
<Tr>
<Th>Name</Th>
<Th>Points</Th>
<Th>Roblox ID</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>{data.etData?.name}</Td>
<Td>{data.etData?.points}</Td>
<Td>
<Link
href={`https://www.roblox.com/users/${data.etData?.roblox_id}`}
>
{data.etData?.roblox_id}
</Link>
</Td>
</Tr>
</Tbody>
</Table>
</TableContainer>
</>
) : null}
<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>Open for details</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>
);
}