Add name/roblox change support
This commit is contained in:
parent
5fe23184c9
commit
2f8ff3b3cc
@ -78,8 +78,7 @@ export async function loader({ context }: { context: RequestContext }) {
|
||||
}
|
||||
}
|
||||
|
||||
return { can_manage: true, members } as {
|
||||
can_manage: boolean;
|
||||
return { members } as {
|
||||
members: { [k: string]: any }[];
|
||||
};
|
||||
}
|
||||
@ -171,7 +170,26 @@ export default function () {
|
||||
onClose: closeNameChange,
|
||||
onOpen: openNameChange,
|
||||
} = useDisclosure();
|
||||
const isManagement = data.can_manage;
|
||||
const {
|
||||
isOpen: isChangeRobloxOpen,
|
||||
onClose: closeChangeRoblox,
|
||||
onOpen: openChangeRoblox,
|
||||
} = useDisclosure();
|
||||
|
||||
function validateRobloxName(e: FormEvent<HTMLInputElement>) {
|
||||
const data = (e.target as HTMLInputElement).value as string;
|
||||
|
||||
if (!data) return;
|
||||
|
||||
if (
|
||||
data.match(/\W/) ||
|
||||
data.length > 20 ||
|
||||
// Need Number pseudo-constructor since matches might be null
|
||||
(data.match(/_/g)?.length || 0) > 1 ||
|
||||
data.startsWith("_")
|
||||
)
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
async function updatePoints(id: string, points: number) {
|
||||
const updateResp = await fetch(`/api/events-team/points/${id}`, {
|
||||
@ -207,6 +225,93 @@ export default function () {
|
||||
|
||||
return (
|
||||
<Container maxW="container.lg">
|
||||
<Modal isOpen={isChangeRobloxOpen} onClose={closeChangeRoblox}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Change Roblox User</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<Heading mb="8px" size="xs">
|
||||
New Roblox Username
|
||||
</Heading>
|
||||
<Input
|
||||
maxLength={20}
|
||||
onBeforeInput={validateRobloxName}
|
||||
onChange={(e) => setAddingMemberRoblox(e.target.value)}
|
||||
placeholder="builderman"
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setAddingMemberRoblox("");
|
||||
closeChangeRoblox();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
colorScheme="blue"
|
||||
ml="8px"
|
||||
onClick={async () => {
|
||||
const changeResp = await fetch(
|
||||
"/api/events-team/team-members/user",
|
||||
{
|
||||
body: JSON.stringify({
|
||||
id: currentModalMember,
|
||||
roblox_username: addingMemberRoblox,
|
||||
}),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
method: "PATCH",
|
||||
},
|
||||
);
|
||||
|
||||
if (!changeResp.ok) {
|
||||
let errorMsg = "Unknown error";
|
||||
|
||||
try {
|
||||
errorMsg = ((await changeResp.json()) as { error: string })
|
||||
.error;
|
||||
} catch {}
|
||||
|
||||
toast({
|
||||
description: errorMsg,
|
||||
status: "error",
|
||||
title: "Failed to change",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
description: "Roblox information updated",
|
||||
status: "success",
|
||||
title: "Change successful",
|
||||
});
|
||||
|
||||
const newMemberData = memberData;
|
||||
newMemberData[
|
||||
memberData.findIndex((m) => m.id === currentModalMember)
|
||||
].roblox_id = (
|
||||
(await changeResp.json()) as {
|
||||
name: string;
|
||||
roblox_id: number;
|
||||
}
|
||||
).roblox_id;
|
||||
|
||||
setMemberData([...newMemberData]);
|
||||
closeChangeRoblox();
|
||||
setModalMember("");
|
||||
setAddingMemberRoblox("");
|
||||
}}
|
||||
>
|
||||
Change
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<Modal isOpen={isNameChangeOpen} onClose={closeNameChange}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
@ -253,8 +358,16 @@ export default function () {
|
||||
setAddingMemberName("");
|
||||
|
||||
if (!nameUpdateResp.ok) {
|
||||
let errorMsg = "Unknown error";
|
||||
|
||||
try {
|
||||
errorMsg = (
|
||||
(await nameUpdateResp.json()) as { error: string }
|
||||
).error;
|
||||
} catch {}
|
||||
|
||||
toast({
|
||||
description: "Failed to update name, try again later.",
|
||||
description: errorMsg,
|
||||
status: "error",
|
||||
title: "Error",
|
||||
});
|
||||
@ -298,7 +411,6 @@ export default function () {
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
</ModalBody>
|
||||
{isManagement ? (
|
||||
<ModalFooter>
|
||||
<Button
|
||||
onClick={() => {
|
||||
@ -318,7 +430,6 @@ export default function () {
|
||||
Update Points
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
) : null}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<Modal isOpen={isDelConfirmOpen} onClose={closeDelConfirm}>
|
||||
@ -386,20 +497,7 @@ export default function () {
|
||||
<Heading size="xs">Roblox Username (optional)</Heading>
|
||||
<Input
|
||||
maxLength={20}
|
||||
onBeforeInput={(e) => {
|
||||
const data = (e.target as HTMLInputElement).value as string;
|
||||
|
||||
if (!data) return;
|
||||
|
||||
if (
|
||||
data.match(/\W/) ||
|
||||
data.length > 20 ||
|
||||
// Need Number pseudo-constructor since matches might be null
|
||||
(data.match(/_/g)?.length || 0) > 1 ||
|
||||
data.startsWith("_")
|
||||
)
|
||||
e.preventDefault();
|
||||
}}
|
||||
onBeforeInput={validateRobloxName}
|
||||
onChange={(e) => setAddingMemberRoblox(e.target.value)}
|
||||
/>
|
||||
</ModalBody>
|
||||
@ -435,18 +533,31 @@ export default function () {
|
||||
{memberData.map((member) => (
|
||||
<Tr>
|
||||
<Td>
|
||||
{isManagement ? (
|
||||
<Link href={`/et-members/strikes/${member.id}`}>
|
||||
{member.id}
|
||||
</Link>
|
||||
) : (
|
||||
member.id
|
||||
)}
|
||||
</Td>
|
||||
<Td>{member.name}</Td>
|
||||
<Td>{member.roblox_id}</Td>
|
||||
<Td>
|
||||
{isManagement ? (
|
||||
<Link
|
||||
onClick={() => {
|
||||
setModalMember(member.id);
|
||||
openNameChange();
|
||||
}}
|
||||
>
|
||||
{member.name}
|
||||
</Link>
|
||||
</Td>
|
||||
<Td>
|
||||
<Link
|
||||
onClick={() => {
|
||||
setModalMember(member.id);
|
||||
openChangeRoblox();
|
||||
}}
|
||||
>
|
||||
{member.roblox_id}
|
||||
</Link>
|
||||
</Td>
|
||||
<Td>
|
||||
<Link
|
||||
onClick={() => {
|
||||
setModalMember(member.id);
|
||||
@ -455,12 +566,8 @@ export default function () {
|
||||
>
|
||||
{member.points}
|
||||
</Link>
|
||||
) : (
|
||||
member.points
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{isManagement ? (
|
||||
<Link
|
||||
onClick={() => {
|
||||
setDelMember({ id: member.id, name: member.name });
|
||||
@ -469,18 +576,15 @@ export default function () {
|
||||
>
|
||||
Remove
|
||||
</Link>
|
||||
) : null}
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{isManagement ? (
|
||||
<Link color="#646cff" onClick={openAddMember} mt="16px">
|
||||
Add Member
|
||||
</Link>
|
||||
) : null}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { jsonError } from "../../../common.js";
|
||||
import { jsonError, jsonResponse } from "../../../common.js";
|
||||
|
||||
export async function onRequestDelete(context: RequestContext) {
|
||||
let body: { id?: string } = {};
|
||||
@ -28,6 +28,62 @@ export async function onRequestDelete(context: RequestContext) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function onRequestPatch(context: RequestContext) {
|
||||
let body: { id?: string; name?: string; roblox_username?: string } = {};
|
||||
|
||||
try {
|
||||
body = await context.request.json();
|
||||
} catch {
|
||||
return jsonError("Invalid body", 400);
|
||||
}
|
||||
|
||||
if (typeof body.name !== "string" && typeof body.roblox_username !== "string")
|
||||
return jsonError("At least one property must be provided", 400);
|
||||
|
||||
const updates = [];
|
||||
|
||||
if (body.name?.length) updates.push({ query: "name = ?", value: body.name });
|
||||
|
||||
if (typeof body.roblox_username === "string" && body.roblox_username) {
|
||||
const robloxResolveResp = await fetch(
|
||||
"https://users.roblox.com/v1/usernames/users",
|
||||
{
|
||||
body: JSON.stringify({
|
||||
excludeBannedUsers: true,
|
||||
usernames: [body.roblox_username],
|
||||
}),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
},
|
||||
);
|
||||
|
||||
const { data } = (await robloxResolveResp.json()) as {
|
||||
data: { [k: string]: any }[];
|
||||
};
|
||||
|
||||
if (!data.length)
|
||||
return jsonError("No Roblox user exists with that name", 400);
|
||||
|
||||
updates.push({ query: "roblox_id = ?", value: data[0].id });
|
||||
}
|
||||
|
||||
await context.env.D1.prepare(
|
||||
`UPDATE et_members
|
||||
SET (${updates.join(", ")});`,
|
||||
)
|
||||
.bind(...updates.map((u) => u.value))
|
||||
.run();
|
||||
|
||||
return jsonResponse(
|
||||
JSON.stringify({
|
||||
name: body.name,
|
||||
roblox_id: updates.find((u) => typeof u.value === "number")?.value,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export async function onRequestPost(context: RequestContext) {
|
||||
const { id, name, roblox_username } = context.data.body;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user