fix(chess): migrate ChessBoard to react-chessboard v5 API
Some checks failed
Deploy to Production / test (push) Failing after 33s

react-chessboard v5 moved all props into an `options` object and
renamed several callbacks/style props. The v4-style props were silently
ignored, causing pieces to snap back, no legal-move highlights, and no
WS events on drop. Also adds a custom promotion dialog since v5 removed
the built-in one.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
syntaxbullet
2026-04-02 19:26:24 +02:00
parent abca1922f2
commit ebac1ad6cc

View File

@@ -325,7 +325,8 @@ export function ChessBoard({ state, myPlayerId, isSpectator, onAction, players }
onAction({ type: "move", from, to, ...(promotion ? { promotion } : {}) });
}
function onDrop(sourceSquare: string, targetSquare: string): boolean {
function onDrop({ sourceSquare, targetSquare }: { piece: any; sourceSquare: string; targetSquare: string | null }): boolean {
if (!targetSquare) return false;
if (isSpectator || !isMyTurn || isGameOver) return false;
const testGame = new Chess(localFenRef.current);
const piece = testGame.get(sourceSquare as any);
@@ -347,15 +348,13 @@ export function ChessBoard({ state, myPlayerId, isSpectator, onAction, players }
function handlePromotion(piece: string) {
if (promotionFrom && promotionTo) {
const promotionPiece = piece[1]?.toLowerCase() ?? "q";
dispatchMove(promotionFrom, promotionTo, promotionPiece);
dispatchMove(promotionFrom, promotionTo, piece);
}
setPromotionFrom(null);
setPromotionTo(null);
return true;
}
function onSquareClick(square: string) {
function onSquareClick({ square }: { piece: any; square: string }) {
if (isSpectator || isGameOver || !isMyTurn) return;
if (promotionFrom !== null) return;
const testGame = new Chess(localFenRef.current);
@@ -397,9 +396,9 @@ export function ChessBoard({ state, myPlayerId, isSpectator, onAction, players }
if (isBothSides ? color === turn : color === myColor) setSelectedSquare(square);
}
function isDraggablePiece({ piece }: { piece: string }): boolean {
function canDragPiece({ piece }: { isSparePiece: boolean; piece: { pieceType: string }; square: string | null }): boolean {
if (isSpectator || !isMyTurn || isGameOver) return false;
const pieceColor = piece[0] === "w" ? "white" : "black";
const pieceColor = piece.pieceType === piece.pieceType.toUpperCase() ? "white" : "black";
return isBothSides ? pieceColor === turn : pieceColor === myColor;
}
@@ -472,26 +471,41 @@ export function ChessBoard({ state, myPlayerId, isSpectator, onAction, players }
/>
{/* Board wrapper */}
<div ref={containerRef} className="relative" style={{ lineHeight: 0 }}>
<div ref={containerRef} className="relative" style={{ lineHeight: 0, width: boardWidth, maxWidth: "100%" }}>
<Chessboard
position={localFen}
onPieceDrop={onDrop}
onPromotionPieceSelect={handlePromotion}
onSquareClick={onSquareClick}
boardOrientation={boardOrientation}
isDraggablePiece={isDraggablePiece}
boardWidth={boardWidth}
showPromotionDialog={promotionFrom !== null}
promotionToSquare={promotionTo as any}
animationDuration={150}
customSquareStyles={customSquareStyles}
customDarkSquareStyle={{ backgroundColor: DARK_SQUARE }}
customLightSquareStyle={{ backgroundColor: LIGHT_SQUARE }}
customBoardStyle={{ borderRadius: "0" }}
customDropSquareStyle={{ boxShadow: "inset 0 0 1px 6px rgba(20,85,30,0.7)" }}
customPremoveDarkSquareStyle={{ backgroundColor: "#a04a4a" }}
customPremoveLightSquareStyle={{ backgroundColor: "#d08080" }}
options={{
position: localFen,
onPieceDrop: onDrop,
onSquareClick: onSquareClick,
boardOrientation: boardOrientation,
canDragPiece: canDragPiece,
animationDurationInMs: 150,
squareStyles: customSquareStyles,
darkSquareStyle: { backgroundColor: DARK_SQUARE },
lightSquareStyle: { backgroundColor: LIGHT_SQUARE },
boardStyle: { borderRadius: "0" },
dropSquareStyle: { boxShadow: "inset 0 0 1px 6px rgba(20,85,30,0.7)" },
}}
/>
{promotionFrom !== null && (
<div
className="absolute inset-0 flex items-center justify-center z-20"
style={{ backgroundColor: "rgba(20, 18, 15, 0.7)" }}
>
<div className="flex gap-1 p-2 rounded-lg" style={{ backgroundColor: "#1a1917", border: "1px solid #3a3733" }}>
{["q", "r", "b", "n"].map((p) => (
<button
key={p}
onClick={() => handlePromotion(p)}
className="w-12 h-12 flex items-center justify-center text-2xl rounded hover:bg-white/10 transition-colors"
style={{ color: "#f0d9b5" }}
>
{{ q: turn === "white" ? "♕" : "♛", r: turn === "white" ? "♖" : "♜", b: turn === "white" ? "♗" : "♝", n: turn === "white" ? "♘" : "♞" }[p]}
</button>
))}
</div>
</div>
)}
{isGameOver && (
<GameOverOverlay
status={chess.status}