From 884f8db0e2cfcc46e13a39e498cd4245f9d6abba Mon Sep 17 00:00:00 2001 From: nakomochi Date: Tue, 9 Dec 2025 17:38:22 +0900 Subject: [PATCH 1/4] fix form action --- .../frontend/app/routes/magic-square/home.tsx | 417 +++++++++-------- .../app/routes/magic-square/room.$roomId.tsx | 20 +- .../app/routes/memory-optimization/home.tsx | 429 ++++++++++-------- .../memory-optimization/room.$roomId.tsx | 20 +- 4 files changed, 511 insertions(+), 375 deletions(-) diff --git a/apps/frontend/app/routes/magic-square/home.tsx b/apps/frontend/app/routes/magic-square/home.tsx index d00c3b5..747ca9c 100644 --- a/apps/frontend/app/routes/magic-square/home.tsx +++ b/apps/frontend/app/routes/magic-square/home.tsx @@ -1,6 +1,14 @@ import type { User } from "@apps/backend"; import { useEffect, useState } from "react"; -import { useLoaderData, useNavigate } from "react-router"; +import { + type ClientActionFunctionArgs, + Form, + redirect, + useActionData, + useLoaderData, + useNavigation, + useSubmit, +} from "react-router"; import { client } from "~/lib/client"; import { IS_DEV } from "~/lib/env"; @@ -11,229 +19,264 @@ type Room = { }; export async function clientLoader() { + let user: (User & { createdAt: Date | null }) | null = null; + let rooms: Room[] = []; + try { const res = await client.users.me.$get({}); - - if (!res.ok) throw new Error("Failed to fetch user.", { cause: res }); - const user = await res.json(); - const createdAt = user.createdAt ? new Date(user.createdAt) : null; - - return { ...user, createdAt }; + if (res.ok) { + const data = await res.json(); + user = { + ...data, + createdAt: data.createdAt ? new Date(data.createdAt) : null, + }; + } else { + const newUserName = `player-${Math.floor(Math.random() * 100000)}`; + const createRes = await client.users.create.$post({ + json: { name: newUserName }, + }); + const data = await createRes.json(); + user = { + ...data, + createdAt: data.createdAt ? new Date(data.createdAt) : null, + }; + } } catch (e) { - console.error(e); + console.error("User fetch/create failed", e); return null; } + + if (IS_DEV) { + try { + const res = await client.rooms.$get(); + if (res.ok) { + rooms = await res.json(); + } + } catch (e) { + console.error("Rooms fetch failed", e); + } + } + + return { user, rooms }; } -export default function Lobby() { - const me = useLoaderData(); - const [user, setUser] = useState(me ?? null); - const [rooms, setRooms] = useState([]); - const [userName, setUserName] = useState(user?.name ?? ""); - const [newUserName, setNewUserName] = useState(""); - const [newRoomName, setNewRoomName] = useState(""); - const [joinRoomSecret, setJoinRoomSecret] = useState(""); - const [joinError, setJoinError] = useState(null); - const navigate = useNavigate(); - const [step, setStep] = useState(0); - const [isEditingName, setIsEditingName] = useState(false); +export async function clientAction({ request }: ClientActionFunctionArgs) { + const formData = await request.formData(); + const intent = formData.get("intent"); - const instructions = [ - "盤上に数字を置いていき、自分のミッションを誰よりも早く達成することを狙うゲームです。", - "自分の番になったら、手札から数字を選び、次に「+」(加算)、「-」(減算)のいずれかを選びます。(パスも可)", - "盤上のマス目を選択すると、選んだカードの数字が加算/減算され、ターンが終了します。", - "制限時間を過ぎると強制的にパスになるので注意!", - ]; + try { + switch (intent) { + case "create-room": { + const nameInput = formData.get("roomName") as string; + const roomName = + nameInput || `room-${Math.floor(Math.random() * 100000)}`; - useEffect(() => { - if (!IS_DEV) return; - const fetchRooms = async () => { - const res = await client.rooms.$get(); - if (res.ok) { - const data = await res.json(); - setRooms(data); + const res = await client.rooms.create.$post({ + json: { name: roomName, gameTitle: "magic-square" }, + }); + + if (!res.ok) return { error: "Failed to create room" }; + const newRoom = await res.json(); + return redirect(`/magic-square/room/${newRoom.id}`); } - }; - fetchRooms(); - }, []); - useEffect(() => { - if (user) return; - const newUserName = `player-${Math.floor(Math.random() * 100000)}`; - const handleCreateUser = async () => { - const res = await client.users.create.$post({ - json: { name: newUserName }, - }); - const data = await res.json(); - const createdAt = data.createdAt ? new Date(data.createdAt) : null; - setUser({ ...data, createdAt }); - setUserName(data.name); - }; - handleCreateUser(); - }, [user]); - - const handleCreateRoom = async () => { - const roomName = - newRoomName || `room-${Math.floor(Math.random() * 100000)}`; - const res = await client.rooms.create.$post({ - json: { name: roomName, gameTitle: "magic-square" }, - }); - if (res.ok) { - const newRoom = await res.json(); - navigate(`/magic-square/room/${newRoom.id}`); - } - }; + case "join-room-secret": { + const secret = formData.get("secret") as string; + if (!secret) return { error: "Secret is required" }; - const handleChangeName = async () => { - if (!newUserName) return; - setUserName(newUserName); - try { - const res = await client.users.me.$patch({ - json: { newName: newUserName }, - }); - if (res.ok) { + const res = await client.rooms.join.$post({ + json: { secret }, + }); + + if (!res.ok) return { error: "Failed to join room (Invalid secret?)" }; const data = await res.json(); - const createdAt = data.createdAt ? new Date(data.createdAt) : null; - setUser({ ...data, createdAt }); + return redirect(`/magic-square/room/${data.id}`); } - } catch (e) { - console.error(e); - } - }; - const handleJoinRoom = async (roomId: string) => { - try { - const res = await client.rooms[":roomId"].join.$post({ - param: { roomId }, - }); - if (res.ok) { - navigate(`/magic-square/room/${roomId}`); + case "dev-join-room-id": { + const roomId = formData.get("roomId") as string; + const res = await client.rooms[":roomId"].join.$post({ + param: { roomId }, + }); + if (res.ok) return redirect(`/magic-square/room/${roomId}`); + return { error: "Failed to join room" }; } - } catch (e) { - console.error(e); - } - }; - const handleJoinWithSecret = async () => { - if (!joinRoomSecret) return; - setJoinError(null); - try { - const res = await client.rooms.join.$post({ - json: { secret: joinRoomSecret }, - }); - if (res.ok) { - const data = await res.json(); - navigate(`/magic-square/room/${data.id}`); - } else { - setJoinError("Failed to join room"); + case "change-name": { + const newName = formData.get("newName") as string; + if (!newName) return { error: "Name is required" }; + + const res = await client.users.me.$patch({ + json: { newName }, + }); + if (!res.ok) return { error: "Failed to update name" }; + + return { success: true }; } - } catch (e) { - console.error(e); + + default: + return { error: "Unknown action" }; } + } catch (e) { + console.error(e); + return { error: "Unexpected error occurred" }; + } +} + +export default function Lobby() { + const loaderData = useLoaderData(); + const actionData = useActionData(); + const submit = useSubmit(); + const navigation = useNavigation(); + + const [step, setStep] = useState(0); + const [isEditingName, setIsEditingName] = useState(false); + const [pendingName, setPendingName] = useState(null); + + useEffect(() => { + const userName = loaderData?.user?.name; + if (userName && pendingName && userName === pendingName) { + setPendingName(null); + } + }, [loaderData?.user, pendingName]); + + if (!loaderData) return
Failed to load user data.
; + const { user, rooms } = loaderData; + + const handleNameChange = (formData: FormData) => { + const newName = formData.get("newName") as string; + setPendingName(newName); + submit(formData, { method: "post" }); + setIsEditingName(false); }; - if (!user) return null; + const displayName = pendingName ?? user.name; + + const isCreatingRoom = + (navigation.state === "submitting" || navigation.state === "loading") && + navigation.formData?.get("intent") === "create-room"; + const isJoiningRoom = + (navigation.state === "submitting" || navigation.state === "loading") && + navigation.formData?.get("intent") === "join-room-secret"; + const isJoiningRoomById = + (navigation.state === "submitting" || navigation.state === "loading") && + navigation.formData?.get("intent") === "dev-join-room-id"; + + const instructions = [ + "盤上に数字を置いていき、自分のミッションを誰よりも早く達成することを狙うゲームです。", + "自分の番になったら、手札から数字を選び、次に「+」(加算)、「-」(減算)のいずれかを選びます。(パスも可)", + "盤上のマス目を選択すると、選んだカードの数字が加算/減算され、ターンが終了します。", + "制限時間を過ぎると強制的にパスになるので注意!", + ]; return (

Lobby

- {isEditingName ? ( -
{ - e.preventDefault(); - handleChangeName(); - setIsEditingName(false); - }} - > - setNewUserName(e.target.value)} - required - /> - -
- ) : ( -
-

Welcome, {userName}!

- -
- )} + + + + + + ) : ( +
+

Welcome, {displayName}!

+ +
+ )} +

Create a Room

-
{ - e.preventDefault(); - handleCreateRoom(); - }} - > + + setNewRoomName(e.target.value)} />
-
-
+
+

Join a Room

-
{ - e.preventDefault(); - handleJoinWithSecret(); - }} - > - {joinError && ( + + + {actionData?.error && (
-
- {joinError} -
+ {actionData.error}
)} setJoinRoomSecret(e.target.value)} required />
-
-
+
+ +
@@ -292,27 +336,44 @@ export default function Lobby() {
+ {IS_DEV && (

Available Rooms (Debug)

- {rooms.map((room) => ( -
-
-

{room.name}

-

{room.users.length} players

-
- + {rooms.map((room) => { + const isJoiningThisRoom = + isJoiningRoomById && + navigation.formData?.get("roomId") === room.id; + + return ( +
+
+

{room.name}

+

{room.users.length} players

+
+ + + + + +
-
- ))} + ); + })}
)} diff --git a/apps/frontend/app/routes/magic-square/room.$roomId.tsx b/apps/frontend/app/routes/magic-square/room.$roomId.tsx index 33a4638..ccd0b04 100644 --- a/apps/frontend/app/routes/magic-square/room.$roomId.tsx +++ b/apps/frontend/app/routes/magic-square/room.$roomId.tsx @@ -257,6 +257,7 @@ export default function RoomPage() { const [spectatedPlayerId, setSpectatedPlayerId] = useState( null, ); + const [isLeavingRoom, setIsLeavingRoom] = useState(false); // WebSocket connection effect useEffect(() => { @@ -366,6 +367,7 @@ export default function RoomPage() { }; const handleLeaveRoom = async () => { + setIsLeavingRoom(true); sendWsMessage({ type: "removePlayer" }); if (roomId) { await client.rooms[":roomId"].leave.$post({ param: { roomId } }); @@ -659,9 +661,21 @@ export default function RoomPage() { > Spectator Mode
-
- Leave Room -
+
); } diff --git a/apps/frontend/app/routes/memory-optimization/home.tsx b/apps/frontend/app/routes/memory-optimization/home.tsx index 0a19a26..e5a5961 100644 --- a/apps/frontend/app/routes/memory-optimization/home.tsx +++ b/apps/frontend/app/routes/memory-optimization/home.tsx @@ -2,7 +2,15 @@ import type { User } from "@apps/backend"; import { useEffect, useState } from "react"; -import { useLoaderData, useNavigate } from "react-router"; +import { + type ClientActionFunctionArgs, + Form, + redirect, + useActionData, + useLoaderData, + useNavigation, + useSubmit, +} from "react-router"; import { client } from "~/lib/client"; import { IS_DEV } from "~/lib/env"; @@ -13,242 +21,264 @@ type Room = { }; export async function clientLoader() { + let user: (User & { createdAt: Date | null }) | null = null; + let rooms: Room[] = []; + try { const res = await client.users.me.$get({}); - - if (!res.ok) throw new Error("Failed to fetch user.", { cause: res }); - const user = await res.json(); - const createdAt = user.createdAt ? new Date(user.createdAt) : null; - - return { ...user, createdAt }; + if (res.ok) { + const data = await res.json(); + user = { + ...data, + createdAt: data.createdAt ? new Date(data.createdAt) : null, + }; + } else { + const newUserName = `player-${Math.floor(Math.random() * 100000)}`; + const createRes = await client.users.create.$post({ + json: { name: newUserName }, + }); + const data = await createRes.json(); + user = { + ...data, + createdAt: data.createdAt ? new Date(data.createdAt) : null, + }; + } } catch (e) { - console.error(e); + console.error("User fetch/create failed", e); return null; } + + if (IS_DEV) { + try { + const res = await client.rooms.$get(); + if (res.ok) { + rooms = await res.json(); + } + } catch (e) { + console.error("Rooms fetch failed", e); + } + } + + return { user, rooms }; } -export default function Lobby() { - const me = useLoaderData(); - const [user, setUser] = useState(me ?? null); - const [rooms, setRooms] = useState([]); - const [userName, setUserName] = useState(user?.name ?? ""); - const [newUserName, setNewUserName] = useState(""); - const [newRoomName, setNewRoomName] = useState(""); - const [joinRoomSecret, setJoinRoomSecret] = useState(""); - const [joinError, setJoinError] = useState(null); - const navigate = useNavigate(); - const [step, setStep] = useState(0); - const [isEditingName, setIsEditingName] = useState(false); +export async function clientAction({ request }: ClientActionFunctionArgs) { + const formData = await request.formData(); + const intent = formData.get("intent"); - const instructions = [ - "盤上に数字を置いていき、自分のミッションを誰よりも早く達成することを狙うゲームです。", - "自分の番になったら、手札から数字を選び、次に「+」(加算)、「-」(減算)のいずれかを選びます。(パスも可)", - "盤上のマス目を選択すると、選んだカードの数字が加算/減算され、ターンが終了します。", - "制限時間を過ぎると強制的にパスになるので注意!", - ]; + try { + switch (intent) { + case "create-room": { + const nameInput = formData.get("roomName") as string; + const roomName = + nameInput || `room-${Math.floor(Math.random() * 100000)}`; - useEffect(() => { - if (!IS_DEV) return; - const fetchRooms = async () => { - const res = await client.rooms.$get(); - if (res.ok) { - const data = await res.json(); - setRooms(data); + const res = await client.rooms.create.$post({ + json: { name: roomName, gameTitle: "memory-optimization" }, + }); + + if (!res.ok) return { error: "Failed to create room" }; + const newRoom = await res.json(); + return redirect(`/memory-optimization/room/${newRoom.id}`); } - }; - fetchRooms(); - }, []); - useEffect(() => { - if (user) return; - const newUserName = `player-${Math.floor(Math.random() * 100000)}`; - const handleCreateUser = async () => { - const res = await client.users.create.$post({ - json: { name: newUserName }, - }); - const data = await res.json(); - const createdAt = data.createdAt ? new Date(data.createdAt) : null; - setUser({ ...data, createdAt }); - setUserName(data.name); - }; - handleCreateUser(); - }, [user]); - - const handleCreateRoom = async () => { - const roomName = - newRoomName || `room-${Math.floor(Math.random() * 100000)}`; - const res = await client.rooms.create.$post({ - json: { name: roomName, gameTitle: "memory-optimization" }, - }); - if (res.ok) { - const newRoom = await res.json(); - navigate(`/memory-optimization/room/${newRoom.id}`); - } - }; + case "join-room-secret": { + const secret = formData.get("secret") as string; + if (!secret) return { error: "Secret is required" }; - const handleChangeName = async () => { - if (!newUserName) return; - setUserName(newUserName); - try { - const res = await client.users.me.$patch({ - json: { newName: newUserName }, - }); - if (res.ok) { + const res = await client.rooms.join.$post({ + json: { secret }, + }); + + if (!res.ok) return { error: "Failed to join room (Invalid secret?)" }; const data = await res.json(); - const createdAt = data.createdAt ? new Date(data.createdAt) : null; - setUser({ ...data, createdAt }); + return redirect(`/memory-optimization/room/${data.id}`); } - } catch (e) { - console.error(e); - } - }; - const handleJoinRoom = async (roomId: string) => { - setJoinError(null); - try { - const res = await client.rooms[":roomId"].join.$post({ - param: { roomId }, - }); - if (res.ok) { - navigate(`/memory-optimization/room/${roomId}`); - } else { - const errorData = - ((await res.json()) as unknown as { message: string }).message || - "Failed to join room"; - setJoinError(errorData); - alert(errorData); + case "dev-join-room-id": { + const roomId = formData.get("roomId") as string; + const res = await client.rooms[":roomId"].join.$post({ + param: { roomId }, + }); + if (res.ok) return redirect(`/memory-optimization/room/${roomId}`); + return { error: "Failed to join room" }; } - } catch (e) { - console.error(e); - alert("An unexpected error occurred."); - } - }; - const handleJoinWithSecret = async () => { - if (!joinRoomSecret) return; - setJoinError(null); - try { - const res = await client.rooms.join.$post({ - json: { secret: joinRoomSecret }, - }); - if (res.ok) { - const data = await res.json(); - navigate(`/memory-optimization/room/${data.id}`); - } else { - const errorData = - ((await res.json()) as unknown as { message: string }).message || - "Failed to join room"; - setJoinError(errorData); - alert(errorData); + case "change-name": { + const newName = formData.get("newName") as string; + if (!newName) return { error: "Name is required" }; + + const res = await client.users.me.$patch({ + json: { newName }, + }); + if (!res.ok) return { error: "Failed to update name" }; + + return { success: true }; } - } catch (e) { - console.error(e); - setJoinError("An unexpected error occurred."); + + default: + return { error: "Unknown action" }; } + } catch (e) { + console.error(e); + return { error: "Unexpected error occurred" }; + } +} + +export default function Lobby() { + const loaderData = useLoaderData(); + const actionData = useActionData(); + const submit = useSubmit(); + const navigation = useNavigation(); + + const [step, setStep] = useState(0); + const [isEditingName, setIsEditingName] = useState(false); + const [pendingName, setPendingName] = useState(null); + + useEffect(() => { + const userName = loaderData?.user?.name; + if (userName && pendingName && userName === pendingName) { + setPendingName(null); + } + }, [loaderData?.user, pendingName]); + + if (!loaderData) return
Failed to load user data.
; + const { user, rooms } = loaderData; + + const handleNameChange = (formData: FormData) => { + const newName = formData.get("newName") as string; + setPendingName(newName); + submit(formData, { method: "post" }); + setIsEditingName(false); }; - if (!user) return null; + const displayName = pendingName ?? user.name; + + const isCreatingRoom = + (navigation.state === "submitting" || navigation.state === "loading") && + navigation.formData?.get("intent") === "create-room"; + const isJoiningRoom = + (navigation.state === "submitting" || navigation.state === "loading") && + navigation.formData?.get("intent") === "join-room-secret"; + const isJoiningRoomById = + (navigation.state === "submitting" || navigation.state === "loading") && + navigation.formData?.get("intent") === "dev-join-room-id"; + + const instructions = [ + "盤上に数字を置いていき、自分のミッションを誰よりも早く達成することを狙うゲームです。", + "自分の番になったら、手札から数字を選び、次に「+」(加算)、「-」(減算)のいずれかを選びます。(パスも可)", + "盤上のマス目を選択すると、選んだカードの数字が加算/減算され、ターンが終了します。", + "制限時間を過ぎると強制的にパスになるので注意!", + ]; return (

Lobby

- {isEditingName ? ( -
{ - e.preventDefault(); - handleChangeName(); - setIsEditingName(false); - }} - > - setNewUserName(e.target.value)} - required - /> - -
- ) : ( -
-

Welcome, {userName}!

- -
- )} + + + + + + ) : ( +
+

Welcome, {displayName}!

+ +
+ )} +

Create a Room

-
{ - e.preventDefault(); - handleCreateRoom(); - }} - > + + setNewRoomName(e.target.value)} />
-
-
+
+

Join a Room

-
{ - e.preventDefault(); - handleJoinWithSecret(); - }} - > - {joinError && ( + + + {actionData?.error && (
-
- {joinError} -
+ {actionData.error}
)} setJoinRoomSecret(e.target.value)} required />
-
-
+
+ +
@@ -311,23 +342,39 @@ export default function Lobby() {

Available Rooms (Debug)

- {rooms.map((room) => ( -
-
-

{room.name}

-

{room.users.length} players

-
- + {rooms.map((room) => { + const isJoiningThisRoom = + isJoiningRoomById && + navigation.formData?.get("roomId") === room.id; + + return ( +
+
+

{room.name}

+

{room.users.length} players

+
+ + + + + +
-
- ))} + ); + })}
)} diff --git a/apps/frontend/app/routes/memory-optimization/room.$roomId.tsx b/apps/frontend/app/routes/memory-optimization/room.$roomId.tsx index fee13b0..ba0d92c 100644 --- a/apps/frontend/app/routes/memory-optimization/room.$roomId.tsx +++ b/apps/frontend/app/routes/memory-optimization/room.$roomId.tsx @@ -314,6 +314,7 @@ export default function RoomPage() { // const [winnerDisplay, setWinnerDisplay] = useState(0); const [remainingTime, setRemainingTime] = useState(0); + const [isLeavingRoom, setIsLeavingRoom] = useState(false); // const [spectatedPlayerId, setSpectatedPlayerId] = useState( // null // ); @@ -423,6 +424,7 @@ export default function RoomPage() { // }; const handleLeaveRoom = async () => { + setIsLeavingRoom(true); sendWsMessage({ type: "removePlayer" }); if (roomId) { await client.rooms[":roomId"].leave.$post({ param: { roomId } }); @@ -687,9 +689,21 @@ export default function RoomPage() { > {myStatus === "ready" ? "READY!!" : "ready?"}
-
- Leave Room -
+
); } From 60d6dcfda3b93250b87b673e14ecfd02aa25685f Mon Sep 17 00:00:00 2001 From: nakomochi Date: Tue, 9 Dec 2025 17:53:43 +0900 Subject: [PATCH 2/4] fix biome error --- .../app/routes/magic-square/room.$roomId.tsx | 99 ++++++++++--------- .../memory-optimization/room.$roomId.tsx | 84 ++++++++-------- 2 files changed, 98 insertions(+), 85 deletions(-) diff --git a/apps/frontend/app/routes/magic-square/room.$roomId.tsx b/apps/frontend/app/routes/magic-square/room.$roomId.tsx index ccd0b04..fc8119c 100644 --- a/apps/frontend/app/routes/magic-square/room.$roomId.tsx +++ b/apps/frontend/app/routes/magic-square/room.$roomId.tsx @@ -1,6 +1,3 @@ -/** biome-ignore-all lint/a11y/noStaticElementInteractions: TODO */ -/** biome-ignore-all lint/suspicious/noArrayIndexKey: TODO */ -/** biome-ignore-all lint/a11y/useKeyWithClickEvents: TODO */ import type { GameState, MessageType, @@ -60,22 +57,25 @@ function GameBoard({ board: (number | null)[][]; onCellClick: (x: number, y: number) => void; }) { + const cells = board.flatMap((row, y) => + row.map((cell, x) => ({ cell, x, y })), + ); + return (
- {board.map((row, y) => - row.map((cell, x) => ( -
onCellClick(x, y)} - > - {cell} -
- )), - )} + {cells.map(({ cell, x, y }) => ( + + ))}
); } @@ -87,28 +87,30 @@ function FinalGameBoard({ board: (number | null)[][]; winnerary: (true | false)[][]; }) { + const cells = board.flatMap((row, y) => + row.map((cell, x) => ({ cell, x, y })), + ); + return (
- {board.map((row, y) => - row.map((cell, x) => - winnerary[y][x] === true ? ( -
- {cell} -
- ) : ( -
- {cell} -
- ), + {cells.map(({ cell, x, y }) => + winnerary[y][x] === true ? ( +
+ {cell} +
+ ) : ( +
+ {cell} +
), )}
@@ -124,17 +126,20 @@ function Hand({ onCardClick: (i: number) => void; selectedNumIndex: number | null; }) { + const cardItems = cards.map((card, i) => ({ card, index: i })); + return (
- {cards.map((card, i) => ( -
onCardClick(i)} + {cardItems.map(({ card, index }) => ( +
+ ))}
@@ -151,18 +156,20 @@ function Operations({ return (
-
onOperationClick("add")} > + -
-
+
+
); @@ -649,18 +656,20 @@ export default function RoomPage() {
-
{myStatus === "ready" ? "READY!!" : "ready?"} -
-
+
+
); } @@ -129,6 +129,10 @@ function Shape({ card: MemoryCard | FunctionCard; cellSz: number; }) { + const shapeCells = card.shape.flatMap((row, y) => + row.map((cell, x) => ({ cell, x, y })), + ); + return (
- {card.shape.map((row, y) => - row.map((cell, x) => ( -
- {x === 0 && y === 0 ? ( -
- ) : null} -
- )), - )} + {shapeCells.map(({ cell, x, y }) => ( +
+ {x === 0 && y === 0 ? ( +
+ ) : null} +
+ ))}
); @@ -170,9 +172,10 @@ function Hand({ return (
- {Object.keys(cards).map((id, i) => ( -
( +
+ + ))}
@@ -683,12 +686,13 @@ export default function RoomPage() {
-
{myStatus === "ready" ? "READY!!" : "ready?"} -
+