refactor: convert from TypeScript to JavaScript with JSDoc

Author types in JSDoc comments. jsconfig.json keeps checkJs: true so
the editor's bundled TS server still validates them; CI can validate
with npx -p typescript tsc --noEmit on demand.

Renames (history preserved via git mv):
- next.config.ts        -> next.config.mjs
- app/layout.tsx        -> app/layout.jsx
- app/page.tsx          -> app/page.jsx
- app/master/page.tsx   -> app/master/page.jsx
- components/player-board.tsx -> components/player-board.jsx
- lib/game-logic.ts     -> lib/game-logic.js
- tsconfig.json         -> jsconfig.json (same @/* alias)

Drops typescript and @types/node, @types/react, @types/react-dom
from devDependencies. Removes vendored next-env.d.ts. eslint config
no longer pulls in eslint-config-next/typescript.

Behavior unchanged. Build, lint, and dev profiles verified.
This commit is contained in:
tiennm99 committed 2026-04-26 19:45:36 +07:00
1 parent 254a6dc39a
commit 95545c9308
19 files changed
+563 -208

No files matched your search

+29 -15
View File
@@ -2,12 +2,12 @@
## File Naming & Structure
- **Kebab-case** for all files: `player-board.tsx`, `game-logic.ts`.
- **Kebab-case** for all files: `player-board.jsx`, `game-logic.js`.
- **Descriptive names**: Long names are preferred for self-documentation. Avoid ambiguity.
- **Single responsibility**: Each file has one primary export (component or utilities).
- **Max 200 lines per file**: Split larger components into smaller focused ones.
## React & TypeScript Conventions
## React & JavaScript Conventions
### Hooks
- **useState**: For local UI state (grid, crossed, form inputs).
@@ -15,10 +15,12 @@
- **useCallback**: For event handlers to stabilize function identity across renders.
- **useRef**: For mutable values that don't trigger renders (timers, celebratedRows set).
### Typing
- Explicit `export interface Props { ... }` for component props.
- Use `Readonly<{ children: React.ReactNode }>` for layout children.
### Typing (JSDoc)
- Author types in JSDoc comments — `jsconfig.json` has `checkJs: true`, so `tsc --noEmit` validates them.
- Component props: `@typedef {Object} Props { ... }` followed by `@param {Props} props` on the function.
- Layout children: `@param {{ children: React.ReactNode }} props`.
- Avoid `any`; use precise types (e.g., `number[][]` for grid).
- Generics: `@template T` then reference `T` in `@param` / `@returns`.
### Client-Only Constraint
- All interactive pages must have `"use client"` at the top.
@@ -47,15 +49,23 @@
## localStorage Patterns
### Saving
```typescript
function saveGrid(grid: number[][], prefix = "loto"): void {
```js
/**
* @param {number[][]} grid
* @param {string} [prefix]
*/
function saveGrid(grid, prefix = "loto") {
localStorage.setItem(`${prefix}_grid`, JSON.stringify(grid));
}
```
### Loading
```typescript
function loadGrid(prefix = "loto"): number[][] | null {
```js
/**
* @param {string} [prefix]
* @returns {number[][] | null}
*/
function loadGrid(prefix = "loto") {
const data = localStorage.getItem(`${prefix}_grid`);
if (!data) return null;
try {
@@ -81,18 +91,22 @@ function loadGrid(prefix = "loto"): number[][] | null {
| camelCase | `handleCellClick`, `storagePrefix` | variables, functions, props |
| PascalCase | `PlayerBoard`, `MasterPage` | components, types |
| UPPER_SNAKE | `STORAGE_KEY`, `NUM_ROWS` | constants |
| kebab-case | `player-board.tsx` | file names |
| kebab-case | `player-board.jsx` | file names |
## Comment Style
- Document **why**, not **what**. The code shows what it does.
- Use `/** JSDoc */` for exported functions.
- Inline comments for complex logic (e.g., weighted random selection in `lib/game-logic.ts:19–28`).
- Inline comments for complex logic (e.g., weighted random selection in `lib/game-logic.js:19–28`).
### Example
```typescript
/** Weighted random selection of a column index */
function randomANumberInRow(weights: number[]): number {
```js
/**
* Weighted random selection of a column index.
* @param {number[]} weights
* @returns {number}
*/
function randomANumberInRow(weights) {
// Convert weights to cumulative distribution for O(n) lookup
const tempWeight = [...weights];
for (let i = 1; i < tempWeight.length; i++) {
@@ -108,7 +122,7 @@ function randomANumberInRow(weights: number[]): number {
2. Third-party imports
3. Local component/utility imports
```typescript
```js
import { useCallback, useState } from "react";
import Link from "next/link";
import PlayerBoard from "@/components/player-board";
+11 -11
View File
@@ -5,19 +5,19 @@
### Routing & Layout
| File | Purpose |
|------|---------|
| `app/layout.tsx` | Root HTML layout. Sets Vietnamese lang, imports Geist font, applies global flex layout. |
| `app/page.tsx` | Player page (`/`). Instructions toggle, PlayerBoard component, indigo gradient branding. |
| `app/master/page.tsx` | Host page (`/master`). Controls (new game, draw number), 9×10 master board, host's player card. |
| `app/layout.jsx` | Root HTML layout. Sets Vietnamese lang, imports Geist font, applies global flex layout. |
| `app/page.jsx` | Player page (`/`). Instructions toggle, PlayerBoard component, indigo gradient branding. |
| `app/master/page.jsx` | Host page (`/master`). Controls (new game, draw number), 9×10 master board, host's player card. |
### Shared Components
| File | Purpose |
|------|---------|
| `components/player-board.tsx` | Reusable player card (9×9 grid). Handles crossed state, bingo popup, "Chờ X" toast. Accepts `storagePrefix` prop for multi-card isolation. |
| `components/player-board.jsx` | Reusable player card (9×9 grid). Handles crossed state, bingo popup, "Chờ X" toast. Accepts `storagePrefix` prop for multi-card isolation. |
### Game Logic
| File | Purpose |
|------|---------|
| `lib/game-logic.ts` | Stateless utilities: generateGrid (weighted column selection), saveGrid, loadGrid, saveCrossedState, loadCrossedState, isRowComplete, getWaitingNumber. |
| `lib/game-logic.js` | Stateless utilities: generateGrid (weighted column selection), saveGrid, loadGrid, saveCrossedState, loadCrossedState, isRowComplete, getWaitingNumber. |
### Styling
| File | Purpose |
@@ -27,7 +27,7 @@
### Configuration
| File | Purpose |
|------|---------|
| `next.config.ts` | Dual basePath: prod `/loto`, codeserver `/absproxy/{PORT}`. Exports static HTML. HMR-aware. |
| `next.config.mjs` | Dual basePath: prod `/loto`, codeserver `/absproxy/{PORT}`. Exports static HTML. HMR-aware. |
| `package.json` | Next 16.2.2, React 19.2.4, Tailwind 4. Scripts: dev, dev:codeserver, build, start, lint. |
| `eslint.config.mjs` | ESLint 9 config (Next.js preset). |
| `.gitignore` | Excludes node_modules, .next, .env.local, etc. |
@@ -66,10 +66,10 @@ RootLayout
| Function | Location | Effect |
|----------|----------|--------|
| `generateGrid()` | game-logic.ts:52 | Creates 9×9 with weighted column selection (5 nums/row). |
| `isRowComplete()` | game-logic.ts:108 | Boolean: all non-zero cells in row crossed? |
| `getWaitingNumber()` | game-logic.ts:120 | Returns the single uncrossed number in row, or null. |
| `handleCellClick()` | player-board.tsx:112 | Toggle crossed[row][col]. |
| `handleDrawNext()` | master/page.tsx:88 | Pop first number from remaining, add to called. |
| `generateGrid()` | game-logic.js:52 | Creates 9×9 with weighted column selection (5 nums/row). |
| `isRowComplete()` | game-logic.js:108 | Boolean: all non-zero cells in row crossed? |
| `getWaitingNumber()` | game-logic.js:120 | Returns the single uncrossed number in row, or null. |
| `handleCellClick()` | player-board.jsx:112 | Toggle crossed[row][col]. |
| `handleDrawNext()` | master/page.jsx:88 | Pop first number from remaining, add to called. |
Last reviewed: 2026-04-26
+3 -3
View File
@@ -11,7 +11,7 @@ The app auto-deploys from the `master` branch via `.github/workflows/deploy.yml`
3. Static export written to `out/` directory
4. Pages deployed to `https://{user}.github.io/loto`
**Note**: basePath is set to `/loto` in production (`next.config.ts:23`).
**Note**: basePath is set to `/loto` in production (`next.config.mjs:23`).
### Manual Deploy (if needed)
```bash
@@ -55,7 +55,7 @@ Replace `your-machine.example.com` with your actual hostname/IP (must match the
npm run dev:codeserver
```
This sets `NEXT_DEV_PROFILE=codeserver`, triggering the code-server config path in `next.config.ts`.
This sets `NEXT_DEV_PROFILE=codeserver`, triggering the code-server config path in `next.config.mjs`.
**4. Access via browser**:
Navigate to:
@@ -88,7 +88,7 @@ Generates:
- `.next/` — Build cache (not needed for deployment)
### Export Settings
- `output: "export"` in `next.config.ts`
- `output: "export"` in `next.config.mjs`
- No server-side rendering
- All pages pre-rendered to HTML + JS bundles
+3 -3
View File
@@ -86,9 +86,9 @@ All pages use `"use client"` because:
- Hydration requires client-only state initialization
Files with `"use client"`:
- `app/page.tsx`
- `app/master/page.tsx`
- `components/player-board.tsx`
- `app/page.jsx`
- `app/master/page.jsx`
- `components/player-board.jsx`
## Data Flow: Mark a Cell