Add ESLint for TypeScript/React linting

- Install eslint, typescript-eslint, eslint-plugin-react-hooks
- Configure eslint.config.js with flat config format
- Add type: module to package.json
- Fix unused variable issues (prefix with underscore)
- Add Makefile targets: lint-frontend, fix-frontend
This commit is contained in:
counterweight 2025-12-21 21:58:41 +01:00
parent 30583805cd
commit 4b394b0698
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
12 changed files with 1467 additions and 16 deletions

1
.gitignore vendored
View file

@ -28,4 +28,5 @@ next_pr.md
# Notes
notes/
prompts/
.coverage

View file

@ -1,4 +1,4 @@
.PHONY: install-backend install-frontend install setup-hooks backend frontend db db-stop db-ready db-seed dev test test-backend test-frontend test-e2e typecheck generate-types generate-types-standalone check-types-fresh check-constants lint-backend format-backend fix-backend security-backend
.PHONY: install-backend install-frontend install setup-hooks backend frontend db db-stop db-ready db-seed dev test test-backend test-frontend test-e2e typecheck generate-types generate-types-standalone check-types-fresh check-constants lint-backend format-backend fix-backend security-backend lint-frontend fix-frontend
-include .env
export
@ -105,3 +105,9 @@ fix-backend:
security-backend:
cd backend && uv run bandit -r . -c pyproject.toml
lint-frontend:
cd frontend && npm run lint
fix-frontend:
cd frontend && npm run lint:fix

View file

@ -12,7 +12,7 @@ import { formatDate, formatDisplayDate, getDateRange, formatTimeString, isWeeken
const { slotDurationMinutes, maxAdvanceDays, minAdvanceDays } = constants.booking;
type AvailabilityDay = components["schemas"]["AvailabilityDay"];
type _AvailabilityDay = components["schemas"]["AvailabilityDay"];
type AvailabilityResponse = components["schemas"]["AvailabilityResponse"];
type TimeSlot = components["schemas"]["TimeSlot"];

View file

@ -12,7 +12,7 @@ import constants from "../../../../shared/constants.json";
const { READY, SPENT, REVOKED } = constants.inviteStatuses;
// Use generated types from OpenAPI schema
type InviteRecord = components["schemas"]["InviteResponse"];
type _InviteRecord = components["schemas"]["InviteResponse"];
type PaginatedInvites = components["schemas"]["PaginatedResponse_InviteResponse_"];
type UserOption = components["schemas"]["AdminUserResponse"];

View file

@ -9,8 +9,8 @@ import { useRequireAuth } from "../hooks/useRequireAuth";
import { components } from "../generated/api";
// Use generated types from OpenAPI schema
type CounterRecord = components["schemas"]["CounterRecordResponse"];
type SumRecord = components["schemas"]["SumRecordResponse"];
type _CounterRecord = components["schemas"]["CounterRecordResponse"];
type _SumRecord = components["schemas"]["SumRecordResponse"];
type PaginatedCounterRecords = components["schemas"]["PaginatedResponse_CounterRecordResponse_"];
type PaginatedSumRecords = components["schemas"]["PaginatedResponse_SumRecordResponse_"];

View file

@ -1,5 +1,5 @@
import { test, expect, Page } from "@playwright/test";
import { formatDateLocal, getTomorrowDateStr } from "./helpers/date";
import { getTomorrowDateStr } from "./helpers/date";
import { API_URL, REGULAR_USER, ADMIN_USER, clearAuth, loginUser } from "./helpers/auth";
/**

View file

@ -1,5 +1,5 @@
import { test, expect, Page } from "@playwright/test";
import { formatDateLocal, getTomorrowDateStr } from "./helpers/date";
import { test, expect } from "@playwright/test";
import { getTomorrowDateStr } from "./helpers/date";
import { API_URL, REGULAR_USER, ADMIN_USER, clearAuth, loginUser } from "./helpers/auth";
/**

View file

@ -1,5 +1,5 @@
import { test, expect, Page } from "@playwright/test";
import { formatDateLocal, getTomorrowDateStr } from "./helpers/date";
import { getTomorrowDateStr } from "./helpers/date";
import { API_URL, REGULAR_USER, ADMIN_USER, clearAuth, loginUser } from "./helpers/auth";
/**
@ -265,7 +265,7 @@ test.describe("Booking Page - With Availability", () => {
// Click any slot (3rd to avoid conflicts)
const slotToBook = slotButtons.nth(2);
const slotText = await slotToBook.textContent();
const _slotText = await slotToBook.textContent();
await slotToBook.click();
// Book it

View file

@ -357,7 +357,7 @@ test.describe("Profile - Unauthenticated Access", () => {
await expect(page).toHaveURL("/login");
});
test("profile API requires authentication", async ({ page, request }) => {
test("profile API requires authentication", async ({ page: _page, request }) => {
const response = await request.get(`${API_URL}/api/profile`);
expect(response.status()).toBe(401);
});

31
frontend/eslint.config.js Normal file
View file

@ -0,0 +1,31 @@
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import reactHooks from 'eslint-plugin-react-hooks';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
plugins: {
'react-hooks': reactHooks,
},
rules: {
...reactHooks.configs.recommended.rules,
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
// Downgrade to warnings - existing patterns use these
'react-hooks/exhaustive-deps': 'warn',
'react-hooks/set-state-in-effect': 'off',
},
},
{
ignores: [
'.next/**',
'node_modules/**',
'app/generated/**',
],
}
);

File diff suppressed because it is too large Load diff

View file

@ -2,13 +2,16 @@
"name": "frontend",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"test": "vitest run",
"test:e2e": "playwright test",
"generate-api-types": "openapi-typescript http://localhost:8000/openapi.json -o app/generated/api.ts"
"generate-api-types": "openapi-typescript http://localhost:8000/openapi.json -o app/generated/api.ts",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"dependencies": {
"bech32": "^2.0.0",
@ -17,14 +20,18 @@
"react-dom": "19.0.0"
},
"devDependencies": {
"@eslint/js": "^9.39.2",
"@playwright/test": "^1.49.1",
"@testing-library/react": "^16.1.0",
"@types/node": "25.0.3",
"@types/react": "19.2.7",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.39.2",
"eslint-plugin-react-hooks": "^7.0.1",
"jsdom": "^26.0.0",
"openapi-typescript": "^7.10.1",
"typescript": "5.9.3",
"typescript-eslint": "^8.50.0",
"vitest": "^2.1.8"
}
}