Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Create start of /me route
- Loading branch information
Showing
1 changed file
with
276 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<typeof loader>(); | ||
const timeStates: { | ||
[k: number]: { data: string; set: Dispatch<SetStateAction<string>> }; | ||
} = {}; | ||
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( | ||
<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; | ||
} | ||
} | ||
|
||
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> | ||
<Table variant="simple"> | ||
<Thead> | ||
<Tr> | ||
<Th>Date</Th> | ||
<Th>ID</Th> | ||
<Th>Status</Th> | ||
<Th>View</Th> | ||
</Tr> | ||
</Thead> | ||
<Tbody> | ||
{data[0] | ||
? data[0].map((result) => { | ||
return ( | ||
<Tr> | ||
<Td>{timeStates[result.created_at].data}</Td> | ||
<Td>{result.id}</Td> | ||
<Td> | ||
{result.open | ||
? "Pending" | ||
: result.approved | ||
? "Accepted" | ||
: "Denied"} | ||
</Td> | ||
<Td> | ||
<Button | ||
onClick={async () => | ||
await fetchItem(result.id, "appeal") | ||
} | ||
> | ||
View | ||
</Button> | ||
</Td> | ||
</Tr> | ||
); | ||
}) | ||
: undefined} | ||
</Tbody> | ||
</Table> | ||
</TableContainer> | ||
</Container> | ||
); | ||
} |