diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..ea57f4e5 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,46 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaFeatures: { jsx: true }, + }, + env: { + browser: true, + node: true, + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react/recommended', + 'plugin:jsx-a11y/recommended', + // Prettier plugin and recommended rules + 'prettier/@typescript-eslint', + 'plugin:prettier/recommended', + ], + rules: { + // Include .prettierrc.js rules + 'prettier/prettier': ['error', {}, { usePrettierrc: true }], + 'react/prop-types': 'off', + 'react/react-in-jsx-scope': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/ban-ts-ignore': 'off', + '@typescript-eslint/no-var-requires': 'off', + 'jsx-a11y/label-has-associated-control': [ + 'error', + { + labelComponents: [], + labelAttributes: [], + controlComponents: [], + assert: 'either', + depth: 25, + }, + ], + '@typescript-eslint/no-explicit-any': 'off', + }, + settings: { + react: { + version: 'detect', + }, + }, +}; diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 00000000..19ccceba --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,8 @@ +module.exports = { + semi: true, + trailingComma: 'es5', + singleQuote: true, + printWidth: 79, + tabWidth: 2, + useTabs: false, +}; diff --git a/__tests__/components/search/Form.test.tsx b/__tests__/components/search/Form.test.tsx index c42d2e00..24b0160d 100644 --- a/__tests__/components/search/Form.test.tsx +++ b/__tests__/components/search/Form.test.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { render } from '@testing-library/react'; -import renderer from 'react-test-renderer'; import Form from '../../../components/search/Form'; const useRouter = jest.spyOn(require('next/router'), 'useRouter'); diff --git a/__tests__/components/search/Item.test.tsx b/__tests__/components/search/Item.test.tsx index 064aa5f2..30b41b1e 100644 --- a/__tests__/components/search/Item.test.tsx +++ b/__tests__/components/search/Item.test.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { render } from '@testing-library/react'; import renderer from 'react-test-renderer'; import Item from '../../../components/search/Item'; @@ -10,6 +9,8 @@ test('📸 of Input component with empty', () => { organization: null, __typename: 'Package', }; - const tree = renderer.create().toJSON(); + const tree = renderer + .create() + .toJSON(); expect(tree).toMatchSnapshot(); }); diff --git a/components/_shared/CustomLink.tsx b/components/_shared/CustomLink.tsx new file mode 100644 index 00000000..2b50236e --- /dev/null +++ b/components/_shared/CustomLink.tsx @@ -0,0 +1,15 @@ +type LinkProps = { + url: string; + format: any; +}; + +const CustomLink: React.FC = ({ url, format }: LinkProps) => ( + + {format} + +); + +export default CustomLink; diff --git a/components/_shared/Error.tsx b/components/_shared/Error.tsx index 87823668..afdb4cec 100644 --- a/components/_shared/Error.tsx +++ b/components/_shared/Error.tsx @@ -1,4 +1,4 @@ -export default function ErrorMessage({ message }) { +const ErrorMessage: React.FC<{ message: any }> = ({ message }) => { return ( ); -} +}; + +export default ErrorMessage; diff --git a/components/_shared/Table.tsx b/components/_shared/Table.tsx index cc0ca0e4..d5ef20c2 100644 --- a/components/_shared/Table.tsx +++ b/components/_shared/Table.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - interface TableProps { columns: Array; data: Array; diff --git a/components/_shared/index.ts b/components/_shared/index.ts index 9d5cf16c..f0643faa 100644 --- a/components/_shared/index.ts +++ b/components/_shared/index.ts @@ -1,4 +1,5 @@ import Table from './Table'; import ErrorMessage from './Error'; +import CustomLink from './CustomLink'; -export { Table, ErrorMessage }; +export { Table, ErrorMessage, CustomLink }; diff --git a/components/dataset/About.tsx b/components/dataset/About.tsx index 52ebb01e..bdaa1f85 100644 --- a/components/dataset/About.tsx +++ b/components/dataset/About.tsx @@ -34,7 +34,7 @@ const columns = [ }, ]; -export default function About({ variables }) { +const About: React.FC<{ variables: any }> = ({ variables }) => { const { loading, error, data } = useQuery(GET_DATAPACKAGE_QUERY, { variables, // Setting this value to true will make the component rerender when @@ -48,4 +48,6 @@ export default function About({ variables }) { const { result } = data.dataset; return ; -} +}; + +export default About; diff --git a/components/dataset/Org.tsx b/components/dataset/Org.tsx index 69314426..cf4f10af 100644 --- a/components/dataset/Org.tsx +++ b/components/dataset/Org.tsx @@ -3,7 +3,7 @@ import { useQuery } from '@apollo/react-hooks'; import { ErrorMessage } from '../_shared'; import { GET_ORG_QUERY } from '../../graphql/queries'; -export default function Org({ variables }) { +const Org: React.FC<{ variables: any }> = ({ variables }) => { const { loading, error, data } = useQuery(GET_ORG_QUERY, { variables, // Setting this value to true will make the component rerender when @@ -26,8 +26,10 @@ export default function Org({ variables }) { 'https://datahub.io/static/img/datahub-cube-edited.svg' } className="h-5 w-5 mr-2 inline-block" + alt="org_img" /> + {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} {organization.title || organization.name} @@ -38,4 +40,6 @@ export default function Org({ variables }) { )} ); -} +}; + +export default Org; diff --git a/components/dataset/Resources.tsx b/components/dataset/Resources.tsx index f42c70ef..d43d8e80 100644 --- a/components/dataset/Resources.tsx +++ b/components/dataset/Resources.tsx @@ -1,3 +1,5 @@ +/* eslint-disable jsx-a11y/anchor-is-valid */ +/* eslint-disable react/display-name */ import Link from 'next/link'; import { useQuery } from '@apollo/react-hooks'; import { Table, ErrorMessage } from '../_shared'; @@ -36,7 +38,7 @@ const columns = [ }, ]; -export default function Resources({ variables }) { +const Resources: React.FC<{ variables: any }> = ({ variables }) => { const { loading, error, data } = useQuery(GET_RESOURCES_QUERY, { variables, // Setting this value to true will make the component rerender when @@ -62,4 +64,6 @@ export default function Resources({ variables }) { /> ); -} +}; + +export default Resources; diff --git a/components/home/Nav.tsx b/components/home/Nav.tsx index 7d8cb864..2422bec2 100644 --- a/components/home/Nav.tsx +++ b/components/home/Nav.tsx @@ -1,7 +1,8 @@ +/* eslint-disable jsx-a11y/anchor-is-valid */ import Link from 'next/link'; -import React, { useState } from 'react'; +import { useState } from 'react'; -export default function Nav() { +const Nav: React.FC = () => { const [open, setOpen] = useState(false); const handleClick = (event) => { @@ -44,6 +45,7 @@ export default function Nav() { href="http://tech.datopian.com/frontend/" className="block mt-4 lg:inline-block lg:mt-0 text-gray-700 hover:text-black mr-6" target="_blank" + rel="noreferrer" > Docs @@ -51,10 +53,13 @@ export default function Nav() { href="https://github.com/datopian/portal" className="inline-block text-tiny px-4 py-2 leading-none border rounded text-primary bg-primary-background border-black hover:border-gray-700 hover:text-gray-700 hover:bg-white mt-4 lg:mt-0" target="_blank" + rel="noreferrer" > GitHub ); -} +}; + +export default Nav; diff --git a/components/home/Recent.tsx b/components/home/Recent.tsx index c67f7281..993dc112 100644 --- a/components/home/Recent.tsx +++ b/components/home/Recent.tsx @@ -1,9 +1,10 @@ +/* eslint-disable jsx-a11y/anchor-is-valid */ import Link from 'next/link'; import { useQuery } from '@apollo/react-hooks'; import { ErrorMessage } from '../_shared'; import { SEARCH_QUERY } from '../../graphql/queries'; -function Recent() { +const Recent: React.FC = () => { const { loading, error, data } = useQuery(SEARCH_QUERY, { variables: { sort: 'metadata_created desc', @@ -47,6 +48,6 @@ function Recent() { ); -} +}; export default Recent; diff --git a/components/resource/About.tsx b/components/resource/About.tsx index 1b4f6b7d..9d22e5db 100644 --- a/components/resource/About.tsx +++ b/components/resource/About.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/display-name */ import { useQuery } from '@apollo/react-hooks'; import { Table, ErrorMessage } from '../_shared'; import { GET_RESOURCES_QUERY } from '../../graphql/queries'; @@ -46,7 +47,7 @@ const columns = [ }, ]; -export default function About({ variables }) { +const About: React.FC<{ variables: any }> = ({ variables }) => { const { loading, error, data } = useQuery(GET_RESOURCES_QUERY, { variables, // Setting this value to true will make the component rerender when @@ -63,4 +64,6 @@ export default function About({ variables }) { (item) => item.name === variables.resource ); return
; -} +}; + +export default About; diff --git a/components/resource/DataExplorer.tsx b/components/resource/DataExplorer.tsx index 7062a933..0c42d156 100644 --- a/components/resource/DataExplorer.tsx +++ b/components/resource/DataExplorer.tsx @@ -2,7 +2,7 @@ import { useQuery } from '@apollo/react-hooks'; import { ErrorMessage } from '../_shared'; import { GET_RESOURCES_QUERY } from '../../graphql/queries'; -export default function DataExplorer({ variables }) { +const DataExplorer: React.FC<{ variables: any }> = ({ variables }) => { const { loading, error, data } = useQuery(GET_RESOURCES_QUERY, { variables, // Setting this value to true will make the component rerender when @@ -20,4 +20,6 @@ export default function DataExplorer({ variables }) { ); return <>{JSON.stringify(resource)}; -} +}; + +export default DataExplorer; diff --git a/components/search/Form.tsx b/components/search/Form.tsx index 4d71b26d..0824c8ff 100644 --- a/components/search/Form.tsx +++ b/components/search/Form.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { useRouter } from 'next/router'; -export default function Form() { +const Form: React.FC = () => { const router = useRouter(); const [q, setQ] = useState(router.query.q); const [sort, setSort] = useState(router.query.sort); @@ -48,6 +48,7 @@ export default function Form() { id="field-order-by" name="sort" onChange={handleChange} + onBlur={handleChange} value={sort} > @@ -59,4 +60,6 @@ export default function Form() { ); -} +}; + +export default Form; diff --git a/components/search/Item.tsx b/components/search/Item.tsx index 6a6725e3..bbd5ffae 100644 --- a/components/search/Item.tsx +++ b/components/search/Item.tsx @@ -1,12 +1,15 @@ +/* eslint-disable jsx-a11y/anchor-is-valid */ import Link from 'next/link'; -export default function Item({ datapackage }) { +const Item: React.FC<{ datapackage: any }> = ({ datapackage }) => { return ( ); -} +}; + +export default Item; diff --git a/components/search/List.tsx b/components/search/List.tsx index b13e1f85..fc488e0d 100644 --- a/components/search/List.tsx +++ b/components/search/List.tsx @@ -3,7 +3,7 @@ import Item from './Item'; import { ErrorMessage } from '../_shared'; import { SEARCH_QUERY } from '../../graphql/queries'; -export default function List({ variables }) { +const List: React.FC<{ variables: any }> = ({ variables }) => { const { loading, error, data } = useQuery(SEARCH_QUERY, { variables, // Setting this value to true will make the component rerender when @@ -22,4 +22,6 @@ export default function List({ variables }) { ))} ); -} +}; + +export default List; diff --git a/components/search/Total.tsx b/components/search/Total.tsx index 0e688021..8b7a826a 100644 --- a/components/search/Total.tsx +++ b/components/search/Total.tsx @@ -2,7 +2,7 @@ import { useQuery } from '@apollo/react-hooks'; import { ErrorMessage } from '../_shared'; import { GET_TOTAL_COUNT_QUERY } from '../../graphql/queries'; -export default function Total({ variables }) { +const Total: React.FC<{ variables: any }> = ({ variables }) => { const { loading, error, data } = useQuery(GET_TOTAL_COUNT_QUERY, { variables, // Setting this value to true will make the component rerender when @@ -21,4 +21,6 @@ export default function Total({ variables }) { {result.count} results found ); -} +}; + +export default Total; diff --git a/components/static/List.tsx b/components/static/List.tsx index db73b895..c560ea11 100644 --- a/components/static/List.tsx +++ b/components/static/List.tsx @@ -3,7 +3,7 @@ import { useQuery } from '@apollo/react-hooks'; import { ErrorMessage } from '../_shared'; import { GET_POSTS_QUERY } from '../../graphql/queries'; -export default function List() { +const List: React.FC = () => { const { loading, error, data } = useQuery(GET_POSTS_QUERY, { // Setting this value to true will make the component rerender when // the "networkStatus" changes, so we are able to know if it is fetching @@ -34,4 +34,6 @@ export default function List() { ))} ); -} +}; + +export default List; diff --git a/components/static/Page.tsx b/components/static/Page.tsx index 81c4122f..87dcb19d 100644 --- a/components/static/Page.tsx +++ b/components/static/Page.tsx @@ -3,7 +3,7 @@ import { useQuery } from '@apollo/react-hooks'; import { ErrorMessage } from '../_shared'; import { GET_PAGE_QUERY } from '../../graphql/queries'; -export default function Page({ variables }) { +const Page: React.FC<{ variables: any }> = ({ variables }) => { const { loading, error, data } = useQuery(GET_PAGE_QUERY, { variables, // Setting this value to true will make the component rerender when @@ -23,8 +23,10 @@ export default function Page({ variables }) { {title}

Edited: {modified}

- + featured_img
{parse(content)}
); -} +}; + +export default Page; diff --git a/components/static/Post.tsx b/components/static/Post.tsx index a698e1b1..65d4aa42 100644 --- a/components/static/Post.tsx +++ b/components/static/Post.tsx @@ -3,7 +3,7 @@ import { useQuery } from '@apollo/react-hooks'; import { ErrorMessage } from '../_shared'; import { GET_PAGE_QUERY } from '../../graphql/queries'; -export default function Post({ variables }) { +const Post: React.FC<{ variables: any }> = ({ variables }) => { const { loading, error, data } = useQuery(GET_PAGE_QUERY, { variables, // Setting this value to true will make the component rerender when @@ -23,8 +23,10 @@ export default function Post({ variables }) { {title}

Edited: {modified}

- + featured_img
{parse(content)}
); -} +}; + +export default Post; diff --git a/lib/apolloClient.ts b/lib/apolloClient.ts index 53e89ec6..6b9487f6 100644 --- a/lib/apolloClient.ts +++ b/lib/apolloClient.ts @@ -1,10 +1,16 @@ import { useMemo } from 'react'; import getConfig from 'next/config'; import { ApolloClient } from 'apollo-client'; -import { InMemoryCache } from 'apollo-cache-inmemory'; +import { + InMemoryCache, + NormalizedCache, + NormalizedCacheObject, +} from 'apollo-cache-inmemory'; import { RestLink } from 'apollo-link-rest'; -let apolloClient; +let apolloClient: + | ApolloClient + | ApolloClient; const restLink = new RestLink({ uri: getConfig().publicRuntimeConfig.DMS + '/api/3/action/', @@ -17,11 +23,7 @@ const restLink = new RestLink({ }/posts/`, }, typePatcher: { - Search: ( - data: any, - outerType: string, - patchDeeper: RestLink.FunctionalTypePatcher - ): any => { + Search: (data: any): any => { if (data.result != null) { data.result.__typename = 'SearchResponse'; @@ -36,11 +38,7 @@ const restLink = new RestLink({ } return data; }, - Response: ( - data: any, - outerType: string, - patchDeeper: RestLink.FunctionalTypePatcher - ): any => { + Response: (data: any): any => { if (data.result != null) { data.result.__typename = 'Package'; if (data.result.organization != null) { @@ -65,8 +63,13 @@ function createApolloClient() { }); } -export function initializeApollo(initialState = null) { - const _apolloClient = apolloClient ?? createApolloClient(); +export function initializeApollo( + initialState = null +): ApolloClient | ApolloClient { + const _apolloClient: + | ApolloClient + | ApolloClient = + apolloClient ?? createApolloClient(); // If your page has Next.js data fetching methods that use Apollo Client, the initial state // gets hydrated here @@ -81,7 +84,9 @@ export function initializeApollo(initialState = null) { return _apolloClient; } -export function useApollo(initialState) { +export function useApollo( + initialState = null +): ApolloClient | ApolloClient { const store = useMemo(() => initializeApollo(initialState), [initialState]); return store; } diff --git a/package.json b/package.json index 7246cb42..071a0434 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "cypress:ci": "cypress run --config video=false", "e2e": "cypress run", "format": "prettier --single-quote --write .", - "pre-commit": "echo 'formating your changes.....' && prettier --single-quote --write" + "pre-commit": "yarn lint:fix && prettier --single-quote --write", + "lint": "eslint . --ext .ts,.tsx", + "lint:fix": "yarn lint --fix" }, "dependencies": { "@apollo/client": "^3.0.2", @@ -44,10 +46,18 @@ "@testing-library/react": "^10.0.4", "@types/jest": "^25.2.3", "@types/react": "^16.9.35", + "@typescript-eslint/eslint-plugin": "^3.8.0", + "@typescript-eslint/parser": "^3.8.0", "autoprefixer": "^9.8.6", "babel-jest": "^26.0.1", "babel-plugin-graphql-tag": "^2.5.0", "cypress": "^4.8.0", + "eslint": "^7.6.0", + "eslint-config-prettier": "^6.11.0", + "eslint-plugin-jsx-a11y": "^6.3.1", + "eslint-plugin-prettier": "^3.1.4", + "eslint-plugin-react": "^7.20.5", + "eslint-plugin-react-hooks": "^4.0.8", "husky": ">=4", "jest": "^26.1.0", "lint-staged": ">=10", @@ -56,7 +66,7 @@ "postcss-cli": "^7.1.1", "postcss-import": "^12.0.1", "postcss-preset-env": "6.7.0", - "prettier": "2.0.5", + "prettier": "^2.0.5", "react-test-renderer": "^16.13.1", "tailwindcss": "^1.4.6", "typescript": "^3.9.3" diff --git a/pages/[org]/[dataset]/index.tsx b/pages/[org]/[dataset]/index.tsx index ee59d2bd..f0cee809 100644 --- a/pages/[org]/[dataset]/index.tsx +++ b/pages/[org]/[dataset]/index.tsx @@ -8,7 +8,7 @@ import Org from '../../../components/dataset/Org'; import Resources from '../../../components/dataset/Resources'; import { GET_DATASET_QUERY } from '../../../graphql/queries'; -function Dataset({ variables }) { +const Dataset: React.FC<{ variables: any }> = ({ variables }) => { const { data, loading } = useQuery(GET_DATASET_QUERY, { variables }); if (loading) return
Loading
; @@ -31,7 +31,7 @@ function Dataset({ variables }) { ); -} +}; export const getServerSideProps: GetServerSideProps = async (context) => { const apolloClient = initializeApollo(); diff --git a/pages/[org]/[dataset]/r/[resource]/index.tsx b/pages/[org]/[dataset]/r/[resource]/index.tsx index 1ab7ec8c..272631f9 100644 --- a/pages/[org]/[dataset]/r/[resource]/index.tsx +++ b/pages/[org]/[dataset]/r/[resource]/index.tsx @@ -7,7 +7,7 @@ import About from '../../../../../components/resource/About'; import DataExplorer from '../../../../../components/resource/DataExplorer'; import { GET_RESOURCES_QUERY } from '../../../../../graphql/queries'; -function Resource({ variables }) { +const Resource: React.FC<{ variables: any }> = ({ variables }) => { const { data, loading } = useQuery(GET_RESOURCES_QUERY, { variables }); if (loading) return
Loading
; @@ -32,7 +32,7 @@ function Resource({ variables }) { ); -} +}; export const getServerSideProps: GetServerSideProps = async (context) => { const apolloClient = initializeApollo(); diff --git a/pages/_app.tsx b/pages/_app.tsx index fe2dc3b1..fbf647b9 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -6,9 +6,14 @@ import { applyTheme } from '../themes/utils'; import '../styles/app.css'; -export default function MyApp({ Component, pageProps }) { +type Props = { + Component: any; + pageProps: any; +}; + +const MyApp: React.FC = ({ Component, pageProps }) => { const apolloClient = useApollo(pageProps.initialApolloState); - const [theme, setTheme] = useState(DEFAULT_THEME); + const [theme] = useState(DEFAULT_THEME); // setTheme useEffect(() => { /** @@ -24,4 +29,6 @@ export default function MyApp({ Component, pageProps }) { ); -} +}; + +export default MyApp; diff --git a/pages/blog/[post]/index.tsx b/pages/blog/[post]/index.tsx index d9c55b20..b760486e 100644 --- a/pages/blog/[post]/index.tsx +++ b/pages/blog/[post]/index.tsx @@ -5,20 +5,22 @@ import Nav from '../../../components/home/Nav'; import Post from '../../../components/static/Post'; import { GET_POST_QUERY } from '../../../graphql/queries'; -function PostItem({ variables }) { - return ( - <> - - Portal | {variables.slug} - - -