From e2dab7dd4e2bbe33235133504767d747c3d8ffdc Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sun, 26 Apr 2026 20:01:43 +0700 Subject: [PATCH] refactor: purge remaining TypeScript residue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the JS+JSDoc conversion, some TS-flavored bits lingered. Removed: - // @ts-check directives (TS-specific pragma) - JSDoc annotations referencing TS-defined types: import('next').NextConfig, React.MutableRefObject, React.Dispatch, React.SetStateAction - jsconfig.json (TS-server-flavored config; only kept it for the @/* alias) @/* imports replaced with relative paths so jsconfig is no longer needed. Remaining JSDoc is plain @param / @returns — vanilla JS, no TS dependency. Build, lint, dev profiles unchanged. --- app/layout.jsx | 2 -- app/master/page.jsx | 23 +++++-------------- app/page.jsx | 2 +- components/player-board.jsx | 46 +++++++++++++------------------------ docs/code-standards.md | 4 ++-- jsconfig.json | 20 ---------------- lib/game-logic.js | 14 +++++------ next.config.mjs | 3 --- 8 files changed, 31 insertions(+), 83 deletions(-) delete mode 100644 jsconfig.json diff --git a/app/layout.jsx b/app/layout.jsx index d2e98a1..d2f30ce 100644 --- a/app/layout.jsx +++ b/app/layout.jsx @@ -6,13 +6,11 @@ const geistSans = Geist({ subsets: ["latin"], }); -/** @type {import('next').Metadata} */ export const metadata = { title: "Lô tô", description: "Bàn số của trò chơi Lô tô", }; -/** @param {{ children: React.ReactNode }} props */ export default function RootLayout({ children }) { return ( diff --git a/app/master/page.jsx b/app/master/page.jsx index 58ff768..8aad9a8 100644 --- a/app/master/page.jsx +++ b/app/master/page.jsx @@ -2,25 +2,19 @@ import Link from "next/link"; import { useCallback, useEffect, useState } from "react"; -import PlayerBoard from "@/components/player-board"; +import PlayerBoard from "../../components/player-board"; const STORAGE_KEY = "loto_master"; /** - * @typedef {Object} MasterState - * @property {number[]} called numbers drawn so far, in order - * @property {number[]} remaining numbers left to draw, pre-shuffled - */ - -/** + * Master draw state shape: + * { called: number[], remaining: number[] } + * * Build the 9x10 board: columns 0-8 map to number ranges 1-9, 10-19, ..., 80-90. - * @returns {number[][]} */ function buildBoard() { - /** @type {number[][]} */ const board = []; for (let row = 0; row < 10; row++) { - /** @type {number[]} */ const cells = []; for (let col = 0; col < 9; col++) { const num = col === 0 ? row + 1 : col * 10 + row; @@ -40,7 +34,6 @@ function buildBoard() { return board; } -/** @returns {MasterState} */ function createFreshState() { const all = Array.from({ length: 90 }, (_, i) => i + 1); // Shuffle @@ -51,12 +44,10 @@ function createFreshState() { return { called: [], remaining: all }; } -/** @param {MasterState} state */ function saveState(state) { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } -/** @returns {MasterState | null} */ function loadState() { const data = localStorage.getItem(STORAGE_KEY); if (!data) return null; @@ -71,10 +62,8 @@ const BOARD = Object.freeze(buildBoard().map((row) => Object.freeze(row))); const BOARD_FLAT = Object.freeze(BOARD.flatMap((r) => r)); export default function MasterPage() { - /** @type {[MasterState | null, (s: MasterState | null) => void]} */ - const [state, setState] = useState(/** @type {MasterState | null} */ (null)); - /** @type {[number | null, (n: number | null) => void]} */ - const [lastCalled, setLastCalled] = useState(/** @type {number | null} */ (null)); + const [state, setState] = useState(null); + const [lastCalled, setLastCalled] = useState(null); useEffect(() => { const saved = loadState(); diff --git a/app/page.jsx b/app/page.jsx index 1ecff61..97fb0ac 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { useState } from "react"; -import PlayerBoard from "@/components/player-board"; +import PlayerBoard from "../components/player-board"; export default function Home() { const [showInstructions, setShowInstructions] = useState(false); diff --git a/components/player-board.jsx b/components/player-board.jsx index 46babb9..a3d5320 100644 --- a/components/player-board.jsx +++ b/components/player-board.jsx @@ -9,29 +9,23 @@ import { loadGrid, saveCrossedState, saveGrid, -} from "@/lib/game-logic"; +} from "../lib/game-logic"; /** - * @typedef {Object} PlayerBoardProps - * @property {string} [storagePrefix] localStorage key prefix; allows multiple - * independent boards (e.g. user vs master) + * Reusable player card component. + * + * @param {object} props + * @param {string} [props.storagePrefix] localStorage key prefix; allows + * multiple independent boards (e.g. user vs master). */ - -/** @param {PlayerBoardProps} props */ export default function PlayerBoard({ storagePrefix = "loto" } = {}) { - /** @type {[number[][] | null, (g: number[][] | null) => void]} */ - const [grid, setGrid] = useState(/** @type {number[][] | null} */ (null)); - /** @type {[boolean[][], React.Dispatch>]} */ - const [crossed, setCrossed] = useState(/** @type {boolean[][]} */ ([])); + const [grid, setGrid] = useState(null); + const [crossed, setCrossed] = useState([]); const [showCongrats, setShowCongrats] = useState(false); const [congratsRow, setCongratsRow] = useState(-1); - /** @type {[string | null, (s: string | null) => void]} */ - const [toast, setToast] = useState(/** @type {string | null} */ (null)); - /** @type {React.MutableRefObject | null>} */ + const [toast, setToast] = useState(null); const toastTimer = useRef(null); - /** @type {React.MutableRefObject>} */ const celebratedRows = useRef(new Set()); - /** @type {React.MutableRefObject>} */ const notifiedWaitingRows = useRef(new Set()); const dismissToast = useCallback(() => { @@ -43,7 +37,6 @@ export default function PlayerBoard({ storagePrefix = "loto" } = {}) { }, []); const showToast = useCallback( - /** @param {string} msg */ (msg) => { dismissToast(); setToast(msg); @@ -124,20 +117,13 @@ export default function PlayerBoard({ storagePrefix = "loto" } = {}) { dismissToast(); }, [grid, dismissToast, storagePrefix]); - const handleCellClick = useCallback( - /** - * @param {number} row - * @param {number} col - */ - (row, col) => { - setCrossed((prev) => { - const next = prev.map((r) => [...r]); - next[row][col] = !next[row][col]; - return next; - }); - }, - [] - ); + const handleCellClick = useCallback((row, col) => { + setCrossed((prev) => { + const next = prev.map((r) => [...r]); + next[row][col] = !next[row][col]; + return next; + }); + }, []); return ( <> diff --git a/docs/code-standards.md b/docs/code-standards.md index fb0db5f..43e919b 100644 --- a/docs/code-standards.md +++ b/docs/code-standards.md @@ -125,8 +125,8 @@ function randomANumberInRow(weights) { ```js import { useCallback, useState } from "react"; import Link from "next/link"; -import PlayerBoard from "@/components/player-board"; -import { generateGrid } from "@/lib/game-logic"; +import PlayerBoard from "../components/player-board"; +import { generateGrid } from "../lib/game-logic"; ``` ## Testing (Not Currently Implemented) diff --git a/jsconfig.json b/jsconfig.json deleted file mode 100644 index cce012d..0000000 --- a/jsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2017", - "module": "esnext", - "moduleResolution": "bundler", - "checkJs": true, - "allowJs": true, - "jsx": "react-jsx", - "lib": ["dom", "dom.iterable", "esnext"], - "esModuleInterop": true, - "resolveJsonModule": true, - "isolatedModules": true, - "skipLibCheck": true, - "strict": false, - "noEmit": true, - "paths": { "@/*": ["./*"] } - }, - "include": ["**/*.js", "**/*.jsx", "**/*.mjs"], - "exclude": ["node_modules", ".next", "out"] -} diff --git a/lib/game-logic.js b/lib/game-logic.js index 48a6a6d..6baeb94 100644 --- a/lib/game-logic.js +++ b/lib/game-logic.js @@ -1,5 +1,3 @@ -// @ts-check - /** * Lô tô card generation, persistence, and row-state helpers. * @module lib/game-logic @@ -101,22 +99,22 @@ export function generateGrid() { } /** - * @template T + * Parse JSON and validate its shape with a runtime guard. Returns null on + * either parse failure or validation failure. * @param {string | null} raw - * @param {(v: unknown) => boolean} validate runtime guard; cast result to T on success - * @returns {T | null} + * @param {(v: any) => boolean} validate */ function safeParse(raw, validate) { if (!raw) return null; try { const parsed = JSON.parse(raw); - return validate(parsed) ? /** @type {T} */ (parsed) : null; + return validate(parsed) ? parsed : null; } catch { return null; } } -/** @param {unknown} v @returns {boolean} */ +/** @param {any} v */ function isNumberMatrix(v) { return ( Array.isArray(v) && @@ -130,7 +128,7 @@ function isNumberMatrix(v) { ); } -/** @param {unknown} v @returns {boolean} */ +/** @param {any} v */ function isBoolMatrix(v) { return ( Array.isArray(v) && diff --git a/next.config.mjs b/next.config.mjs index 29d953d..1ae7b0b 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,5 +1,3 @@ -// @ts-check - const isProd = process.env.NODE_ENV === "production"; const isCodeserver = process.env.NEXT_DEV_PROFILE === "codeserver"; @@ -24,7 +22,6 @@ const cs = isCodeserver ? codeserverConfig() : null; const basePath = process.env.NEXT_BASE_PATH ?? cs?.basePath ?? (isProd ? "/loto" : ""); -/** @type {import('next').NextConfig} */ const nextConfig = { output: "export", basePath,