diff --git a/app/routes/et-members.tsx b/app/routes/et-members.tsx index aae3e14..1de19c0 100644 --- a/app/routes/et-members.tsx +++ b/app/routes/et-members.tsx @@ -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) { + 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 ( + + + + Change Roblox User + + + + New Roblox Username + + setAddingMemberRoblox(e.target.value)} + placeholder="builderman" + /> + + + + + + + @@ -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,27 +411,25 @@ export default function () { - {isManagement ? ( - - - - - ) : null} + + + + @@ -386,20 +497,7 @@ export default function () { Roblox Username (optional) { - 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)} /> @@ -435,52 +533,58 @@ export default function () { {memberData.map((member) => ( - {isManagement ? ( - - {member.id} - - ) : ( - member.id - )} + + {member.id} + + + + { + setModalMember(member.id); + openNameChange(); + }} + > + {member.name} + + + + { + setModalMember(member.id); + openChangeRoblox(); + }} + > + {member.roblox_id} + - {member.name} - {member.roblox_id} - {isManagement ? ( - { - setModalMember(member.id); - onOpen(); - }} - > - {member.points} - - ) : ( - member.points - )} + { + setModalMember(member.id); + onOpen(); + }} + > + {member.points} + - {isManagement ? ( - { - setDelMember({ id: member.id, name: member.name }); - openDelConfirm(); - }} - > - Remove - - ) : null} + { + setDelMember({ id: member.id, name: member.name }); + openDelConfirm(); + }} + > + Remove + ))} - {isManagement ? ( - - Add Member - - ) : null} + + Add Member + ); } diff --git a/functions/api/events-team/team-members/user.ts b/functions/api/events-team/team-members/user.ts index 35f2ed4..044b7bf 100644 --- a/functions/api/events-team/team-members/user.ts +++ b/functions/api/events-team/team-members/user.ts @@ -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;