fix: upgrade Next.js and Node LTS container

This commit is contained in:
joshuafhiggins committed 2026-10-22 17:10:36 -04:00 Verified
1 parent f9607b4628
commit 7a2fc4d406
10 files changed
+516 -414

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
.git
.next
node_modules
npm-debug.log*
tsconfig.tsbuildinfo
-6
View File
@@ -1,6 +0,0 @@
{
"extends": [
"next/core-web-vitals",
"next/typescript"
]
}
+3 -3
View File
@@ -3,9 +3,9 @@
# ============================================
# IMPORTANT: Node.js Version Maintenance
# This Dockerfile uses Node.js 24.13.0-slim, which was the latest LTS version at the time of writing.
# This Dockerfile uses Node.js 24.22.1-slim, which was the latest LTS version at the time of writing.
# To ensure security and compatibility, regularly update the NODE_VERSION ARG to the latest LTS version.
ARG NODE_VERSION=24.13.0-slim
ARG NODE_VERSION=24.22.1-slim
FROM node:${NODE_VERSION} AS dependencies
@@ -13,7 +13,7 @@ FROM node:${NODE_VERSION} AS dependencies
WORKDIR /app
# Copy package-related files first to leverage Docker's caching mechanism
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* pnpm-workspace.yaml* .npmrc* ./
# Install project dependencies with frozen lockfile for reproducible builds
RUN --mount=type=cache,target=/root/.npm \
+15 -24
View File
@@ -49,6 +49,20 @@ export default function PlayPage() {
const myColorRef = useRef<1 | 2 | null>(null);
const isMyTurnRef = useRef(false);
// Restore match state before showing Ready Up after a mid-match reconnect.
if (role === "player" && status === "connected" && gamePhase === "idle") {
setGamePhase(isInMatch ? "playing" : "connected");
if (isInMatch) {
setMyColor(shouldGoFirst ? 1 : 2);
setIsMyTurn(shouldGoFirst);
}
}
useEffect(() => {
myColorRef.current = myColor;
isMyTurnRef.current = isMyTurn;
}, [myColor, isMyTurn]);
const resetGame = useCallback(() => {
setBoard(createEmptyBoard());
setLastMove(null);
@@ -86,30 +100,7 @@ export default function PlayPage() {
router.replace("/spectate");
return;
}
if (status === "connected" && gamePhase === "idle") {
// Mid-match reconnect can remount with phase idle while still in a match; avoid
// the pre-queue "connected" / Ready Up state until we know we are not in-game.
setGamePhase(isInMatch ? "playing" : "connected");
if (isInMatch) {
const color: 1 | 2 = shouldGoFirst ? 1 : 2;
setMyColor(color);
myColorRef.current = color;
setIsMyTurn(shouldGoFirst);
isMyTurnRef.current = shouldGoFirst;
}
}
}, [
role,
status,
router,
gamePhase,
isInMatch,
shouldRedirectToConnect,
clearRedirectFlag,
]);
}, [role, status, router, shouldRedirectToConnect, clearRedirectFlag]);
useEffect(() => {
const unsubscribe = subscribe((msg: ParsedMessage) => {
+18 -16
View File
@@ -30,12 +30,27 @@ export default function Board({
column: number;
row: number;
} | null>(null);
const isFirstRender = useRef(true);
const animationTimeoutRef = useRef<number | null>(null);
const chipDropSoundsRef = useRef<HTMLAudioElement[]>([]);
const canInteract = !disabled && !!onColumnClick;
const lastMoveColumn = lastMove?.column ?? null;
const lastMoveRow = lastMove?.row ?? null;
const [previousMove, setPreviousMove] = useState({
column: lastMoveColumn,
row: lastMoveRow,
});
if (
previousMove.column !== lastMoveColumn ||
previousMove.row !== lastMoveRow
) {
setPreviousMove({ column: lastMoveColumn, row: lastMoveRow });
setAnimatingMove(
lastMoveColumn === null || lastMoveRow === null
? null
: { column: lastMoveColumn, row: lastMoveRow },
);
}
useEffect(() => {
chipDropSoundsRef.current = CHIP_DROP_SOUND_PATHS.map((path) => {
@@ -54,26 +69,13 @@ export default function Board({
}, []);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}
if (animationTimeoutRef.current !== null) {
window.clearTimeout(animationTimeoutRef.current);
}
if (lastMoveColumn === null || lastMoveRow === null) {
setAnimatingMove(null);
return;
}
if (animatingMove === null) return;
const prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)",
).matches;
const dropDurationMs = prefersReducedMotion ? 0 : CHIP_DROP_ANIMATION_MS;
setAnimatingMove({ column: lastMoveColumn, row: lastMoveRow });
animationTimeoutRef.current = window.setTimeout(() => {
const sounds = chipDropSoundsRef.current;
const sound = sounds[Math.floor(Math.random() * sounds.length)];
@@ -94,7 +96,7 @@ export default function Board({
window.clearTimeout(animationTimeoutRef.current);
}
};
}, [lastMoveColumn, lastMoveRow]);
}, [animatingMove]);
return (
<div className="flex flex-col items-center gap-3">
+5 -4
View File
@@ -1,7 +1,7 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { SubmitEvent, useEffect, useState } from "react";
import { SubmitEvent, useState } from "react";
import { Settings, X } from "lucide-react";
import { useRouter } from "next/navigation";
import { usePathname } from "next/navigation";
@@ -28,11 +28,12 @@ export default function Nav() {
const disableRoleSwitch =
status === "connecting" || status === "reconnecting";
useEffect(() => {
if (isConnectionPage || (status === "disconnected" && shouldRedirectToConnect)) {
if (
showSettingsModal &&
(isConnectionPage || (status === "disconnected" && shouldRedirectToConnect))
) {
setShowSettingsModal(false);
}
}, [isConnectionPage, status, shouldRedirectToConnect]);
const handleBecomePlayer = (event: SubmitEvent<HTMLFormElement>) => {
event.preventDefault();
+9
View File
@@ -0,0 +1,9 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
export default defineConfig([
...nextVitals,
...nextTs,
globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts"]),
]);
+2 -1
View File
@@ -1,6 +1,7 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
import "./.next/types/routes.d.ts";
import "./.next/types/root-params.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
+456 -357
View File
File diff suppressed because it is too large. Load diff
+3 -3
View File
@@ -6,13 +6,13 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
"dependencies": {
"lucide-react": "^1.8.0",
"next": "16.2.4",
"next": "16.4.2",
"react": "19.2.5",
"react-confetti": "^6.4.0",
"react-dom": "19.2.5"
@@ -23,7 +23,7 @@
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"eslint": "^9",
"eslint-config-next": "16.2.4",
"eslint-config-next": "16.4.2",
"postcss": "^8",
"prettier": "^3.8.1",
"tailwindcss": "^4",