-
Notifications
You must be signed in to change notification settings - Fork 4
Initial UI for Creating and Joining Lobby #548
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces the initial user interface for creating and joining game lobbies in the duel feature. It establishes the basic routing, API layer, and UI components needed for lobby management, though several critical implementation details remain incomplete with placeholder code.
Key Changes:
- Added
/lobbyroute andLobbyEntryPagecomponent with UI for creating/joining lobbies - Created API mutation hooks for lobby creation and joining operations
- Implemented basic client-side validation for lobby codes
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 12 comments.
| File | Description |
|---|---|
js/src/lib/router.tsx |
Added /lobby route to enable navigation to the lobby entry page (note: contains duplicate /privacy route that needs removal) |
js/src/lib/api/queries/duel/lobby/index.ts |
Created mutation hooks for createLobby and joinLobby, but implementations are incomplete with placeholder lobby codes and missing parameter handling |
js/src/app/duel/_components/lobby/LobbyEntry.page.tsx |
Implemented lobby entry UI with create/join options, but missing API integration for join functionality, error handling, and accessibility features |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <Button | ||
| radius="md" | ||
| color="green" | ||
| onClick={handleJoinLobby} | ||
| style={{ | ||
| backgroundColor: "transparent", | ||
| border: "1px solid rgba(0,255,125,0.4)", | ||
| width: "100%", | ||
| color: "rgba(0,255,125,0.85)", | ||
| }} | ||
| > | ||
| Join | ||
| </Button> |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing input validation feedback. When the join code is less than 4 characters and the user clicks "Join", nothing happens. Consider disabling the button or showing an error message when the input is invalid. For example, add a disabled prop to the Button: disabled={joinCode.trim().length < 4}.
| <Paper | ||
| withBorder | ||
| radius="md" | ||
| onClick={() => | ||
| mutate(undefined, { | ||
| onSuccess: (data) => { | ||
| if (data?.lobbyCode) { | ||
| navigate(`/lobby/${data.lobbyCode}`); | ||
| } | ||
| }, | ||
| }) | ||
| } | ||
| style={{ | ||
| cursor: "pointer", | ||
| width: "48%", | ||
| minHeight: "20vh", | ||
| borderColor: "rgba(0,255,125,0.4)", | ||
| transition: "0.2s", | ||
| }} | ||
| onMouseEnter={(e) => | ||
| (e.currentTarget.style.boxShadow = | ||
| "0 0 1.5vw rgba(0,255,125,0.5)") | ||
| } | ||
| onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")} | ||
| > | ||
| <Center style={{ height: "100%" }}> | ||
| <Stack align="center" gap={4}> | ||
| <Title | ||
| order={3} | ||
| style={{ | ||
| color: "white", | ||
| }} | ||
| > | ||
| Create Lobby | ||
| </Title> | ||
| <Text size="sm" c="dimmed"> | ||
| Generate a new lobby code | ||
| </Text> | ||
| </Stack> | ||
| </Center> | ||
| </Paper> |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "Create Lobby" Paper element uses onClick but is not keyboard accessible. Users navigating with a keyboard cannot activate this element. Consider using a Button component instead, or add role="button", tabIndex={0}, and onKeyDown handler to support keyboard navigation (Enter and Space keys).
| <Stack align="center" gap={12} style={{ width: "100%" }}> | ||
| <Title | ||
| order={3} | ||
| style={{ | ||
| color: "white", | ||
| fontSize: "clamp(1rem, 2.5vw, 1.6rem)", | ||
| }} | ||
| > | ||
| Join Lobby | ||
| </Title> | ||
| <TextInput | ||
| placeholder="Enter lobby code" | ||
| value={joinCode} | ||
| onChange={(e) => setJoinCode(e.target.value)} | ||
| radius="md" | ||
| styles={{ | ||
| input: { | ||
| backgroundColor: "rgba(255,255,255,0.03)", | ||
| border: "1px solid rgba(0,255,125,0.4)", | ||
| color: "white", | ||
| textAlign: "center", | ||
| }, | ||
| }} | ||
| style={{ width: "100%" }} | ||
| /> | ||
| <Button | ||
| radius="md" | ||
| color="green" | ||
| onClick={handleJoinLobby} | ||
| style={{ | ||
| backgroundColor: "transparent", | ||
| border: "1px solid rgba(0,255,125,0.4)", | ||
| width: "100%", | ||
| color: "rgba(0,255,125,0.85)", | ||
| }} | ||
| > | ||
| Join | ||
| </Button> | ||
| </Stack> |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "Join Lobby" TextInput doesn't support pressing Enter to submit. Users expect to press Enter after typing a lobby code. Consider adding onKeyDown={(e) => { if (e.key === 'Enter') handleJoinLobby(); }} to the TextInput, or wrap the input and button in a <form> element with onSubmit.
| <Stack align="center" gap={12} style={{ width: "100%" }}> | |
| <Title | |
| order={3} | |
| style={{ | |
| color: "white", | |
| fontSize: "clamp(1rem, 2.5vw, 1.6rem)", | |
| }} | |
| > | |
| Join Lobby | |
| </Title> | |
| <TextInput | |
| placeholder="Enter lobby code" | |
| value={joinCode} | |
| onChange={(e) => setJoinCode(e.target.value)} | |
| radius="md" | |
| styles={{ | |
| input: { | |
| backgroundColor: "rgba(255,255,255,0.03)", | |
| border: "1px solid rgba(0,255,125,0.4)", | |
| color: "white", | |
| textAlign: "center", | |
| }, | |
| }} | |
| style={{ width: "100%" }} | |
| /> | |
| <Button | |
| radius="md" | |
| color="green" | |
| onClick={handleJoinLobby} | |
| style={{ | |
| backgroundColor: "transparent", | |
| border: "1px solid rgba(0,255,125,0.4)", | |
| width: "100%", | |
| color: "rgba(0,255,125,0.85)", | |
| }} | |
| > | |
| Join | |
| </Button> | |
| </Stack> | |
| <form | |
| onSubmit={(e) => { | |
| e.preventDefault(); | |
| handleJoinLobby(); | |
| }} | |
| style={{ width: "100%" }} | |
| > | |
| <Stack align="center" gap={12} style={{ width: "100%" }}> | |
| <Title | |
| order={3} | |
| style={{ | |
| color: "white", | |
| fontSize: "clamp(1rem, 2.5vw, 1.6rem)", | |
| }} | |
| > | |
| Join Lobby | |
| </Title> | |
| <TextInput | |
| placeholder="Enter lobby code" | |
| value={joinCode} | |
| onChange={(e) => setJoinCode(e.target.value)} | |
| radius="md" | |
| styles={{ | |
| input: { | |
| backgroundColor: "rgba(255,255,255,0.03)", | |
| border: "1px solid rgba(0,255,125,0.4)", | |
| color: "white", | |
| textAlign: "center", | |
| }, | |
| }} | |
| style={{ width: "100%" }} | |
| /> | |
| <Button | |
| type="submit" | |
| radius="md" | |
| color="green" | |
| style={{ | |
| backgroundColor: "transparent", | |
| border: "1px solid rgba(0,255,125,0.4)", | |
| width: "100%", | |
| color: "rgba(0,255,125,0.85)", | |
| }} | |
| > | |
| Join | |
| </Button> | |
| </Stack> | |
| </form> |
| async function joinLobby() { | ||
| const { url, method, res } = ApiURL.create("/api/duel/lobby/join", { | ||
| method: "POST", | ||
| }); | ||
| const response = await fetch(url, { | ||
| method | ||
| }); | ||
|
|
||
| const json = res(await response.json()); | ||
|
|
||
| if (json.success) { | ||
| return { | ||
| lobbyCode: "0XXXX" // Placeholder |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The joinLobby function doesn't accept a lobbyCode parameter, but this is required to join a specific lobby. The function should accept an argument like { lobbyCode: string } and pass it to the API endpoint in the request body using the req() function, similar to how other mutations in the codebase handle parameters (e.g., verifyPassword in js/src/lib/api/queries/club/index.ts).
| async function joinLobby() { | |
| const { url, method, res } = ApiURL.create("/api/duel/lobby/join", { | |
| method: "POST", | |
| }); | |
| const response = await fetch(url, { | |
| method | |
| }); | |
| const json = res(await response.json()); | |
| if (json.success) { | |
| return { | |
| lobbyCode: "0XXXX" // Placeholder | |
| async function joinLobby({ lobbyCode }: { lobbyCode: string }) { | |
| const { url, method, res } = ApiURL.create("/api/duel/lobby/join", { | |
| method: "POST", | |
| }); | |
| const response = await fetch(url, { | |
| method, | |
| headers: { | |
| "Content-Type": "application/json" | |
| }, | |
| body: JSON.stringify({ lobbyCode }) | |
| }); | |
| const json = res(await response.json()); | |
| if (json.success) { | |
| return { | |
| lobbyCode: json.lobbyCode || lobbyCode |
| if (json.success) { | ||
| return { | ||
| lobbyCode: "0XXXX" // Placeholder | ||
| }; |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both createLobby and joinLobby functions return a hardcoded placeholder "0XXXX" instead of the actual lobby code from the API response. The lobby code should be extracted from json.payload (e.g., json.payload.lobbyCode or similar field based on the API response structure). See js/src/lib/api/queries/auth/index.ts lines 24-29 for an example of extracting data from the API response payload.
| const { url, method, res } = ApiURL.create("/api/duel/party/create", { | ||
| method: "POST", | ||
| }); | ||
| const response = await fetch(url, { | ||
| method | ||
| }); | ||
|
|
||
| const json = res(await response.json()); | ||
|
|
||
| if (json.success) { | ||
| return { | ||
| lobbyCode: "0XXXX" // Placeholder | ||
| }; | ||
| } | ||
|
|
||
| return { lobbyCode: "" }; | ||
| } | ||
|
|
||
| export const useJoinLobbyMutation = () => { | ||
| return useMutation({ | ||
| mutationFn: joinLobby, | ||
| }); | ||
| } | ||
|
|
||
| async function joinLobby() { | ||
| const { url, method, res } = ApiURL.create("/api/duel/lobby/join", { |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API endpoint paths are inconsistent: createLobby uses /api/duel/party/create while joinLobby uses /api/duel/lobby/join. For consistency and clarity, both should use either "party" or "lobby" terminology. Consider using /api/duel/lobby/create and /api/duel/lobby/join to maintain a consistent naming pattern.
| onClick={() => | ||
| mutate(undefined, { | ||
| onSuccess: (data) => { | ||
| if (data?.lobbyCode) { | ||
| navigate(`/lobby/${data.lobbyCode}`); | ||
| } | ||
| }, | ||
| }) |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling for the createLobby mutation. If the API call fails or returns an error, the user receives no feedback. Consider adding an onError callback to show an error notification using notifications.show(), similar to the pattern used in js/src/app/club/[clubSlug]/_components/ClubSignUpForm.tsx lines 64-77.
| <TextInput | ||
| placeholder="Enter lobby code" | ||
| value={joinCode} | ||
| onChange={(e) => setJoinCode(e.target.value)} | ||
| radius="md" | ||
| styles={{ | ||
| input: { | ||
| backgroundColor: "rgba(255,255,255,0.03)", | ||
| border: "1px solid rgba(0,255,125,0.4)", | ||
| color: "white", | ||
| textAlign: "center", | ||
| }, | ||
| }} | ||
| style={{ width: "100%" }} | ||
| /> |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TextInput for entering the lobby code is missing accessibility attributes. Consider adding aria-label="Lobby code" or an id with a corresponding label element to improve screen reader support.
| } | ||
|
|
||
| export const useJoinLobbyMutation = () => { | ||
| return useMutation({ | ||
| mutationFn: joinLobby, | ||
| }); | ||
| } | ||
|
|
||
| async function joinLobby() { | ||
| const { url, method, res } = ApiURL.create("/api/duel/lobby/join", { | ||
| method: "POST", | ||
| }); | ||
| const response = await fetch(url, { | ||
| method | ||
| }); | ||
|
|
||
| const json = res(await response.json()); | ||
|
|
||
| if (json.success) { | ||
| return { | ||
| lobbyCode: "0XXXX" // Placeholder | ||
| }; | ||
| } | ||
|
|
||
| return { lobbyCode: "" }; |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The useJoinLobbyMutation hook is never used in the UI. The component uses handleJoinLobby which directly navigates without calling the join API. Either remove this unused hook or integrate it into the handleJoinLobby function to actually call the join endpoint before navigation.
| } | |
| export const useJoinLobbyMutation = () => { | |
| return useMutation({ | |
| mutationFn: joinLobby, | |
| }); | |
| } | |
| async function joinLobby() { | |
| const { url, method, res } = ApiURL.create("/api/duel/lobby/join", { | |
| method: "POST", | |
| }); | |
| const response = await fetch(url, { | |
| method | |
| }); | |
| const json = res(await response.json()); | |
| if (json.success) { | |
| return { | |
| lobbyCode: "0XXXX" // Placeholder | |
| }; | |
| } | |
| return { lobbyCode: "" }; |
| const handleJoinLobby = () => { | ||
| if (joinCode.trim().length >= 4) { | ||
| navigate(`/lobby/${joinCode.trim().toUpperCase()}`); | ||
| } | ||
| }; |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The handleJoinLobby function only validates that the join code has at least 4 characters, but it doesn't actually call the useJoinLobbyMutation to verify the lobby code with the backend before navigating. This means users can navigate to invalid lobby codes. The function should call the join mutation and only navigate on successful validation, similar to how mutate is used for creating a lobby on lines 84-90.
d0862d7 to
d8bf77b
Compare
|
d8bf77b to
768421d
Compare
|
14d30ed to
3d23ff7
Compare
3d23ff7 to
24fef29
Compare
|
Notion Ticket (use public link, not private)
https://codebloom.notion.site/1717c85563aa81e1b23cf2cbea8c7b7c?v=2427c85563aa8034b397000cca570fe1&p=2b67c85563aa80a683d5d4f53e3f337c&pm=s
Description of changes
Screenshots
Dev
Staging