Compare commits

..

2 Commits

Author SHA1 Message Date
64edc6169b New page 2025-10-14 02:10:25 -04:00
cf84722418 Remove old game mod management modal 2025-10-14 02:10:07 -04:00
5 changed files with 188 additions and 182 deletions

174
app/routes/gmm.tsx Normal file
View File

@@ -0,0 +1,174 @@
import {
Button,
Container,
FormControl,
FormLabel,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Table,
TableCaption,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr,
useDisclosure,
useToast,
} from "@chakra-ui/react";
import { useLoaderData } from "@remix-run/react";
import { useState } from "react";
export async function loader({ context }: { context: RequestContext }) {
if (!context.data.current_user)
throw new Response(null, {
status: 401,
});
if (
![
"165594923586945025",
"289372404541554689",
"396347223736057866",
].includes(context.data.current_user.id)
)
throw new Response(null, {
status: 403,
});
return (await context.env.DATA.list({ prefix: "gamemod_" }))?.keys ?? [];
}
export default function () {
const data: { [k: string]: any }[] = useLoaderData<typeof loader>();
const { isOpen, onClose, onOpen } = useDisclosure();
const [idToAdd, setIdToAdd] = useState<string>("");
const [nameToAdd, setNameToAdd] = useState<string>("");
const toast = useToast();
async function addMod(id: string, name: string) {
const response = await fetch("/api/gme/add", {
body: JSON.stringify({ name, user: id }),
headers: {
"content-type": "application/json",
},
method: "POST",
});
if (!response.ok) {
let msg = "Unknown error";
try {
msg = ((await response.json()) as { error: string }).error;
} catch {}
toast({
description: msg,
status: "error",
title: "Cannot add game mod",
});
} else {
toast({
description: `${name} was added as a game mod`,
status: "success",
title: "Game mod added",
});
}
setIdToAdd("");
setNameToAdd("");
}
return (
<Container maxW="container.lg">
<TableContainer>
<Table variant="simple">
<TableCaption>Currently active game mods</TableCaption>
<Thead>
<Tr>
<Th>ID</Th>
<Th>Name</Th>
<Th>Added At</Th>
<Th>Added By</Th>
<Th>Remove</Th>
</Tr>
</Thead>
<Tbody>
{data.map((item) => {
return (
<Tr key={item.metadata.id}>
<Td>{item.metadata.id}</Td>
<Td>{item.metadata.name}</Td>
<Td>{item.metadata.created_at}</Td>
<Td>{item.metadata.created_by}</Td>
<Td>
<Button>Remove</Button>
</Td>
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
<Button alignSelf="end" onClick={onOpen} pt="16px">
Add
</Button>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Add Game Mod</ModalHeader>
<ModalCloseButton />
<ModalBody>
<FormControl>
<FormLabel>Name</FormLabel>
<Input
maxLength={32}
onChange={(e) => setNameToAdd(e.target.value)}
value={nameToAdd}
/>
</FormControl>
<br />
<FormControl pt="16px">
<FormLabel>ID</FormLabel>
<Input
maxLength={19}
onChange={(e) => setIdToAdd(e.target.value)}
value={idToAdd}
/>
</FormControl>
</ModalBody>
<ModalFooter gap="8px">
<Button
onClick={() => {
setIdToAdd("");
setNameToAdd("");
}}
>
Cancel
</Button>
<Button
colorScheme="blue"
disabled={
idToAdd.length < 17 ||
idToAdd.length > 19 ||
nameToAdd.length < 1 ||
nameToAdd.length > 32
}
onClick={async () => {
await addMod(idToAdd, nameToAdd);
}}
>
Add
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</Container>
);
}

View File

@@ -30,7 +30,6 @@ import { useLoaderData } from "@remix-run/react";
import AppealBans from "../../components/AppealBans.js"; import AppealBans from "../../components/AppealBans.js";
import AppealCard from "../../components/AppealCard.js"; import AppealCard from "../../components/AppealCard.js";
import GameAppealCard from "../../components/GameAppealCard.js"; import GameAppealCard from "../../components/GameAppealCard.js";
import GameModManagementModal from "../../components/GameModManagementModal.js";
import NewGameBan from "../../components/NewGameBan.js"; import NewGameBan from "../../components/NewGameBan.js";
import NewInfractionModal from "../../components/NewInfractionModal.js"; import NewInfractionModal from "../../components/NewInfractionModal.js";
import ReportCard from "../../components/ReportCard.js"; import ReportCard from "../../components/ReportCard.js";
@@ -151,7 +150,7 @@ export default function () {
useEffect(() => { useEffect(() => {
if (messageChannel.current) { if (messageChannel.current) {
messageChannel.current.port1.onmessage = function (ev) { messageChannel.current.port1.onmessage = function (ev: any) {
const { data }: { data: string } = ev; const { data }: { data: string } = ev;
setEntries([...entries].filter((entry) => entry.id !== data)); setEntries([...entries].filter((entry) => entry.id !== data));
@@ -337,7 +336,11 @@ export default function () {
}, },
appeal_bans: useDisclosure(), appeal_bans: useDisclosure(),
game_ban: useDisclosure(), game_ban: useDisclosure(),
gme: useDisclosure(), gme: {
isOpen: false,
onClose: () => {},
onOpen: () => location.assign("/gmm"),
},
inactivity: useDisclosure(), inactivity: useDisclosure(),
infraction: useDisclosure(), infraction: useDisclosure(),
user_lookup: { user_lookup: {
@@ -431,10 +434,6 @@ export default function () {
isOpen={itemModals.appeal_bans.isOpen} isOpen={itemModals.appeal_bans.isOpen}
onClose={itemModals.appeal_bans.onClose} onClose={itemModals.appeal_bans.onClose}
/> />
<GameModManagementModal
isOpen={itemModals.gme.isOpen}
onClose={itemModals.gme.onClose}
/>
<NewGameBan <NewGameBan
isOpen={itemModals.game_ban.isOpen} isOpen={itemModals.game_ban.isOpen}
onClose={itemModals.game_ban.onClose} onClose={itemModals.game_ban.onClose}

View File

@@ -1,157 +0,0 @@
import {
Button,
HStack,
Input,
Link,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
Table,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr,
useToast,
} from "@chakra-ui/react";
import { useEffect, useState } from "react";
export default function (props: { isOpen: boolean; onClose: () => void }) {
const [mods, setMods] = useState([]);
const [userToAdd, setUserToAdd] = useState("");
const toast = useToast();
useEffect(() => {
if (!props.isOpen) return;
(async function () {
const gmeResp = await fetch("/api/gme/list");
if (!gmeResp.ok) {
toast({
description: "Failed to load GME data",
status: "error",
title: "Oops",
});
return;
}
setMods(await gmeResp.json());
})();
}, [props.isOpen]);
async function addUser() {
if (!userToAdd || !userToAdd.match(/^\d{17,19}$/)) {
toast({
description: "Please check your input and try again",
status: "error",
title: "Invalid user",
});
return;
}
const addResp = await fetch("/api/gme/add", {
body: JSON.stringify({ user: userToAdd }),
headers: {
"content-type": "application/json",
},
method: "POST",
});
if (!addResp.ok) {
toast({
description: ((await addResp.json()) as { error: string }).error,
status: "error",
title: "Oops",
});
return;
}
toast({
description: `User ${userToAdd} added`,
status: "success",
title: "Success",
});
props.onClose();
}
async function removeUser(user: string) {
const removeResp = await fetch("/api/gme/remove", {
body: JSON.stringify({ user }),
headers: {
"content-type": "application/json",
},
method: "POST",
});
if (!removeResp.ok) {
toast({
description: ((await removeResp.json()) as { error: string }).error,
status: "error",
title: "Oops",
});
} else {
toast({
description: `User ${user} removed`,
status: "success",
title: "Success",
});
setMods(mods.filter((mod: any) => mod.user !== user));
}
}
return (
<Modal isCentered isOpen={props.isOpen} onClose={props.onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Game Moderators</ModalHeader>
<ModalCloseButton />
<ModalBody>
<TableContainer>
<Table variant="simple">
<Thead>
<Tr>
<Th>User</Th>
<Th>Added At</Th>
<Th>Added By</Th>
<Th>Remove</Th>
</Tr>
</Thead>
<Tbody>
{mods.map((mod: any) => (
<Tr>
<Td>{mod.user}</Td>
<Td>{new Date(mod.metadata.time).toLocaleString()}</Td>
<Td>{mod.metadata.user}</Td>
<Td>
<Link onClick={async () => await removeUser(mod.user)}>
Remove
</Link>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<HStack mt="8px">
<Input
maxLength={19}
onChange={(e) => setUserToAdd(e.target.value)}
placeholder="1234567890987654321"
/>
<Button onClick={async () => await addUser()}>Add</Button>
</HStack>
</ModalBody>
</ModalContent>
</Modal>
);
}

View File

@@ -1,10 +1,13 @@
import { jsonError } from "../../common.js"; import { jsonError } from "../../common.js";
export async function onRequestPost(context: RequestContext) { export async function onRequestPost(context: RequestContext) {
const { user } = context.data.body; const { user, name } = context.data.body;
if (!user) return jsonError("No user provided", 400); if (!user) return jsonError("No user provided", 400);
if (typeof name !== "string" || name.length > 32)
return jsonError("Invalid name", 400);
const existingUser = await context.env.DATA.get(`gamemod_${user}`); const existingUser = await context.env.DATA.get(`gamemod_${user}`);
if (existingUser) return jsonError("Cannot add an existing user", 400); if (existingUser) return jsonError("Cannot add an existing user", 400);
@@ -21,9 +24,10 @@ export async function onRequestPost(context: RequestContext) {
if (!user.match(/^\d{17,19}$/)) return jsonError("Invalid User ID", 400); if (!user.match(/^\d{17,19}$/)) return jsonError("Invalid User ID", 400);
const data = { const data = {
time: Date.now(), created_at: Date.now(),
user: context.data.current_user.id, created_by: context.data.current_user.id,
name: context.data.current_user.username, id: user,
name,
}; };
await context.env.DATA.put(`gamemod_${user}`, JSON.stringify(data), { await context.env.DATA.put(`gamemod_${user}`, JSON.stringify(data), {

View File

@@ -1,14 +0,0 @@
import { jsonResponse } from "../../common.js";
export async function onRequestGet(context: RequestContext) {
const list = await context.env.DATA.list({ prefix: "gamemod_" });
const entries = [];
for (const key of list.keys)
entries.push({
metadata: key.metadata,
user: key.name.replace("gamemod_", ""),
});
return jsonResponse(JSON.stringify(entries));
}