diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 00000000..751ce990 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,27 @@ +name: Lint + +on: + push: + branches: [ "*" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./webapp + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18.x + cache: 'yarn' + cache-dependency-path: ./webapp/yarn.lock + - run: yarn + - run: yarn lint diff --git a/webapp/.eslintrc.js b/webapp/.eslintrc.js new file mode 100644 index 00000000..f89004ec --- /dev/null +++ b/webapp/.eslintrc.js @@ -0,0 +1,98 @@ +module.exports = { + root: true, + env: { + node: true, + es6: true, + }, + parserOptions: { ecmaVersion: 8 }, // to enable features such as async/await + // We don't want to lint generated files nor node_modules, but we want to lint .prettierrc.json (ignored by default by eslint) + ignorePatterns: ['node_modules/*', '.next/*', '.out/*', '!.prettierrc.json'], + extends: [ + 'eslint:recommended', + 'next', + 'prettier', + ], + settings: { react: { version: 'detect' } }, + overrides: [ + // This configuration will apply only to TypeScript files + { + files: ['**/*.ts', '**/*.tsx', 'src/**/*.js'], + parser: '@typescript-eslint/parser', + env: { + browser: true, + node: true, + es6: true, + }, + extends: [ + 'plugin:@typescript-eslint/recommended', // TypeScript rules + 'plugin:react-hooks/recommended', // React hooks rules + 'plugin:jsx-a11y/recommended', // Accessibility rules + ], + plugins: ['no-switch-statements'], + rules: { + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-unused-vars': ['error'], + '@typescript-eslint/member-ordering': [ + 'error', + { + default: { memberTypes: 'never', order: 'alphabetically' }, + interfaces: ['signature', 'method', 'constructor', 'field'], + }, + ], + curly: 'error', + 'jsx-a11y/anchor-is-valid': 'off', + 'no-console': 'error', + 'no-switch-statements/no-switch': 'error', + 'prefer-const': ['error', {}], + 'react/jsx-handler-names': [ + 'error', + { + eventHandlerPrefix: 'on', + eventHandlerPropPrefix: 'on', + }, + ], + 'react/jsx-no-target-blank': 'error', + 'react/jsx-sort-props': [ + 'error', + { + ignoreCase: true, + reservedFirst: true, + }, + ], + 'react/no-danger': 'error', + 'react/no-deprecated': 'error', + 'react/no-typos': 'error', + 'react/no-unknown-property': 'error', + 'react/no-unsafe': [ + 'error', + { + checkAliases: true, + }, + ], + 'react/no-unused-prop-types': 'error', + 'react/prefer-stateless-function': 'error', + 'react/prop-types': 'off', + 'react/react-in-jsx-scope': 'off', + 'react-hooks/exhaustive-deps': 'off', + 'react/self-closing-comp': [ + 'error', + { + component: true, + html: true, + }, + ], + 'sort-imports': [ + 'error', + { + ignoreCase: true, + allowSeparatedGroups: true, + memberSyntaxSortOrder: ['none', 'all', 'single', 'multiple'], + }, + ], + 'sort-keys': 'error', + 'sort-vars': 'error', + }, + }, + ], +}; diff --git a/webapp/.eslintrc.json b/webapp/.eslintrc.json deleted file mode 100644 index bffb357a..00000000 --- a/webapp/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/webapp/.prettierignore b/webapp/.prettierignore new file mode 100644 index 00000000..a07eff62 --- /dev/null +++ b/webapp/.prettierignore @@ -0,0 +1,19 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# auto-generated localizations +src/locale/**/*.yml + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +.nyc_output + +# next.js +/.next/ +/out/ + +# production +/build diff --git a/webapp/.prettierrc b/webapp/.prettierrc deleted file mode 100644 index 9e26dfee..00000000 --- a/webapp/.prettierrc +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/webapp/.prettierrc.json b/webapp/.prettierrc.json new file mode 100644 index 00000000..544138be --- /dev/null +++ b/webapp/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/webapp/package.json b/webapp/package.json index e8956362..90304ea5 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -6,6 +6,8 @@ "dev": "next dev", "build": "next build", "start": "next start", + "lint:eslint": "eslint .", + "lint:eslint-fix": "eslint . --fix", "lint": "next lint" }, "dependencies": { @@ -23,9 +25,19 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", - "eslint": "^8", - "eslint-config-next": "13.5.6", - "prettier": "^3.0.3", + "eslint": "^7.15.0", + "eslint-config-next": "^12.0.10", + "eslint-config-prettier": "^8.3.0", + "eslint-loader": "^4.0.2", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-next": "^0.0.0", + "eslint-plugin-no-switch-statements": "^1.0.0", + "eslint-plugin-react": "^7.22.0", + "eslint-plugin-react-hooks": "^4.2.0", + "eslint-plugin-yml": "^0.13.0", + "@typescript-eslint/eslint-plugin": "^6", + "@typescript-eslint/parser": "^6", + "prettier": "^2.5.1", "typescript": "^5" } } diff --git a/webapp/src/app/[lang]/page.tsx b/webapp/src/app/[lang]/page.tsx index 869893d6..dcd310e9 100644 --- a/webapp/src/app/[lang]/page.tsx +++ b/webapp/src/app/[lang]/page.tsx @@ -1,19 +1,18 @@ -"use client"; +'use client'; -import MessageForm from "@/components/MessageForm"; -import { MessageData } from "@/utils/readTypedMessages"; -import { Box, Button, Link, Typography } from "@mui/joy"; -import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; +import { MessageData } from '@/utils/readTypedMessages'; +import MessageForm from '@/components/MessageForm'; +import { Box, Button, Link, Typography } from '@mui/joy'; +import { useEffect, useState } from 'react'; export default function Home({ params }: { params: { lang: string } }) { const [messages, setMessages] = useState([]); const [translations, setTranslations] = useState>({}); - const [pullRequestUrl, setPullRequestUrl] = useState(""); + const [pullRequestUrl, setPullRequestUrl] = useState(''); useEffect(() => { async function loadMessages() { - const res = await fetch("/api/messages"); + const res = await fetch('/api/messages'); const payload = await res.json(); setMessages(payload.data); } @@ -36,8 +35,8 @@ export default function Home({ params }: { params: { lang: string } }) { Messages