diff --git a/.eslintrc.js b/.eslintrc.js index d0350ff18..265d626af 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,6 +9,7 @@ module.exports = { 'plugin:jest/recommended', 'plugin:cypress/recommended', 'plugin:storybook/recommended', + 'plugin:@typescript-eslint/eslint-recommended', ], env: { browser: true, @@ -27,12 +28,58 @@ module.exports = { '@operation_code/custom-rules', 'import', 'lodash', + '@typescript-eslint', ], globals: { cy: true, Cypress: true, }, overrides: [ + { + files: ['./**/*.ts', './**/*.tsx'], + parser: '@typescript-eslint/parser', + parserOptions: { + project: true, + }, + extends: ['plugin:@typescript-eslint/strict', 'plugin:@typescript-eslint/stylistic'], + rules: { + 'react/no-array-index-key': 'off', + 'react/require-default-props': 'off', + + // Typescript Rules + // custom rules for typescript-eslint: https://github.com/OperationCode/front-end/pull/1792#pullrequestreview-1874516174 + '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'no-type-imports' }], + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: 'variable', + types: ['boolean'], + format: ['PascalCase', 'UPPER_CASE'], + prefix: [ + 'is', + 'was', + 'should', + 'has', + 'can', + 'did', + 'will', + 'IS_', + 'WAS_', + 'SHOULD_', + 'HAS_', + 'CAN_', + 'DID_', + 'WILL_', + ], + }, + ], + '@typescript-eslint/no-empty-interface': ['error', { allowSingleExtends: true }], + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-unused-vars': ['error', { vars: 'all', varsIgnorePattern: '_' }], + '@typescript-eslint/unbound-method': 'off', // gives false negatives in arrow funcs + }, + }, { files: ['cypress/**/*.js'], rules: { @@ -128,7 +175,7 @@ module.exports = { ], 'react/forbid-prop-types': ['error', { forbid: ['any'] }], 'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }], - 'react/jsx-filename-extension': ['error', { extensions: ['.js'] }], + 'react/jsx-filename-extension': ['error', { extensions: ['.js', '.tsx'] }], 'react/jsx-max-props-per-line': ['error', { maximum: 1, when: 'multiline' }], 'react/jsx-no-target-blank': 'off', // browsers protect against this vulnerability now 'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }], @@ -197,4 +244,6 @@ module.exports = { ], 'no-use-before-define': 'off', }, + + root: true, }; diff --git a/components/Cards/Card/Card.js b/components/Cards/Card/Card.js index 911bd0ebc..4576efd36 100644 --- a/components/Cards/Card/Card.js +++ b/components/Cards/Card/Card.js @@ -13,14 +13,14 @@ Card.defaultProps = { hasAnimationOnHover: false, }; -function Card({ children, className, hasAnimationOnHover, ...props }) { +function Card({ children, className, ...props }) { const customDataAttributes = getDataAttributes(props); return (
+/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/next.config.js b/next.config.js index 20b7bb7b6..2f5104c94 100644 --- a/next.config.js +++ b/next.config.js @@ -1,3 +1,4 @@ +// @ts-check const hasBundleAnalyzer = process.env.ANALYZE === 'true'; const { withSentryConfig } = require('@sentry/nextjs'); const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: hasBundleAnalyzer }); @@ -127,10 +128,14 @@ const nextConfig = { * The name of the function type is `normalizeConfig` in `next/dist/server/config-shared`, * but JSDoc type imports can't read it. * - * @type {(phase: string, config: import('next').NextConfig) => Promise)} + * @type {(phase: string, config: import('next').NextConfig) => Promise} */ -module.exports = (phase, defaultConfig) => { +module.exports = async (phase, defaultConfig) => { const plugins = [ + /** + * + * @type {(config: import('next').NextConfig) => any} + */ config => withSentryConfig(config, sentryWebpackPluginOptions), withBundleAnalyzer, ]; diff --git a/package.json b/package.json index 6c2523337..14529b6ae 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,17 @@ "@testing-library/cypress": "^8.0.3", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^12.1.5", + "@types/fingerprintjs2": "2", + "@types/fontfaceobserver": "^2.1.3", + "@types/lodash": "^4.14.202", + "@types/logrocket-react": "^3.0.3", + "@types/object-hash": "^3.0.0", + "@types/prop-types": "^15.7.11", + "@types/react": "^18.2.55", + "@types/react-dom": "^18.2.19", + "@types/react-select": "^4.0.18", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", "autoprefixer": "^10.4.15", "axios-mock-adapter": "^1.21.1", "babel-jest": "^28.1.3", @@ -114,7 +125,7 @@ "css-loader": "^6.7.1", "cypress": "^10.3.1", "cypress-image-snapshot": "^4.0.1", - "eslint": "^8.24.0", + "eslint": "^8.56.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-prettier": "^8.5.0", "eslint-plugin-cypress": "^2.12.1", @@ -154,6 +165,7 @@ "stylelint-config-standard": "^26.0.0", "stylelint-prettier": "^2.0.0", "tailwindcss": "^3.3.3", + "typescript": "^5.3.3", "url-loader": "^4.1.1", "webpack": "^5.73.0" }, diff --git a/pages/404.js b/pages/404.tsx similarity index 100% rename from pages/404.js rename to pages/404.tsx diff --git a/pages/_app.js b/pages/_app.tsx similarity index 87% rename from pages/_app.js rename to pages/_app.tsx index 316766cf7..efcc85655 100644 --- a/pages/_app.js +++ b/pages/_app.tsx @@ -1,11 +1,8 @@ -/* eslint-disable max-classes-per-file */ - // Polyfills import 'intersection-observer'; -import { useEffect } from 'react'; +import { useEffect, PropsWithChildren } from 'react'; import * as Sentry from '@sentry/nextjs'; -import { node } from 'prop-types'; import Router from 'next/router'; import Fingerprint2 from 'fingerprintjs2'; import FontFaceObserver from 'fontfaceobserver'; @@ -18,6 +15,8 @@ import { gtag } from 'common/utils/thirdParty/gtag'; import Nav from 'components/Nav/Nav'; import Footer from 'components/Footer/Footer'; import 'common/styles/globals.css'; +import { AppProps } from 'next/app'; +import NextErrorComponent from 'next/error'; const isProduction = process.env.NODE_ENV === 'production'; @@ -34,11 +33,7 @@ const fonts = [ }, ]; -Layout.propTypes = { - children: node.isRequired, -}; - -function Layout({ children }) { +function Layout({ children }: PropsWithChildren) { return (