Create revoke punishment modal
This commit is contained in:
@ -6,17 +6,28 @@ import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
Center,
|
||||
Container,
|
||||
Flex,
|
||||
Heading,
|
||||
HStack,
|
||||
Image,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
Link,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Spacer,
|
||||
Stack,
|
||||
StackDivider,
|
||||
Text,
|
||||
useToast
|
||||
useDisclosure,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { type FormEvent, type ReactElement, useState } from "react";
|
||||
|
||||
@ -25,7 +36,7 @@ export async function loader({ context }: { context: RequestContext }) {
|
||||
|
||||
if (!currentUser)
|
||||
throw new Response(null, {
|
||||
status: 401
|
||||
status: 401,
|
||||
});
|
||||
|
||||
if (
|
||||
@ -33,7 +44,7 @@ export async function loader({ context }: { context: RequestContext }) {
|
||||
!(currentUser.permissions & (1 << 8))
|
||||
)
|
||||
throw new Response(null, {
|
||||
status: 403
|
||||
status: 403,
|
||||
});
|
||||
|
||||
return null;
|
||||
@ -43,15 +54,18 @@ export function meta() {
|
||||
return [{ title: "Hammer - Car Crushers" }];
|
||||
}
|
||||
|
||||
export default function() {
|
||||
export default function () {
|
||||
const [queriedUsername, setQueriedUsername] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [uid, setUid] = useState("");
|
||||
const [status, setStatus] = useState("");
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [avatarUrl, setAvatarUrl] = useState("");
|
||||
const [ticketLink, setTicketLink] = useState("");
|
||||
const [history, setHistory] = useState([] as ReactElement[]);
|
||||
const [hasResults, setHasResults] = useState(true);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { isOpen, onClose, onOpen } = useDisclosure();
|
||||
const toast = useToast();
|
||||
|
||||
async function getHistory() {
|
||||
@ -64,11 +78,13 @@ export default function() {
|
||||
return toast({
|
||||
title: "Validation Error",
|
||||
description: `Username is too short`,
|
||||
status: "error"
|
||||
status: "error",
|
||||
});
|
||||
}
|
||||
|
||||
const historyResp = await fetch(`/api/game-bans/${username}/history`);
|
||||
const historyResp = await fetch(
|
||||
`/api/game-bans/${queriedUsername}/history`,
|
||||
);
|
||||
|
||||
if (!historyResp.ok) {
|
||||
setLoading(false);
|
||||
@ -77,13 +93,13 @@ export default function() {
|
||||
description: `${
|
||||
((await historyResp.json()) as { error: string }).error
|
||||
}`,
|
||||
status: "error"
|
||||
status: "error",
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
history,
|
||||
user
|
||||
user,
|
||||
}: {
|
||||
history: { [k: string]: any }[];
|
||||
user: { avatar: string | null; id: number; name: string };
|
||||
@ -92,10 +108,11 @@ export default function() {
|
||||
if (!history.length) {
|
||||
setLoading(false);
|
||||
setHasResults(false);
|
||||
setStatus("");
|
||||
return toast({
|
||||
title: "Nothing Found",
|
||||
description: "This user doesn't have any moderation history.",
|
||||
status: "info"
|
||||
status: "info",
|
||||
});
|
||||
}
|
||||
|
||||
@ -104,6 +121,7 @@ export default function() {
|
||||
|
||||
setAvatarUrl(user.avatar ?? "https://i.hep.gg/floppa");
|
||||
setUid(user.id.toString());
|
||||
setUsername(user.name);
|
||||
setStatus(history[history.length - 1].entity.properties.action.stringValue);
|
||||
|
||||
for (const entry of history) {
|
||||
@ -123,7 +141,7 @@ export default function() {
|
||||
<CardHeader>
|
||||
<Heading size="md">
|
||||
{new Date(
|
||||
parseInt(entry.entity.properties.executed_at.integerValue)
|
||||
parseInt(entry.entity.properties.executed_at.integerValue),
|
||||
).toLocaleString()}
|
||||
</Heading>
|
||||
</CardHeader>
|
||||
@ -150,7 +168,7 @@ export default function() {
|
||||
</Stack>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Container>
|
||||
</Container>,
|
||||
);
|
||||
}
|
||||
|
||||
@ -159,8 +177,111 @@ export default function() {
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
const invalidIcon = (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
color="red"
|
||||
>
|
||||
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const validIcon = (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 16 16"
|
||||
color="green"
|
||||
>
|
||||
<path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.267.267 0 0 1 .02-.022z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
async function revokePunishment() {
|
||||
const revokeResponse = await fetch(`/api/game-bans/${uid}/revoke`, {
|
||||
body: JSON.stringify({ ticket_link: ticketLink }),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
if (!revokeResponse.ok) {
|
||||
let error: string;
|
||||
|
||||
try {
|
||||
error = ((await revokeResponse.json()) as { error: string }).error;
|
||||
} catch {
|
||||
error = "Unknown error";
|
||||
}
|
||||
|
||||
toast({
|
||||
description: error,
|
||||
isClosable: true,
|
||||
status: "error",
|
||||
title: "Oops",
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
description: `Punishment revoked for ${username}`,
|
||||
isClosable: true,
|
||||
status: "success",
|
||||
title: "Success",
|
||||
});
|
||||
}
|
||||
|
||||
onClose();
|
||||
setTicketLink("");
|
||||
}
|
||||
|
||||
return (
|
||||
<Container maxW="container.md">
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Revoke punishment for {username}</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<InputGroup>
|
||||
<Input
|
||||
onChange={(e) => setTicketLink(e.target.value)}
|
||||
placeholder="https://carcrushers.modmail.dev/logs/abcdef123456"
|
||||
maxLength={49}
|
||||
/>
|
||||
<InputRightElement>
|
||||
{ticketLink.match(
|
||||
/https:\/\/carcrushers\.modmail\.dev\/logs\/[a-f\d]{12}$/,
|
||||
)
|
||||
? validIcon
|
||||
: invalidIcon}
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onClose();
|
||||
setTicketLink("");
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
colorScheme="red"
|
||||
mr="4px"
|
||||
onClick={async () => await revokePunishment()}
|
||||
>
|
||||
Revoke
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<Heading>User Lookup</Heading>
|
||||
<Text>Look up a user's punishment history here.</Text>
|
||||
{!hasResults ? (
|
||||
@ -178,7 +299,7 @@ export default function() {
|
||||
|
||||
if (data?.match(/\W/)) e.preventDefault();
|
||||
}}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
onChange={(e) => setQueriedUsername(e.target.value)}
|
||||
placeholder="Roblox username"
|
||||
/>
|
||||
<Button
|
||||
@ -189,10 +310,20 @@ export default function() {
|
||||
Search
|
||||
</Button>
|
||||
</HStack>
|
||||
<Center mb={3} mt={3}>
|
||||
<Container mb={3} mt={3}>
|
||||
<Card visibility={visible ? "visible" : "hidden"}>
|
||||
<CardBody>
|
||||
<Image mb="16" src={avatarUrl} />
|
||||
<Flex>
|
||||
<Image mb="16" src={avatarUrl} />
|
||||
<Spacer />
|
||||
<Button
|
||||
colorScheme="red"
|
||||
onClick={onOpen}
|
||||
visibility={status ? "visible" : "hidden"}
|
||||
>
|
||||
Revoke Punishment
|
||||
</Button>
|
||||
</Flex>
|
||||
<Stack divider={<StackDivider />} spacing="6">
|
||||
<Box>
|
||||
<Heading size="xs">USERNAME</Heading>
|
||||
@ -215,7 +346,7 @@ export default function() {
|
||||
</Stack>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Center>
|
||||
</Container>
|
||||
{history}
|
||||
</Container>
|
||||
);
|
||||
|
Reference in New Issue
Block a user