155 lines
3.9 KiB
TypeScript
155 lines
3.9 KiB
TypeScript
import {
|
|
Box,
|
|
Button,
|
|
Card,
|
|
CardBody,
|
|
CardHeader,
|
|
Container,
|
|
Heading,
|
|
HStack,
|
|
Image,
|
|
Input,
|
|
Link,
|
|
Stack,
|
|
StackDivider,
|
|
Text,
|
|
} from "@chakra-ui/react";
|
|
import { type FormEvent, useState } from "react";
|
|
|
|
export async function loader({ context }: { context: RequestContext }) {
|
|
const { current_user: currentUser } = context.data;
|
|
|
|
if (!currentUser)
|
|
throw new Response(null, {
|
|
status: 401,
|
|
});
|
|
|
|
if (!(currentUser.permissions & (1 << 5)))
|
|
throw new Response(null, {
|
|
status: 403,
|
|
});
|
|
|
|
return null;
|
|
}
|
|
|
|
export function meta() {
|
|
return [{ title: "Hammer - Car Crushers" }];
|
|
}
|
|
|
|
export default function () {
|
|
const [username, setUsername] = useState("");
|
|
const [uid, setUid] = useState("");
|
|
const [status, setStatus] = useState("");
|
|
const [visible, setVisible] = useState(false);
|
|
const [avatarUrl, setAvatarUrl] = useState("");
|
|
const [history, setHistory] = useState([]);
|
|
|
|
async function getHistory() {
|
|
const username = (document.getElementById("username") as HTMLInputElement)
|
|
.value;
|
|
|
|
if (username.length < 4) return alert("Username is too short!");
|
|
|
|
const historyResp = await fetch(`/api/game-bans/${username}/history`);
|
|
|
|
if (!historyResp.ok)
|
|
return alert(
|
|
`ERROR: ${((await historyResp.json()) as { error: string }).error}`,
|
|
);
|
|
|
|
const history: { [k: string]: any }[] = await historyResp.json();
|
|
|
|
if (!history.length) return alert("No history for this user.");
|
|
|
|
const cardList = [];
|
|
|
|
for (const entry of history) {
|
|
const url = entry.entity.properties.evidence.stringValue;
|
|
const isUrl = () => {
|
|
try {
|
|
new URL(url).href;
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
cardList.push(
|
|
<Card>
|
|
<CardHeader>
|
|
<Heading size="md">{new Date().toLocaleString()}</Heading>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<Stack divider={<StackDivider />} spacing="4">
|
|
<Box>
|
|
<Heading size="xs">ACTION</Heading>
|
|
<Text pt="2" size="sm">
|
|
{entry.entity.properties.action.stringValue}
|
|
</Text>
|
|
</Box>
|
|
<Box>
|
|
<Heading size="xs">EVIDENCE</Heading>
|
|
<Text pt="2" size="sm">
|
|
{isUrl() ? (
|
|
<Link color="#646cff" href={url}>
|
|
{url}
|
|
</Link>
|
|
) : (
|
|
url
|
|
)}
|
|
</Text>
|
|
</Box>
|
|
</Stack>
|
|
</CardBody>
|
|
</Card>,
|
|
);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Container maxW="container.md">
|
|
<Heading>User Lookup</Heading>
|
|
<HStack>
|
|
<Input
|
|
id="username"
|
|
onBeforeInput={(e) => {
|
|
const { data }: { data?: string } & FormEvent<HTMLInputElement> = e;
|
|
|
|
if (data?.match(/\W/)) e.preventDefault();
|
|
}}
|
|
placeholder="Roblox username"
|
|
/>
|
|
<Button ml="8px" onClick={async () => await getHistory()}>
|
|
Search
|
|
</Button>
|
|
</HStack>
|
|
<Card maxW="sm" visibility={visible ? "visible" : "hidden"}>
|
|
<CardBody>
|
|
<Image mb="16" src={avatarUrl} />
|
|
<Stack divider={<StackDivider />} spacing="6">
|
|
<Box>
|
|
<Heading size="xs">USERNAME</Heading>
|
|
<Text pt="2" fontSize="sm">
|
|
{username}
|
|
</Text>
|
|
</Box>
|
|
<Box>
|
|
<Heading size="xs">USER ID</Heading>
|
|
<Text pt="2" fontSize="sm">
|
|
{uid}
|
|
</Text>
|
|
</Box>
|
|
<Box>
|
|
<Heading size="xs">MODERATION STATUS</Heading>
|
|
<Text pt="2" fontSize="sm">
|
|
{status}
|
|
</Text>
|
|
</Box>
|
|
</Stack>
|
|
</CardBody>
|
|
</Card>
|
|
{history}
|
|
</Container>
|
|
);
|
|
}
|