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:
parent
30583805cd
commit
4b394b0698
12 changed files with 1467 additions and 16 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -28,4 +28,5 @@ next_pr.md
|
||||||
|
|
||||||
# Notes
|
# Notes
|
||||||
notes/
|
notes/
|
||||||
|
prompts/
|
||||||
.coverage
|
.coverage
|
||||||
|
|
|
||||||
8
Makefile
8
Makefile
|
|
@ -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
|
-include .env
|
||||||
export
|
export
|
||||||
|
|
@ -105,3 +105,9 @@ fix-backend:
|
||||||
|
|
||||||
security-backend:
|
security-backend:
|
||||||
cd backend && uv run bandit -r . -c pyproject.toml
|
cd backend && uv run bandit -r . -c pyproject.toml
|
||||||
|
|
||||||
|
lint-frontend:
|
||||||
|
cd frontend && npm run lint
|
||||||
|
|
||||||
|
fix-frontend:
|
||||||
|
cd frontend && npm run lint:fix
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { formatDate, formatDisplayDate, getDateRange, formatTimeString, isWeeken
|
||||||
|
|
||||||
const { slotDurationMinutes, maxAdvanceDays, minAdvanceDays } = constants.booking;
|
const { slotDurationMinutes, maxAdvanceDays, minAdvanceDays } = constants.booking;
|
||||||
|
|
||||||
type AvailabilityDay = components["schemas"]["AvailabilityDay"];
|
type _AvailabilityDay = components["schemas"]["AvailabilityDay"];
|
||||||
type AvailabilityResponse = components["schemas"]["AvailabilityResponse"];
|
type AvailabilityResponse = components["schemas"]["AvailabilityResponse"];
|
||||||
type TimeSlot = components["schemas"]["TimeSlot"];
|
type TimeSlot = components["schemas"]["TimeSlot"];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import constants from "../../../../shared/constants.json";
|
||||||
const { READY, SPENT, REVOKED } = constants.inviteStatuses;
|
const { READY, SPENT, REVOKED } = constants.inviteStatuses;
|
||||||
|
|
||||||
// Use generated types from OpenAPI schema
|
// Use generated types from OpenAPI schema
|
||||||
type InviteRecord = components["schemas"]["InviteResponse"];
|
type _InviteRecord = components["schemas"]["InviteResponse"];
|
||||||
type PaginatedInvites = components["schemas"]["PaginatedResponse_InviteResponse_"];
|
type PaginatedInvites = components["schemas"]["PaginatedResponse_InviteResponse_"];
|
||||||
type UserOption = components["schemas"]["AdminUserResponse"];
|
type UserOption = components["schemas"]["AdminUserResponse"];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ import { useRequireAuth } from "../hooks/useRequireAuth";
|
||||||
import { components } from "../generated/api";
|
import { components } from "../generated/api";
|
||||||
|
|
||||||
// Use generated types from OpenAPI schema
|
// Use generated types from OpenAPI schema
|
||||||
type CounterRecord = components["schemas"]["CounterRecordResponse"];
|
type _CounterRecord = components["schemas"]["CounterRecordResponse"];
|
||||||
type SumRecord = components["schemas"]["SumRecordResponse"];
|
type _SumRecord = components["schemas"]["SumRecordResponse"];
|
||||||
type PaginatedCounterRecords = components["schemas"]["PaginatedResponse_CounterRecordResponse_"];
|
type PaginatedCounterRecords = components["schemas"]["PaginatedResponse_CounterRecordResponse_"];
|
||||||
type PaginatedSumRecords = components["schemas"]["PaginatedResponse_SumRecordResponse_"];
|
type PaginatedSumRecords = components["schemas"]["PaginatedResponse_SumRecordResponse_"];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { test, expect, Page } from "@playwright/test";
|
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";
|
import { API_URL, REGULAR_USER, ADMIN_USER, clearAuth, loginUser } from "./helpers/auth";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { test, expect, Page } from "@playwright/test";
|
import { test, expect } 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";
|
import { API_URL, REGULAR_USER, ADMIN_USER, clearAuth, loginUser } from "./helpers/auth";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { test, expect, Page } from "@playwright/test";
|
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";
|
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)
|
// Click any slot (3rd to avoid conflicts)
|
||||||
const slotToBook = slotButtons.nth(2);
|
const slotToBook = slotButtons.nth(2);
|
||||||
const slotText = await slotToBook.textContent();
|
const _slotText = await slotToBook.textContent();
|
||||||
await slotToBook.click();
|
await slotToBook.click();
|
||||||
|
|
||||||
// Book it
|
// Book it
|
||||||
|
|
|
||||||
|
|
@ -357,7 +357,7 @@ test.describe("Profile - Unauthenticated Access", () => {
|
||||||
await expect(page).toHaveURL("/login");
|
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`);
|
const response = await request.get(`${API_URL}/api/profile`);
|
||||||
expect(response.status()).toBe(401);
|
expect(response.status()).toBe(401);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
31
frontend/eslint.config.js
Normal file
31
frontend/eslint.config.js
Normal 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/**',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
1414
frontend/package-lock.json
generated
1414
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -2,13 +2,16 @@
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:e2e": "playwright test",
|
"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": {
|
"dependencies": {
|
||||||
"bech32": "^2.0.0",
|
"bech32": "^2.0.0",
|
||||||
|
|
@ -17,14 +20,18 @@
|
||||||
"react-dom": "19.0.0"
|
"react-dom": "19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.39.2",
|
||||||
"@playwright/test": "^1.49.1",
|
"@playwright/test": "^1.49.1",
|
||||||
"@testing-library/react": "^16.1.0",
|
"@testing-library/react": "^16.1.0",
|
||||||
"@types/node": "25.0.3",
|
"@types/node": "25.0.3",
|
||||||
"@types/react": "19.2.7",
|
"@types/react": "19.2.7",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"eslint": "^9.39.2",
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"openapi-typescript": "^7.10.1",
|
"openapi-typescript": "^7.10.1",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
|
"typescript-eslint": "^8.50.0",
|
||||||
"vitest": "^2.1.8"
|
"vitest": "^2.1.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue