Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions frontend-react/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# EditorConfig helps maintain consistent coding styles
# for multiple developers working on the same project
# across various editors and IDEs.
# https://editorconfig.org

root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 100
Copy link

Copilot AI Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent line length limits: Prettier is configured with printWidth: 80 in .prettierrc, while EditorConfig specifies max_line_length = 100. These should be aligned to the same value to avoid confusion.

Suggested change
max_line_length = 100
max_line_length = 80

Copilot uses AI. Check for mistakes.

[*.md]
trim_trailing_whitespace = false

[*.{json,yml,yaml}]
indent_size = 2

[*.{ts,tsx,js,jsx}]
indent_size = 2
47 changes: 47 additions & 0 deletions frontend-react/.github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Lint and Format Check

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

jobs:
lint-and-format:
name: ESLint & Prettier Check
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [18.x, 20.x]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
cache-dependency-path: frontend-react/package-lock.json

- name: Install dependencies
working-directory: ./frontend-react
run: npm ci

- name: Run ESLint
working-directory: ./frontend-react
run: npm run lint

- name: Check formatting with Prettier
working-directory: ./frontend-react
run: npm run format:check

- name: Type check
working-directory: ./frontend-react
run: npm run type-check

- name: Build project
working-directory: ./frontend-react
run: npm run build
1 change: 1 addition & 0 deletions frontend-react/.husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx lint-staged
41 changes: 41 additions & 0 deletions frontend-react/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Dependencies
node_modules
package-lock.json
yarn.lock
pnpm-lock.yaml

# Build outputs
dist
build
.next
out
coverage

# Cache
.cache
.turbo
.vite
.eslintcache

# Environment files
.env
.env.local
.env.production

# Logs
*.log

# OS files
.DS_Store
Thumbs.db

# IDE
.vscode/*
!.vscode/settings.json
!.vscode/extensions.json
.idea

# Generated files
*.min.js
*.min.css
public/build
16 changes: 16 additions & 0 deletions frontend-react/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": false,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"arrowParens": "always",
"endOfLine": "lf",
"bracketSpacing": true,
"jsxSingleQuote": false,
"bracketSameLine": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"embeddedLanguageFormatting": "auto"
}
8 changes: 8 additions & 0 deletions frontend-react/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"editorconfig.editorconfig",
"bradlc.vscode-tailwindcss"
]
}
100 changes: 87 additions & 13 deletions frontend-react/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,102 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import js from '@eslint/js';
import prettierConfig from 'eslint-config-prettier';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import prettier from 'eslint-plugin-prettier';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import globals from 'globals';
import tseslint from 'typescript-eslint';

export default tseslint.config(
{ ignores: ['dist'] },
{ ignores: ['dist', 'node_modules', 'build', 'coverage', '.vite'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
extends: [js.configs.recommended, ...tseslint.configs.recommended, prettierConfig],
files: ['**/*.{ts,tsx,js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
'jsx-a11y': jsxA11y,
prettier,
},
settings: {
react: {
version: 'detect',
},
},
rules: {
// Prettier integration
'prettier/prettier': 'error',

// React Hooks
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },

// React Refresh
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],

// React best practices
'react/react-in-jsx-scope': 'off', // Not needed in React 17+
'react/prop-types': 'off', // Using TypeScript
'react/jsx-uses-react': 'off', // Not needed in React 17+
'react/jsx-uses-vars': 'error',
'react/jsx-key': ['error', { checkFragmentShorthand: true }],
'react/jsx-no-duplicate-props': 'error',
'react/jsx-no-undef': 'error',
'react/jsx-pascal-case': 'error',
'react/no-children-prop': 'error',
'react/no-danger-with-children': 'error',
'react/no-deprecated': 'warn',
'react/no-direct-mutation-state': 'error',
'react/no-unescaped-entities': 'warn',
'react/self-closing-comp': 'error',
'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }],

// TypeScript specific
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'warn',
'@typescript-eslint/consistent-type-imports': ['warn', { prefer: 'type-imports' }],

// Accessibility
'jsx-a11y/alt-text': 'warn',
'jsx-a11y/anchor-is-valid': 'warn',
'jsx-a11y/click-events-have-key-events': 'warn',
'jsx-a11y/no-static-element-interactions': 'warn',
'jsx-a11y/aria-props': 'error',
'jsx-a11y/aria-proptypes': 'error',
'jsx-a11y/aria-unsupported-elements': 'error',
'jsx-a11y/role-has-required-aria-props': 'error',
'jsx-a11y/role-supports-aria-props': 'error',

// General best practices
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-debugger': 'warn',
'prefer-const': 'error',
'no-var': 'error',
'object-shorthand': 'error',
'prefer-template': 'error',
'prefer-arrow-callback': 'error',
'no-duplicate-imports': 'error',
eqeqeq: ['error', 'always', { null: 'ignore' }],
'no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true }],
},
},
)
}
);
Loading