refactor: purge remaining TypeScript residue

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.
This commit is contained in:
2026-04-26 20:01:43 +07:00
parent 308a999a76
commit e2dab7dd4e
8 changed files with 31 additions and 83 deletions
-2
View File
@@ -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 (
<html lang="vi" className={`${geistSans.variable} h-full antialiased`}>
+6 -17
View File
@@ -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();
+1 -1
View File
@@ -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);
+16 -30
View File
@@ -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<React.SetStateAction<boolean[][]>>]} */
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<ReturnType<typeof setTimeout> | null>} */
const [toast, setToast] = useState(null);
const toastTimer = useRef(null);
/** @type {React.MutableRefObject<Set<number>>} */
const celebratedRows = useRef(new Set());
/** @type {React.MutableRefObject<Set<number>>} */
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 (
<>
+2 -2
View File
@@ -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)
-20
View File
@@ -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"]
}
+6 -8
View File
@@ -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) &&
-3
View File
@@ -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,