diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 31076ae..cad9e7f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -109,8 +109,6 @@ jobs:
exit 1
fi
-
-
# Run all pending migrations against the test database so
# the schema is correct before tests start.
- name: Run database migrations
@@ -214,6 +212,9 @@ jobs:
- name: Run ESLint
run: npm run lint
+ - name: Check formatting
+ run: npm run format:check
+
# TypeScript type checking without emitting output files.
# Catches type errors that ESLint does not catch.
- name: Run type check
@@ -299,4 +300,4 @@ jobs:
refactor
perf
chore
- requireScope: false
\ No newline at end of file
+ requireScope: false
\ No newline at end of file
diff --git a/frontend/.prettierignore b/frontend/.prettierignore
new file mode 100644
index 0000000..a0b30b3
--- /dev/null
+++ b/frontend/.prettierignore
@@ -0,0 +1,6 @@
+# Build output — never format generated files
+dist/
+node_modules/
+
+# Generated by TypeScript
+*.tsbuildinfo
\ No newline at end of file
diff --git a/frontend/.prettierrc b/frontend/.prettierrc
new file mode 100644
index 0000000..7a87c0d
--- /dev/null
+++ b/frontend/.prettierrc
@@ -0,0 +1,9 @@
+{
+ "semi": false,
+ "singleQuote": true,
+ "tabWidth": 2,
+ "trailingComma": "es5",
+ "printWidth": 80,
+ "arrowParens": "avoid",
+ "endOfLine": "lf"
+}
\ No newline at end of file
diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js
index ef614d2..9304b71 100644
--- a/frontend/eslint.config.js
+++ b/frontend/eslint.config.js
@@ -4,19 +4,29 @@ import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
+import prettierConfig from 'eslint-config-prettier'
export default defineConfig([
globalIgnores(['dist']),
+ js.configs.recommended,
+ ...tseslint.configs.recommended,
{
files: ['**/*.{ts,tsx}'],
- extends: [
- js.configs.recommended,
- tseslint.configs.recommended,
- reactHooks.configs.flat.recommended,
- reactRefresh.configs.vite,
- ],
languageOptions: {
globals: globals.browser,
},
+ plugins: {
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...reactHooks.configs.recommended.rules,
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
},
-])
+ // Must be the absolute last object in the flat array to override formatting rules
+ prettierConfig,
+])
\ No newline at end of file
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index aab84ca..b411a8b 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -18,9 +18,11 @@
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^10.3.0",
+ "eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.6.0",
+ "prettier": "^3.8.3",
"typescript": "~6.0.2",
"typescript-eslint": "^8.59.2",
"vite": "^8.0.12"
@@ -1467,6 +1469,22 @@
}
}
},
+ "node_modules/eslint-config-prettier": {
+ "version": "10.1.8",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
+ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-config-prettier"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
"node_modules/eslint-plugin-react-hooks": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz",
@@ -2363,6 +2381,22 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.8.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
+ "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index d54e538..831124f 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -8,7 +8,9 @@
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
- "type-check": "tsc --noEmit"
+ "type-check": "tsc --noEmit",
+ "format": "prettier --write src",
+ "format:check": "prettier --check src"
},
"dependencies": {
"react": "^19.2.6",
@@ -21,11 +23,13 @@
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^10.3.0",
+ "eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.6.0",
+ "prettier": "^3.8.3",
"typescript": "~6.0.2",
"typescript-eslint": "^8.59.2",
"vite": "^8.0.12"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index a66b5ef..715ccdf 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -24,7 +24,7 @@ function App() {
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index bef5202..2caec89 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -6,5 +6,5 @@ import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
- ,
+
)