diff --git a/README.md b/README.md index c82d47fc..660378de 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Our pull request template will request that you fill out some key fields. It'll ### Code style -This repository uses eslint to enforce a consistent style for frontend JavaScript code. +This repository uses eslint to enforce a consistent style for frontend Typescript code. Before committing, run `dev/docker-lint.sh` (Mac/Linux) or `dev\docker-lint.bat` (Windows) to check for style errors and automatically fix formatting issues. (You will need to run `docker-compose up` or `docker-compose build` at least once before the docker-lint script will work.) @@ -113,6 +113,7 @@ The build steps are defined in `cloudbuild.yaml`. package managers offer similar performance, we were already using NPM for backend package management, and the Yarn roadmap did not offer compelling improvements going forward. +- **Typescript** - To enforce build time static typing and make our code more scalable. - **React** - Selected for popularity, simple view, and speedy virutal DOM. Code lives in the `/frontend` directory. It was built using [Create React App](https://facebook.github.io/create-react-app/docs/folder-structure). - **Material UI** - which we use over Bootstrap since MUI doesn't rely on jQuery. It has a @@ -122,7 +123,7 @@ popular React framework and looks great on mobile. - **React Hooks** - to manage interactions with state management. - **Functional Components** - We migrated away from ES6 React Components and toward React [Functional Components](https://reactjs.org/docs/components-and-props.html) due to the simpler component logic and the ability to use React Hooks that Functional Components offer. -- **ESLint** - Linting set in the format of AirBNB Style. +- **ESLint** - Linting set in the format of AirBNB Style + with Typescript additions. - **Prettier** - Code formatter to maintain standard code format for the frontend code. - **Husky** - Pre-commit hook to trigger Prettier auto formatting before pushing to Github. diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index c11fbca5..6787a7b0 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -1,11 +1,17 @@ { - "extends": ["airbnb", "prettier", "prettier/react"], + "extends": [ + "airbnb", + "prettier", + "prettier/react", + "plugin:@typescript-eslint/recommended", + "plugin:import/typescript" + ], "plugins": ["react", "prettier"], "env": { "browser": true, "jest": true }, - "parser": "babel-eslint", + "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": 9, "sourceType": "module", @@ -40,14 +46,35 @@ "react/destructuring-assignment": "off", "react/jsx-filename-extension": [ 1, - { "extensions": [".js", ".jsx"] } + { "extensions": [".js", ".jsx", ".ts", ".tsx"] } ], - "react/prop-types": "off" + "react/prop-types": "off", + "import/extensions": [ + "error", + "ignorePackages", + { + "js": "never", + "jsx": "never", + "ts": "never", + "tsx": "never" + } + ], + //remove in future + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-explicit-any":"off", + "@typescript-eslint/ban-ts-ignore": "off", + "react/no-children-prop": "off" }, "settings": { - "react": { - "pragma": "React", - "version": "16.8.6" - } + "react": { + "pragma": "React", + "version": "detect" + }, + "import/extensions": [ + ".js", ".jsx", ".ts", ".tsx" + ], + "import/resolver": { + "typescript": {} + } } } \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3adae244..9979c348 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -4476,11 +4476,26 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==" }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" }, + "@types/moment-timezone": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@types/moment-timezone/-/moment-timezone-0.5.13.tgz", + "integrity": "sha512-SWk1qM8DRssS5YR9L4eEX7WUhK/wc96aIr4nMa6p0kTk9YhGGOJjECVhIdPEj13fvJw72Xun69gScXSZ/UmcPg==", + "dev": true, + "requires": { + "moment": ">=2.14.0" + } + }, "@types/node": { "version": "12.12.31", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.31.tgz", @@ -4543,49 +4558,49 @@ "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" }, "@typescript-eslint/eslint-plugin": { - "version": "2.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.25.0.tgz", - "integrity": "sha512-W2YyMtjmlrOjtXc+FtTelVs9OhuR6OlYc4XKIslJ8PUJOqgYYAPRJhAqkYRQo3G4sjvG8jSodsNycEn4W2gHUw==", + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.28.0.tgz", + "integrity": "sha512-w0Ugcq2iatloEabQP56BRWJowliXUP5Wv6f9fKzjJmDW81hOTBxRoJ4LoEOxRpz9gcY51Libytd2ba3yLmSOfg==", "requires": { - "@typescript-eslint/experimental-utils": "2.25.0", + "@typescript-eslint/experimental-utils": "2.28.0", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.0.0", "tsutils": "^3.17.1" }, "dependencies": { "regexpp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz", - "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==" } } }, "@typescript-eslint/experimental-utils": { - "version": "2.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.25.0.tgz", - "integrity": "sha512-0IZ4ZR5QkFYbaJk+8eJ2kYeA+1tzOE1sBjbwwtSV85oNWYUBep+EyhlZ7DLUCyhMUGuJpcCCFL0fDtYAP1zMZw==", + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.28.0.tgz", + "integrity": "sha512-4SL9OWjvFbHumM/Zh/ZeEjUFxrYKtdCi7At4GyKTbQlrj1HcphIDXlje4Uu4cY+qzszR5NdVin4CCm6AXCjd6w==", "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "2.25.0", + "@typescript-eslint/typescript-estree": "2.28.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" } }, "@typescript-eslint/parser": { - "version": "2.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.25.0.tgz", - "integrity": "sha512-mccBLaBSpNVgp191CP5W+8U1crTyXsRziWliCqzj02kpxdjKMvFHGJbK33NroquH3zB/gZ8H511HEsJBa2fNEg==", + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.28.0.tgz", + "integrity": "sha512-RqPybRDquui9d+K86lL7iPqH6Dfp9461oyqvlXMNtap+PyqYbkY5dB7LawQjDzot99fqzvS0ZLZdfe+1Bt3Jgw==", "requires": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "2.25.0", - "@typescript-eslint/typescript-estree": "2.25.0", + "@typescript-eslint/experimental-utils": "2.28.0", + "@typescript-eslint/typescript-estree": "2.28.0", "eslint-visitor-keys": "^1.1.0" } }, "@typescript-eslint/typescript-estree": { - "version": "2.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.25.0.tgz", - "integrity": "sha512-VUksmx5lDxSi6GfmwSK7SSoIKSw9anukWWNitQPqt58LuYrKalzsgeuignbqnB+rK/xxGlSsCy8lYnwFfB6YJg==", + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.28.0.tgz", + "integrity": "sha512-HDr8MP9wfwkiuqzRVkuM3BeDrOC4cKbO5a6BymZBHUt5y/2pL0BXD6I/C/ceq2IZoHWhcASk+5/zo+dwgu9V8Q==", "requires": { "debug": "^4.1.1", "eslint-visitor-keys": "^1.1.0", @@ -5192,6 +5207,34 @@ "postcss-value-parser": "^4.0.3" } }, + "awesome-typescript-loader": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/awesome-typescript-loader/-/awesome-typescript-loader-5.2.1.tgz", + "integrity": "sha512-slv66OAJB8orL+UUaTI3pKlLorwIvS4ARZzYR9iJJyGsEgOqueMfOMdKySWzZ73vIkEe3fcwFgsKMg4d8zyb1g==", + "requires": { + "chalk": "^2.4.1", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^1.1.0", + "lodash": "^4.17.5", + "micromatch": "^3.1.9", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.3", + "webpack-log": "^1.2.0" + }, + "dependencies": { + "webpack-log": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-1.2.0.tgz", + "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==", + "requires": { + "chalk": "^2.1.0", + "log-symbols": "^2.1.0", + "loglevelnext": "^1.0.1", + "uuid": "^3.1.0" + } + } + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -6925,9 +6968,9 @@ } }, "core-js-pure": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz", - "integrity": "sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==" + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", + "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==" }, "core-util-is": { "version": "1.0.2", @@ -8500,6 +8543,39 @@ } } }, + "eslint-import-resolver-typescript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.0.0.tgz", + "integrity": "sha512-bT5Frpl8UWoHBtY25vKUOMoVIMlJQOMefHLyQ4Tz3MQpIZ2N6yYKEEIHMo38bszBNUuMBW6M3+5JNYxeiGFH4w==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "is-glob": "^4.0.1", + "resolve": "^1.12.0", + "tiny-glob": "^0.2.6", + "tsconfig-paths": "^3.9.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "resolve": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.16.1.tgz", + "integrity": "sha512-rmAglCSqWWMrrBv/XM6sW0NuRFiKViw/W4d9EbC4pt+49H8JwHy+mcGmALTEg504AUDcLTvb1T2q3E9AnmY+ig==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, "eslint-loader": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-2.2.1.tgz", @@ -8740,17 +8816,6 @@ "has": "^1.0.3" } }, - "object.fromentries": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", - "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, "object.values": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", @@ -8763,9 +8828,9 @@ } }, "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.16.1.tgz", + "integrity": "sha512-rmAglCSqWWMrrBv/XM6sW0NuRFiKViw/W4d9EbC4pt+49H8JwHy+mcGmALTEg504AUDcLTvb1T2q3E9AnmY+ig==", "requires": { "path-parse": "^1.0.6" } @@ -8774,14 +8839,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "xregexp": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz", - "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==", - "requires": { - "@babel/runtime-corejs3": "^7.8.3" - } } } }, @@ -9724,6 +9781,12 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, + "globalyzer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.4.tgz", + "integrity": "sha512-LeguVWaxgHN0MNbWC6YljNMzHkrCny9fzjmEUdnF1kQ7wATFD1RHFRqA1qxaX2tgxGENlcxjOflopBwj3YZiXA==", + "dev": true + }, "globby": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", @@ -9755,6 +9818,12 @@ } } }, + "globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", @@ -12660,7 +12729,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, "requires": { "chalk": "^2.0.1" } @@ -12693,6 +12761,15 @@ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==" }, + "loglevelnext": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/loglevelnext/-/loglevelnext-1.0.5.tgz", + "integrity": "sha512-V/73qkPuJmx4BcBF19xPBr+0ZRVBhc4POxvZTZdMeXpJ4NItXSJ/MSwuFT0kQJlCbXvdlZoQQ/418bS1y9Jh6A==", + "requires": { + "es6-symbol": "^3.1.1", + "object.assign": "^4.1.0" + } + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -13524,6 +13601,65 @@ "has": "^1.0.3" } }, + "object.fromentries": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", + "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "requires": { + "has": "^1.0.3" + } + } + } + }, "object.getownpropertydescriptors": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", @@ -17364,6 +17500,15 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, + "source-map-loader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", + "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", + "requires": { + "async": "^2.5.0", + "loader-utils": "^1.1.0" + } + }, "source-map-resolve": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", @@ -18240,6 +18385,16 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "tiny-glob": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.6.tgz", + "integrity": "sha512-A7ewMqPu1B5PWwC3m7KVgAu96Ch5LA0w4SnEN/LbDREj/gAD0nPWboRbn8YoP9ISZXqeNAlMvKSKoEuhcfK3Pw==", + "dev": true, + "requires": { + "globalyzer": "^0.1.0", + "globrex": "^0.1.1" + } + }, "tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", @@ -18365,6 +18520,37 @@ "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.6.tgz", "integrity": "sha512-CrG5GqAAzMT7144Cl+UIFP7mz/iIhiy+xQ6GGcnjTezhALT02uPMRw7tgDSESgB5MsfKt55+GPWw4ir1kVtMIQ==" }, + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } + }, + "tsconfig-paths-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-S/gOOPOkV8rIL4LurZ1vUdYCVgo15iX9ZMJ6wx6w2OgcpT/G4wMyHB6WM+xheSqGMrWKuxFul+aXpCju3wmj/g==", + "dev": true, + "requires": { + "chalk": "^2.3.0", + "enhanced-resolve": "^4.0.0", + "tsconfig-paths": "^3.4.0" + } + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", @@ -18444,6 +18630,12 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "dev": true + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -18847,6 +19039,8 @@ "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", "optional": true, "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1", "node-pre-gyp": "*" }, "dependencies": { @@ -20814,6 +21008,14 @@ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, + "xregexp": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz", + "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==", + "requires": { + "@babel/runtime-corejs3": "^7.8.3" + } + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 26ef5e58..76e65241 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ "@material-ui/lab": "^4.0.0-alpha.46", "@material-ui/styles": "^4.9.0", "@turf/turf": "^5.1.6", + "awesome-typescript-loader": "^5.2.1", "axios": "0.19.0", "clsx": "^1.1.0", "d3": "^5.15.0", @@ -31,14 +32,22 @@ "redux": "^4.0.5", "redux-first-router": "^2.1.5", "redux-first-router-link": "^2.1.1", - "redux-thunk": "^2.3.0" + "redux-thunk": "^2.3.0", + "source-map-loader": "^0.2.4" }, "devDependencies": { + "@types/moment-timezone": "^0.5.13", + "@types/node": "^13.13.0", + "@types/react": "^16.9.34", + "@types/react-dom": "^16.9.6", + "@typescript-eslint/eslint-plugin": "^2.28.0", + "@typescript-eslint/parser": "^2.28.0", "babel-eslint": "10.1.0", "eslint": "^6.8.0", "eslint-config-airbnb": "^17.1.1", "eslint-config-prettier": "^4.3.0", "eslint-config-react-app": "^3.0.6", + "eslint-import-resolver-typescript": "^2.0.0", "eslint-loader": "^2.2.1", "eslint-plugin-flowtype": "^3.13.0", "eslint-plugin-import": "^2.20.1", @@ -47,7 +56,9 @@ "eslint-plugin-react": "^7.19.0", "husky": "3.0.2", "lint-staged": "^8.2.1", - "prettier": "^1.19.1" + "prettier": "^1.19.1", + "tsconfig-paths-webpack-plugin": "^3.2.0", + "typescript": "^3.8.3" }, "scripts": { "start": "react-scripts start", @@ -55,9 +66,9 @@ "test": "react-scripts test", "eject": "react-scripts eject", "heroku-postbuild": "npm build", - "lint": "eslint src --fix --ext .jsx,.js", - "lint:check": "eslint src --ext .jsx,.js", - "lint:all": "eslint --fix --ext src/**/*.{js,jsx}" + "lint": "eslint src --fix --ext .jsx,.js,.tsx,.ts", + "lint:check": "eslint src --ext .jsx,.js,.tsx,.ts", + "lint:all": "eslint --fix --ext src/**/*.{js,jsx,ts,tsx}" }, "eslintConfig": { "extends": "react-app" @@ -68,9 +79,9 @@ } }, "lint-staged": { - "src/**/*.{js,jsx}": [ + "src/**/*.{js,jsx,ts,tsx}": [ "prettier --write", - "eslint --fix src/", + "eslint --fix", "git add" ] }, diff --git a/frontend/src/App.test.js b/frontend/src/App.test.tsx old mode 100755 new mode 100644 similarity index 100% rename from frontend/src/App.test.js rename to frontend/src/App.test.tsx diff --git a/frontend/src/App.jsx b/frontend/src/App.tsx old mode 100755 new mode 100644 similarity index 98% rename from frontend/src/App.jsx rename to frontend/src/App.tsx index efef5f24..2d2929b7 --- a/frontend/src/App.jsx +++ b/frontend/src/App.tsx @@ -37,7 +37,11 @@ const Screens = { const theme = createMuiTheme({ breakpoints: { values: { + xs: 0, + sm: 600, md: 1050, + lg: 1280, + xl: 1920, }, }, palette: { diff --git a/frontend/src/UIConstants.js b/frontend/src/UIConstants.ts similarity index 100% rename from frontend/src/UIConstants.js rename to frontend/src/UIConstants.ts diff --git a/frontend/src/actions/index.js b/frontend/src/actions/index.ts similarity index 99% rename from frontend/src/actions/index.js rename to frontend/src/actions/index.ts index 8a113f35..f63955e1 100644 --- a/frontend/src/actions/index.js +++ b/frontend/src/actions/index.ts @@ -222,7 +222,7 @@ query($agencyId:String!, $routeId:String!, const dates = computeDates(params.firstDateRange); return function(dispatch) { - const variables = { + const variables: { [key: string]: any } = { agencyId: params.agencyId, routeId: params.routeId, directionId: params.directionId, @@ -287,7 +287,7 @@ export function resetTripMetrics() { }; } -export function fetchRoutes() { +export function fetchRoutes(params) { return function(dispatch, getState) { const agencyId = Agencies[0].id; @@ -365,7 +365,7 @@ query($agencyId:String!, $routeId:String!, } }`.replace(/\s+/g, ' '); - const variables = { + const variables: { [key: string]: any } = { agencyId: Agencies[0].id, routeId: params.routeId, dates, diff --git a/frontend/src/components/AppBarLogo.jsx b/frontend/src/components/AppBarLogo.tsx similarity index 100% rename from frontend/src/components/AppBarLogo.jsx rename to frontend/src/components/AppBarLogo.tsx diff --git a/frontend/src/components/ControlPanel.jsx b/frontend/src/components/ControlPanel.tsx similarity index 96% rename from frontend/src/components/ControlPanel.jsx rename to frontend/src/components/ControlPanel.tsx index ada6933c..e5ccd39e 100644 --- a/frontend/src/components/ControlPanel.jsx +++ b/frontend/src/components/ControlPanel.tsx @@ -1,11 +1,10 @@ /* eslint-disable react/prop-types */ import React, { useState } from 'react'; import { connect } from 'react-redux'; -import { makeStyles } from '@material-ui/core/styles'; -import FormControl from '@material-ui/core/FormControl'; import Navlink from 'redux-first-router-link'; +import { makeStyles, FormControl } from '@material-ui/core'; import BackspaceIcon from '@material-ui/icons/Backspace'; -import { getDownstreamStopIds } from '../helpers/mapGeometry'; +import { getDownstreamStopIds } from 'helpers'; import ReactSelect from './ReactSelect'; import DateTimeRangeControls from './DateTimeRangeControls'; @@ -112,9 +111,9 @@ function ControlPanel(props) { const hoverStop = selectedRoute ? selectedRoute.stops[stopId] : null; - mapNode.style.setProperty( - '--stop-name', - `"${hoverStop ? hoverStop.title : ''}"`, + mapNode.setAttribute( + 'style', + `--stop-name: "${hoverStop ? hoverStop.title : ''}"`, ); } } @@ -155,7 +154,7 @@ function ControlPanel(props) { const directionStops = selectedDirection ? selectedDirection.stops : []; - const labelStyle = { whiteSpace: 'nowrap' }; + const labelStyle: { [key: string]: any } = { whiteSpace: 'nowrap' }; return (
diff --git a/frontend/src/components/DateRangeControl.jsx b/frontend/src/components/DateRangeControl.tsx similarity index 94% rename from frontend/src/components/DateRangeControl.jsx rename to frontend/src/components/DateRangeControl.tsx index aa79e227..2bb0c519 100644 --- a/frontend/src/components/DateRangeControl.jsx +++ b/frontend/src/components/DateRangeControl.tsx @@ -1,29 +1,31 @@ import React, { useState, useEffect } from 'react'; -import Moment from 'moment'; -import { makeStyles } from '@material-ui/core/styles'; -import Checkbox from '@material-ui/core/Checkbox'; -import Divider from '@material-ui/core/Divider'; -import Grid from '@material-ui/core/Grid'; -import Popover from '@material-ui/core/Popover'; -import Typography from '@material-ui/core/Typography'; -import Button from '@material-ui/core/Button'; import { connect } from 'react-redux'; +import Moment from 'moment'; +import { Theme, makeStyles } from '@material-ui/core/styles'; import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; - -import FormControl from '@material-ui/core/FormControl'; -import FormGroup from '@material-ui/core/FormGroup'; -import { List, ListItem, Snackbar } from '@material-ui/core'; -import TextField from '@material-ui/core/TextField'; -import FormControlLabel from '@material-ui/core/FormControlLabel'; -import IconButton from '@material-ui/core/IconButton'; import CloseIcon from '@material-ui/icons/Close'; - +import { + List, + ListItem, + Snackbar, + FormGroup, + FormControl, + TextField, + FormControlLabel, + IconButton, + Checkbox, + Divider, + Grid, + Popover, + Typography, + Button, +} from '@material-ui/core'; import { allFalse, allTrue, getDaysOfTheWeekLabel, renderDateString, -} from '../helpers/dateTime'; +} from 'helpers'; import { DATE_RANGES, @@ -35,7 +37,7 @@ import { initialGraphParams } from '../reducers'; import { fullQueryFromParams } from '../routesMap'; import { updateQuery } from '../actions'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme: Theme) => ({ button: { textTransform: 'none', borderRadius: '0px', @@ -58,9 +60,9 @@ const useStyles = makeStyles(theme => ({ // color: theme.palette.text.secondary, textAlign: 'left', }, - column: { - flexGrow: '1', - }, + // column: { + // flexGrow: '1', + // }, dateTime: { whiteSpace: 'nowrap', display: 'flex', @@ -253,7 +255,7 @@ function DateRangeControl(props) { const newMoment = Moment(newDate); const startMoment = Moment(dateRangeParams.startDate); - const payload = { + const payload: { [key: string]: any } = { date: newDate, }; @@ -284,7 +286,7 @@ function DateRangeControl(props) { }; const setDateRange = daysBack => { - const date = initialGraphParams.date; + const date = initialGraphParams.firstDateRange.date; const startMoment = Moment(date).subtract(daysBack - 1, 'days'); // include end date updateLocalDateRangeParams({ @@ -391,7 +393,7 @@ function DateRangeControl(props) { .format('YYYY-MM-DD'), }, }} - className={classes.textField} + // className={classes.textField} InputLabelProps={{ shrink: true, }} @@ -412,7 +414,7 @@ function DateRangeControl(props) { max: maxDate, }, }} - className={classes.textField} + // className={classes.textField} InputLabelProps={{ shrink: true, }} diff --git a/frontend/src/components/DateTimeRangeControls.jsx b/frontend/src/components/DateTimeRangeControls.tsx similarity index 95% rename from frontend/src/components/DateTimeRangeControls.jsx rename to frontend/src/components/DateTimeRangeControls.tsx index 58d69781..9ad2338f 100644 --- a/frontend/src/components/DateTimeRangeControls.jsx +++ b/frontend/src/components/DateTimeRangeControls.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import FormControl from '@material-ui/core/FormControl'; -import Tooltip from '@material-ui/core/Tooltip'; -import InputLabel from '@material-ui/core/InputLabel'; +import { FormControl, Tooltip, InputLabel } from '@material-ui/core'; import Navlink from 'redux-first-router-link'; import BackspaceIcon from '@material-ui/icons/Backspace'; import AddCircleIcon from '@material-ui/icons/AddCircle'; diff --git a/frontend/src/components/LoadingIndicator.jsx b/frontend/src/components/LoadingIndicator.tsx similarity index 84% rename from frontend/src/components/LoadingIndicator.jsx rename to frontend/src/components/LoadingIndicator.tsx index e75fe051..de557ffa 100644 --- a/frontend/src/components/LoadingIndicator.jsx +++ b/frontend/src/components/LoadingIndicator.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import Box from '@material-ui/core/Box'; -import CircularProgress from '@material-ui/core/CircularProgress'; +import { Box, CircularProgress } from '@material-ui/core'; import { isLoadingRequest } from '../reducers/loadingReducer'; /* diff --git a/frontend/src/components/MapShield.jsx b/frontend/src/components/MapShield.tsx similarity index 100% rename from frontend/src/components/MapShield.jsx rename to frontend/src/components/MapShield.tsx diff --git a/frontend/src/components/MapSpider.jsx b/frontend/src/components/MapSpider.tsx similarity index 96% rename from frontend/src/components/MapSpider.jsx rename to frontend/src/components/MapSpider.tsx index 0e5d2a08..68a140fa 100644 --- a/frontend/src/components/MapSpider.jsx +++ b/frontend/src/components/MapSpider.tsx @@ -1,5 +1,5 @@ import React, { Component, createRef, Fragment } from 'react'; -import Button from '@material-ui/core/Button'; +import { Button, Snackbar } from '@material-ui/core'; import GpsIcon from '@material-ui/icons/GpsFixed'; import { connect } from 'react-redux'; import { @@ -12,22 +12,18 @@ import { } from 'react-leaflet'; import L from 'leaflet'; import Control from 'react-leaflet-control'; -import { Snackbar } from '@material-ui/core'; import { getDownstreamStopIds, getTripPoints, isInServiceArea, -} from '../helpers/mapGeometry'; -import { filterRoutes, milesBetween, getRouteColor, -} from '../helpers/routeCalculations'; +} from 'helpers'; +import MapShield from './MapShield'; import { handleSpiderMapClick } from '../actions'; import { Agencies } from '../config'; -import MapShield from './MapShield'; - const CLICK_RADIUS_MI = 0.5; // maximum radius for stops near a point // Displays alert when an invalid location is set @@ -40,8 +36,24 @@ function ValidLocationAlert(props) { ); } -class MapSpider extends Component { - constructor(props) { +interface Props { + [key: string]: any; +} + +interface State { + [key: string]: any; +} + +class MapSpider extends Component { + agency: any; + + routeLayers: any[]; + + mapRef: any; + + boundUpdate: any; + + constructor(props: Props) { super(props); // for now, only supports 1 agency at a time. @@ -272,14 +284,16 @@ class MapSpider extends Component { return true; }} onFocus={e => { - this.onMouseOver(e); + e.target.setStyle({ opacity: 1, weight: computedWeight + 4 }); + return true; }} onMouseOut={e => { e.target.setStyle({ opacity: 0.5, weight: computedWeight }); return true; }} onBlur={e => { - this.onMouseOut(e); + e.target.setStyle({ opacity: 0.5, weight: computedWeight }); + return true; }} // when this route segment is clicked, plot only the stops for this route/dir by setting the first stop onClick={e => { diff --git a/frontend/src/components/MapStops.jsx b/frontend/src/components/MapStops.tsx similarity index 98% rename from frontend/src/components/MapStops.jsx rename to frontend/src/components/MapStops.tsx index 91e6c89b..33353e5e 100644 --- a/frontend/src/components/MapStops.jsx +++ b/frontend/src/components/MapStops.tsx @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { Map, TileLayer, Marker, Tooltip, Polyline } from 'react-leaflet'; +import ReactDOMServer from 'react-dom/server'; import * as d3 from 'd3'; import L from 'leaflet'; import Control from 'react-leaflet-control'; @@ -8,13 +9,24 @@ import StartStopIcon from '@material-ui/icons/DirectionsTransit'; import EndStopIcon from '@material-ui/icons/Flag'; import { withTheme } from '@material-ui/core/styles'; import { ThemeProvider } from '@material-ui/styles'; -import ReactDOMServer from 'react-dom/server'; +import { getTripPoints, getDistanceInMiles } from 'helpers'; import { handleGraphParams } from '../actions'; -import { getTripPoints, getDistanceInMiles } from '../helpers/mapGeometry'; import { Agencies } from '../config'; -class MapStops extends Component { - constructor(props) { +interface Props { + [key: string]: any; +} + +interface State { + [key: string]: any; +} + +class MapStops extends Component { + agency: any; + + boundUpdate: any; + + constructor(props: Props) { super(props); this.agency = Agencies[0]; diff --git a/frontend/src/components/MareyChart.jsx b/frontend/src/components/MareyChart.tsx similarity index 95% rename from frontend/src/components/MareyChart.jsx rename to frontend/src/components/MareyChart.tsx index 36284a92..842b6320 100644 --- a/frontend/src/components/MareyChart.jsx +++ b/frontend/src/components/MareyChart.tsx @@ -3,7 +3,7 @@ /* Note: Importing MomentTZ adds new methods to Moment. MomentTZ is not meant to be used directly. */ import React, { Fragment, useState, useEffect } from 'react'; - +import { connect } from 'react-redux'; import { XYPlot, HorizontalGridLines, @@ -16,20 +16,19 @@ import { Borders, } from 'react-vis'; import '../../node_modules/react-vis/dist/style.css'; - -import { connect } from 'react-redux'; - import { Radio, FormControl, FormControlLabel } from '@material-ui/core'; - import Moment from 'moment'; +// eslint-disable-next-line import MomentTZ from 'moment-timezone/builds/moment-timezone-with-data-10-year-range'; // this augments Moment - import * as d3 from 'd3'; +import { metersToMiles } from 'helpers'; import { fetchArrivals } from '../actions'; import { getAgency } from '../config'; import { DWELL_THRESHOLD_SECS } from '../UIConstants'; -import { metersToMiles } from '../helpers/routeCalculations'; +interface Props { + [key: string]: any; +} /** * Within state.arrivals.data, the data is organized as follows: @@ -56,7 +55,7 @@ import { metersToMiles } from '../helpers/routeCalculations'; * * @param {Object} props */ -function MareyChart(props) { +const MareyChart: React.FC = (props: Props) => { const INBOUND_AND_OUTBOUND = 'Inbound_and_outbound'; const INBOUND = '1'; // same as directionInfo id const OUTBOUND = '0'; // same as directionInfo id @@ -64,10 +63,12 @@ function MareyChart(props) { const { graphParams, arrivals, arrivalsErr, routes, hidden } = props; const myFetchArrivals = props.fetchArrivals; - const [hintValue, setHintValue] = useState(); - const [tripHighlight, setTripHighlight] = useState(); - const [processedArrivals, setProcessedArrivals] = useState(); // where the tripData gets stored - const [selectedOption, setSelectedOption] = useState(INBOUND_AND_OUTBOUND); + const [hintValue, setHintValue] = useState(); + const [tripHighlight, setTripHighlight] = useState(); + const [processedArrivals, setProcessedArrivals] = useState(); // where the tripData gets stored + const [selectedOption, setSelectedOption] = useState( + INBOUND_AND_OUTBOUND, + ); const agency = getAgency(graphParams.agencyId); const timezoneId = agency ? agency.timezoneId : 'UTC'; @@ -137,7 +138,7 @@ function MareyChart(props) { } distance = metersToMiles(distance); - const arrivalMoment = Moment.unix(arrival.t).tz(timezoneId); + const arrivalMoment = Moment.unix(arrival.t).utcOffset(timezoneId); const yValue = (arrival.t - startTime) / 60 / 60 + startHourOfDay; // time of arrival in fractional hours myTripData.byTripId[tripId].series.push({ @@ -167,7 +168,7 @@ function MareyChart(props) { // so we can see the vehicle's exit in the data series. if (arrival.e - arrival.t > DWELL_THRESHOLD_SECS) { - const exitMoment = Moment.unix(arrival.e).tz(timezoneId); + const exitMoment = Moment.unix(arrival.e).utcOffset(timezoneId); const exitYValue = (arrival.e - startTime) / 60 / 60 + startHourOfDay; // time of arrival in fractional hours myTripData.byTripId[tripId].series.push({ @@ -201,9 +202,8 @@ function MareyChart(props) { const stops = myArrivals.stops; const startTime = myArrivals.start_time; const startHourOfDay = Moment.unix(startTime) - .tz(timezoneId) + .utcOffset(timezoneId) .hour(); - const routeId = myArrivals.route_id; const route = myRoutes.find(myRoute => myRoute.id === routeId); @@ -373,7 +373,7 @@ function MareyChart(props) { control={ @@ -388,7 +388,7 @@ function MareyChart(props) { control={ @@ -403,7 +403,7 @@ function MareyChart(props) { control={ @@ -477,7 +477,7 @@ function MareyChart(props) { ) : ( {arrivalsErr || 'Loading...'} ); -} +}; const mapStateToProps = state => ({ routes: state.routes.data, diff --git a/frontend/src/components/OnTimePerformanceByDayChart.jsx b/frontend/src/components/OnTimePerformanceByDayChart.tsx similarity index 97% rename from frontend/src/components/OnTimePerformanceByDayChart.jsx rename to frontend/src/components/OnTimePerformanceByDayChart.tsx index 92925ad9..c72e342e 100644 --- a/frontend/src/components/OnTimePerformanceByDayChart.jsx +++ b/frontend/src/components/OnTimePerformanceByDayChart.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; import Moment from 'moment'; -import { getOnTimePercent } from '../helpers/graphData'; +import { getOnTimePercent } from 'helpers'; import SimpleLineMarkChart from './SimpleLineMarkChart'; import { CHART_COLORS } from '../UIConstants'; diff --git a/frontend/src/components/OnTimePerformanceByTimeChart.jsx b/frontend/src/components/OnTimePerformanceByTimeChart.tsx similarity index 95% rename from frontend/src/components/OnTimePerformanceByTimeChart.jsx rename to frontend/src/components/OnTimePerformanceByTimeChart.tsx index edb8c9a9..af2ca10b 100644 --- a/frontend/src/components/OnTimePerformanceByTimeChart.jsx +++ b/frontend/src/components/OnTimePerformanceByTimeChart.tsx @@ -1,7 +1,10 @@ import React from 'react'; import { connect } from 'react-redux'; -import { getOnTimePercent } from '../helpers/graphData'; -import { renderDateRange, getTimeRangeShortLabel } from '../helpers/dateTime'; +import { + getOnTimePercent, + renderDateRange, + getTimeRangeShortLabel, +} from 'helpers'; import SimpleVerticalBarChart from './SimpleVerticalBarChart'; import { CHART_COLORS } from '../UIConstants'; diff --git a/frontend/src/components/OnTimePerformanceHistogram.jsx b/frontend/src/components/OnTimePerformanceHistogram.tsx similarity index 95% rename from frontend/src/components/OnTimePerformanceHistogram.jsx rename to frontend/src/components/OnTimePerformanceHistogram.tsx index 67543c33..cd7dbc09 100644 --- a/frontend/src/components/OnTimePerformanceHistogram.jsx +++ b/frontend/src/components/OnTimePerformanceHistogram.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { getHistogramChartData } from '../helpers/graphData'; -import { renderDateRange } from '../helpers/dateTime'; +import { getHistogramChartData, renderDateRange } from 'helpers'; import SimpleVerticalRectChart from './SimpleVerticalRectChart'; import { CHART_COLORS } from '../UIConstants'; diff --git a/frontend/src/components/OnTimePerformanceStats.jsx b/frontend/src/components/OnTimePerformanceStats.tsx similarity index 100% rename from frontend/src/components/OnTimePerformanceStats.jsx rename to frontend/src/components/OnTimePerformanceStats.tsx diff --git a/frontend/src/components/QuadrantChart.jsx b/frontend/src/components/QuadrantChart.tsx similarity index 99% rename from frontend/src/components/QuadrantChart.jsx rename to frontend/src/components/QuadrantChart.tsx index f944ae64..42028e70 100644 --- a/frontend/src/components/QuadrantChart.jsx +++ b/frontend/src/components/QuadrantChart.tsx @@ -1,5 +1,4 @@ import React from 'react'; - import { connect } from 'react-redux'; import { XYPlot, diff --git a/frontend/src/components/ReactSelect.jsx b/frontend/src/components/ReactSelect.tsx similarity index 95% rename from frontend/src/components/ReactSelect.jsx rename to frontend/src/components/ReactSelect.tsx index 1c5a2c26..c118bb97 100644 --- a/frontend/src/components/ReactSelect.jsx +++ b/frontend/src/components/ReactSelect.tsx @@ -1,11 +1,7 @@ import React, { useState, useEffect, useRef } from 'react'; import Select, { components } from 'react-select'; import { makeStyles, createMuiTheme } from '@material-ui/core/styles'; -import Paper from '@material-ui/core/Paper'; -import Fade from '@material-ui/core/Fade'; -import Grow from '@material-ui/core/Grow'; -import TextField from '@material-ui/core/TextField'; -import MenuItem from '@material-ui/core/MenuItem'; +import { Paper, Fade, Grow, TextField, MenuItem } from '@material-ui/core'; import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; const transitionDuration = 350; @@ -13,7 +9,7 @@ const scrollHandlerDelay = 30; const theme = createMuiTheme({ palette: { background: { - focus: 'rgba(0, 0, 0, 0.05)', + // focus: 'rgba(0, 0, 0, 0.05)', }, }, }); @@ -21,7 +17,7 @@ const useStyles = makeStyles({ input: { display: 'flex', '&:focus': { - backgroundColor: theme.palette.background.focus, + // backgroundColor: theme.palette.background.focus, }, }, selectInput: { @@ -94,14 +90,16 @@ function Control(props) { ref={textRef} fullWidth InputProps={{ - inputComponent: 'div', - inputProps: { - children, - ...innerProps, - className: classes.input, - tabIndex: 0, - onKeyUp: handleTextKeyUp(props), - }, + inputComponent: () => ( +
+ ), }} label={textFieldProps.label} InputLabelProps={inputLabelProps} @@ -214,7 +212,7 @@ function Menu(props) { textFieldDOMRect, }, } = props; - const menuStyle = {}; + const menuStyle: { [key: string]: any } = {}; const timeout = menuTransition.current ? transitionDuration : 0; const [menuStyleRight, setMenuStyleRight] = useState(0); const [menuStyleBottom, setMenuStyleBottom] = useState(0); @@ -350,7 +348,7 @@ function Option(props) { data: { value }, selectProps: { onItemMouseOver, onItemMouseOut }, } = props; - const focusedStyle = {}; + const focusedStyle: { [key: string]: any } = {}; if (isFocused) { if (onItemMouseOver) { @@ -401,7 +399,9 @@ function handleMenuClose( return () => { allowTransition.current = true; - document.activeElement.blur(); + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } setMenuIsOpenTransition(false); setTimeout(() => setMenuIsOpen(false), transitionDuration); diff --git a/frontend/src/components/RouteSummary.jsx b/frontend/src/components/RouteSummary.tsx similarity index 98% rename from frontend/src/components/RouteSummary.jsx rename to frontend/src/components/RouteSummary.tsx index 7904dc78..f1983793 100644 --- a/frontend/src/components/RouteSummary.jsx +++ b/frontend/src/components/RouteSummary.tsx @@ -1,10 +1,9 @@ import React from 'react'; import { connect } from 'react-redux'; import { Table, TableBody, TableHead } from '@material-ui/core'; +import { metersToMiles, renderDateRange } from 'helpers'; import SummaryRow from './SummaryRow'; import SummaryHeaderRow from './SummaryHeaderRow'; -import { metersToMiles } from '../helpers/routeCalculations'; -import { renderDateRange } from '../helpers/dateTime'; /* * Renders the Summary tab on the RouteScreen when a route and optional direction are selected, diff --git a/frontend/src/components/RouteTable.jsx b/frontend/src/components/RouteTable.tsx similarity index 94% rename from frontend/src/components/RouteTable.jsx rename to frontend/src/components/RouteTable.tsx index 1cf3282b..e185f66b 100644 --- a/frontend/src/components/RouteTable.jsx +++ b/frontend/src/components/RouteTable.tsx @@ -1,19 +1,20 @@ import React, { useState, Fragment } from 'react'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { lighten, makeStyles, useTheme } from '@material-ui/core/styles'; -import Popover from '@material-ui/core/Popover'; -import Table from '@material-ui/core/Table'; -import TableBody from '@material-ui/core/TableBody'; -import TableCell from '@material-ui/core/TableCell'; -import TableHead from '@material-ui/core/TableHead'; -import TableRow from '@material-ui/core/TableRow'; -import TableSortLabel from '@material-ui/core/TableSortLabel'; -import IconButton from '@material-ui/core/IconButton'; - +import { + Popover, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + TableSortLabel, + IconButton, +} from '@material-ui/core'; import InfoIcon from '@material-ui/icons/InfoOutlined'; -import { connect } from 'react-redux'; import Navlink from 'redux-first-router-link'; -import { filterRoutes } from '../helpers/routeCalculations'; +import { filterRoutes } from 'helpers'; import DateTimeRangeControls from './DateTimeRangeControls'; function getComparisonFunction(order, orderBy) { @@ -77,7 +78,11 @@ function stableSort(array, sortOrder, orderBy) { return stabilizedThis.map(el => el[0]); } -function EnhancedTableHead(props) { +interface Props { + [key: string]: any; +} + +const EnhancedTableHead: React.FC = (props: Props) => { const { order, orderBy, onRequestSort, columns } = props; const createSortHandler = property => event => { onRequestSort(event, property); @@ -106,7 +111,7 @@ function EnhancedTableHead(props) { ); -} +}; EnhancedTableHead.propTypes = { onRequestSort: PropTypes.func.isRequired, @@ -367,7 +372,7 @@ function RouteTable(props) { order={order} orderBy={orderBy} onRequestSort={handleRequestSort} - rowCount={displayedRouteStats.length} + // rowCount={displayedRouteStats.length} columns={columns} /> diff --git a/frontend/src/components/ServiceFrequencyByDayChart.jsx b/frontend/src/components/ServiceFrequencyByDayChart.tsx similarity index 100% rename from frontend/src/components/ServiceFrequencyByDayChart.jsx rename to frontend/src/components/ServiceFrequencyByDayChart.tsx index 5d84e73e..5be7f403 100644 --- a/frontend/src/components/ServiceFrequencyByDayChart.jsx +++ b/frontend/src/components/ServiceFrequencyByDayChart.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { connect } from 'react-redux'; import Moment from 'moment'; -import { CHART_COLORS } from '../UIConstants'; import SimpleLineMarkChart from './SimpleLineMarkChart'; +import { CHART_COLORS } from '../UIConstants'; function getMedianServiceFrequencyByDayChartData(byDayData, property) { return byDayData diff --git a/frontend/src/components/ServiceFrequencyByTimeChart.jsx b/frontend/src/components/ServiceFrequencyByTimeChart.tsx similarity index 97% rename from frontend/src/components/ServiceFrequencyByTimeChart.jsx rename to frontend/src/components/ServiceFrequencyByTimeChart.tsx index 0a8e2451..1b29224e 100644 --- a/frontend/src/components/ServiceFrequencyByTimeChart.jsx +++ b/frontend/src/components/ServiceFrequencyByTimeChart.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { connect } from 'react-redux'; -import { CHART_COLORS } from '../UIConstants'; -import { renderDateRange, getTimeRangeShortLabel } from '../helpers/dateTime'; +import { renderDateRange, getTimeRangeShortLabel } from 'helpers'; import SimpleVerticalBarChart from './SimpleVerticalBarChart'; +import { CHART_COLORS } from '../UIConstants'; function getMedianServiceFrequencyByTimeRangeChartData( timeRangesData, diff --git a/frontend/src/components/ServiceFrequencyGapHistogram.jsx b/frontend/src/components/ServiceFrequencyGapHistogram.tsx similarity index 95% rename from frontend/src/components/ServiceFrequencyGapHistogram.jsx rename to frontend/src/components/ServiceFrequencyGapHistogram.tsx index 5352f027..bc57da47 100644 --- a/frontend/src/components/ServiceFrequencyGapHistogram.jsx +++ b/frontend/src/components/ServiceFrequencyGapHistogram.tsx @@ -1,9 +1,8 @@ import React from 'react'; import { connect } from 'react-redux'; -import { CHART_COLORS } from '../UIConstants'; -import { renderDateRange } from '../helpers/dateTime'; -import { getHistogramChartData } from '../helpers/graphData'; +import { renderDateRange, getHistogramChartData } from 'helpers'; import SimpleVerticalRectChart from './SimpleVerticalRectChart'; +import { CHART_COLORS } from '../UIConstants'; function getGapHistogramChartData(headwayScheduleDeltas) { return getHistogramChartData( diff --git a/frontend/src/components/ServiceFrequencyHistogram.jsx b/frontend/src/components/ServiceFrequencyHistogram.tsx similarity index 95% rename from frontend/src/components/ServiceFrequencyHistogram.jsx rename to frontend/src/components/ServiceFrequencyHistogram.tsx index 10ccd711..a102db22 100644 --- a/frontend/src/components/ServiceFrequencyHistogram.jsx +++ b/frontend/src/components/ServiceFrequencyHistogram.tsx @@ -1,9 +1,8 @@ import React from 'react'; import { connect } from 'react-redux'; -import { CHART_COLORS } from '../UIConstants'; -import { renderDateRange } from '../helpers/dateTime'; -import { getHistogramChartData } from '../helpers/graphData'; +import { renderDateRange, getHistogramChartData } from 'helpers'; import SimpleVerticalRectChart from './SimpleVerticalRectChart'; +import { CHART_COLORS } from '../UIConstants'; function getHeadwaysHistogramChartData(headways) { return getHistogramChartData(headways.histogram, headways.count); diff --git a/frontend/src/components/ServiceFrequencyStats.jsx b/frontend/src/components/ServiceFrequencyStats.tsx similarity index 100% rename from frontend/src/components/ServiceFrequencyStats.jsx rename to frontend/src/components/ServiceFrequencyStats.tsx diff --git a/frontend/src/components/SimpleChart.jsx b/frontend/src/components/SimpleChart.tsx similarity index 99% rename from frontend/src/components/SimpleChart.jsx rename to frontend/src/components/SimpleChart.tsx index 3af3596d..091cc466 100644 --- a/frontend/src/components/SimpleChart.jsx +++ b/frontend/src/components/SimpleChart.tsx @@ -11,7 +11,7 @@ import { } from 'react-vis'; import DiscreteColorLegend from 'react-vis/dist/legends/discrete-color-legend'; -function getDefaultYDomain(series) { +function getDefaultYDomain(series, stackBy) { let yMin = 0; let yMax = 0; diff --git a/frontend/src/components/SimpleLineMarkChart.jsx b/frontend/src/components/SimpleLineMarkChart.tsx similarity index 99% rename from frontend/src/components/SimpleLineMarkChart.jsx rename to frontend/src/components/SimpleLineMarkChart.tsx index ea48fd57..b960b77a 100644 --- a/frontend/src/components/SimpleLineMarkChart.jsx +++ b/frontend/src/components/SimpleLineMarkChart.tsx @@ -1,5 +1,4 @@ import React from 'react'; - import { LineMarkSeries } from 'react-vis'; import SimpleChart from './SimpleChart'; diff --git a/frontend/src/components/SimpleVerticalBarChart.jsx b/frontend/src/components/SimpleVerticalBarChart.tsx similarity index 99% rename from frontend/src/components/SimpleVerticalBarChart.jsx rename to frontend/src/components/SimpleVerticalBarChart.tsx index 6f22292f..25fe622c 100644 --- a/frontend/src/components/SimpleVerticalBarChart.jsx +++ b/frontend/src/components/SimpleVerticalBarChart.tsx @@ -1,5 +1,4 @@ import React from 'react'; - import { VerticalBarSeries } from 'react-vis'; import SimpleChart from './SimpleChart'; diff --git a/frontend/src/components/SimpleVerticalRectChart.jsx b/frontend/src/components/SimpleVerticalRectChart.tsx similarity index 99% rename from frontend/src/components/SimpleVerticalRectChart.jsx rename to frontend/src/components/SimpleVerticalRectChart.tsx index 8753f80f..78f478f7 100644 --- a/frontend/src/components/SimpleVerticalRectChart.jsx +++ b/frontend/src/components/SimpleVerticalRectChart.tsx @@ -1,5 +1,4 @@ import React from 'react'; - import { VerticalRectSeries } from 'react-vis'; import SimpleChart from './SimpleChart'; diff --git a/frontend/src/components/SingleDateControl.jsx b/frontend/src/components/SingleDateControl.tsx similarity index 99% rename from frontend/src/components/SingleDateControl.jsx rename to frontend/src/components/SingleDateControl.tsx index 7ef354fa..1075c439 100644 --- a/frontend/src/components/SingleDateControl.jsx +++ b/frontend/src/components/SingleDateControl.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; - import { TextField } from '@material-ui/core'; - import { updateQuery } from '../actions'; import { dateQueryFromDateRangeParams } from '../routesMap'; diff --git a/frontend/src/components/SummaryHeaderRow.jsx b/frontend/src/components/SummaryHeaderRow.tsx similarity index 99% rename from frontend/src/components/SummaryHeaderRow.jsx rename to frontend/src/components/SummaryHeaderRow.tsx index a69c66b1..54e45aa9 100644 --- a/frontend/src/components/SummaryHeaderRow.jsx +++ b/frontend/src/components/SummaryHeaderRow.tsx @@ -1,5 +1,4 @@ import React from 'react'; - import { TableCell, TableRow } from '@material-ui/core'; /* diff --git a/frontend/src/components/SummaryRow.jsx b/frontend/src/components/SummaryRow.tsx similarity index 97% rename from frontend/src/components/SummaryRow.jsx rename to frontend/src/components/SummaryRow.tsx index 9c27e0db..f06a4d13 100644 --- a/frontend/src/components/SummaryRow.jsx +++ b/frontend/src/components/SummaryRow.tsx @@ -1,10 +1,6 @@ import React, { useState } from 'react'; - -import { TableCell, TableRow } from '@material-ui/core'; - +import { TableCell, TableRow, IconButton, Popover } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import IconButton from '@material-ui/core/IconButton'; -import Popover from '@material-ui/core/Popover'; import InfoIcon from '@material-ui/icons/InfoOutlined'; /* diff --git a/frontend/src/components/SummaryStats.jsx b/frontend/src/components/SummaryStats.tsx similarity index 99% rename from frontend/src/components/SummaryStats.jsx rename to frontend/src/components/SummaryStats.tsx index a2cf9fe6..546ffa63 100644 --- a/frontend/src/components/SummaryStats.jsx +++ b/frontend/src/components/SummaryStats.tsx @@ -1,7 +1,5 @@ import React from 'react'; - import { connect } from 'react-redux'; - import RouteSummary from './RouteSummary'; import TripSummary from './TripSummary'; diff --git a/frontend/src/components/TimeRangeControl.jsx b/frontend/src/components/TimeRangeControl.tsx similarity index 96% rename from frontend/src/components/TimeRangeControl.jsx rename to frontend/src/components/TimeRangeControl.tsx index a0cf1620..8303af72 100644 --- a/frontend/src/components/TimeRangeControl.jsx +++ b/frontend/src/components/TimeRangeControl.tsx @@ -1,9 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; - -import MenuItem from '@material-ui/core/MenuItem'; -import Select from '@material-ui/core/Select'; - +import { MenuItem, Select } from '@material-ui/core'; import { TIME_RANGES, TIME_RANGE_ALL_DAY } from '../UIConstants'; import { dateQueryFromDateRangeParams } from '../routesMap'; import { updateQuery } from '../actions'; diff --git a/frontend/src/components/TravelTimeChart.jsx b/frontend/src/components/TravelTimeChart.tsx similarity index 98% rename from frontend/src/components/TravelTimeChart.jsx rename to frontend/src/components/TravelTimeChart.tsx index 0dee36e6..67428dd3 100644 --- a/frontend/src/components/TravelTimeChart.jsx +++ b/frontend/src/components/TravelTimeChart.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; - +import { connect } from 'react-redux'; import { XYPlot, HorizontalGridLines, @@ -13,11 +13,8 @@ import { } from 'react-vis'; import DiscreteColorLegend from 'react-vis/dist/legends/discrete-color-legend'; import '../../node_modules/react-vis/dist/style.css'; - -import { connect } from 'react-redux'; - -import Typography from '@material-ui/core/Typography'; -import { metersToMiles } from '../helpers/routeCalculations'; +import { Typography } from '@material-ui/core'; +import { metersToMiles } from 'helpers'; /** * Returns an array of {x: stop index, y: time} objects for diff --git a/frontend/src/components/TravelTimesHistogram.jsx b/frontend/src/components/TravelTimesHistogram.tsx similarity index 95% rename from frontend/src/components/TravelTimesHistogram.jsx rename to frontend/src/components/TravelTimesHistogram.tsx index e2c13522..164c1eaf 100644 --- a/frontend/src/components/TravelTimesHistogram.jsx +++ b/frontend/src/components/TravelTimesHistogram.tsx @@ -1,9 +1,8 @@ import React from 'react'; import { connect } from 'react-redux'; -import { CHART_COLORS } from '../UIConstants'; -import { renderDateRange } from '../helpers/dateTime'; -import { getHistogramChartData } from '../helpers/graphData'; +import { renderDateRange, getHistogramChartData } from 'helpers'; import SimpleVerticalRectChart from './SimpleVerticalRectChart'; +import { CHART_COLORS } from '../UIConstants'; function getTravelTimesHistogramChartData(tripTimes) { return getHistogramChartData(tripTimes.histogram, tripTimes.count); diff --git a/frontend/src/components/TripSummary.jsx b/frontend/src/components/TripSummary.tsx similarity index 98% rename from frontend/src/components/TripSummary.jsx rename to frontend/src/components/TripSummary.tsx index fcbb8116..4fef700c 100644 --- a/frontend/src/components/TripSummary.jsx +++ b/frontend/src/components/TripSummary.tsx @@ -1,8 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { Table, TableBody, TableHead } from '@material-ui/core'; -import { getDistanceInMiles, getTripStops } from '../helpers/mapGeometry'; -import { renderDateRange } from '../helpers/dateTime'; +import { getDistanceInMiles, getTripStops, renderDateRange } from 'helpers'; import SummaryRow from './SummaryRow'; import SummaryHeaderRow from './SummaryHeaderRow'; diff --git a/frontend/src/components/TripTimesByDayChart.jsx b/frontend/src/components/TripTimesByDayChart.tsx similarity index 97% rename from frontend/src/components/TripTimesByDayChart.jsx rename to frontend/src/components/TripTimesByDayChart.tsx index 2d7ed4a7..6df5639a 100644 --- a/frontend/src/components/TripTimesByDayChart.jsx +++ b/frontend/src/components/TripTimesByDayChart.tsx @@ -1,15 +1,15 @@ import React, { useState } from 'react'; +import { connect } from 'react-redux'; import { FormControl, FormControlLabel, Radio, Typography, } from '@material-ui/core'; -import { connect } from 'react-redux'; import Moment from 'moment'; -import { CHART_COLORS, PLANNING_PERCENTILE } from '../UIConstants'; -import { getPercentileValue } from '../helpers/graphData'; +import { getPercentileValue } from 'helpers'; import SimpleVerticalBarChart from './SimpleVerticalBarChart'; +import { CHART_COLORS, PLANNING_PERCENTILE } from '../UIConstants'; import '../../node_modules/react-vis/dist/style.css'; const AVERAGE_TIME = 'average_time'; @@ -99,7 +99,7 @@ function TripTimesByDayChart(props) { control={ diff --git a/frontend/src/reducers/index.js b/frontend/src/reducers/index.ts similarity index 100% rename from frontend/src/reducers/index.js rename to frontend/src/reducers/index.ts diff --git a/frontend/src/reducers/loadingReducer.js b/frontend/src/reducers/loadingReducer.ts similarity index 100% rename from frontend/src/reducers/loadingReducer.js rename to frontend/src/reducers/loadingReducer.ts diff --git a/frontend/src/reducers/page.js b/frontend/src/reducers/page.ts similarity index 91% rename from frontend/src/reducers/page.js rename to frontend/src/reducers/page.ts index 0bceaa2d..ea120e08 100644 --- a/frontend/src/reducers/page.js +++ b/frontend/src/reducers/page.ts @@ -26,5 +26,5 @@ export function typeForPage(page) { return currentType; } -export default (state = 'DASHBOARD', action = {}) => +export default (state = 'DASHBOARD', action: { type: string }) => components[action.type] || state; diff --git a/frontend/src/reducers/titleReducer.js b/frontend/src/reducers/titleReducer.ts similarity index 88% rename from frontend/src/reducers/titleReducer.js rename to frontend/src/reducers/titleReducer.ts index 66236204..afcff206 100644 --- a/frontend/src/reducers/titleReducer.js +++ b/frontend/src/reducers/titleReducer.ts @@ -2,7 +2,7 @@ import { APP_NAME } from '../UIConstants'; const DEFAULT = APP_NAME; -export default (state = DEFAULT, action = {}) => { +export default (state = DEFAULT, action: { type: string }) => { switch (action.type) { case 'DASHBOARD': return `Dashboard | ${DEFAULT}`; diff --git a/frontend/src/routesMap.js b/frontend/src/routesMap.ts similarity index 93% rename from frontend/src/routesMap.js rename to frontend/src/routesMap.ts index 8bc7fdd4..f5fddcb8 100644 --- a/frontend/src/routesMap.js +++ b/frontend/src/routesMap.ts @@ -7,6 +7,13 @@ export const START_DATE = 'startDate'; export const START_TIME = 'startTime'; export const END_TIME = 'endTime'; export const DAYS_OF_THE_WEEK = 'daysOfTheWeek'; +export interface DateRangeInterface { + startTime: any; + endTime: any; + date: string; + startDate: string; + daysOfTheWeek: any; +} /** * Gets the string values from a date range object within a query string. @@ -14,7 +21,7 @@ export const DAYS_OF_THE_WEEK = 'daysOfTheWeek'; * @param dateRangeQuery A query subobject representing a date range. * @returns A graphParams subobject representing a date range. */ -function processDateRangeQuery(dateRangeQuery) { +function processDateRangeQuery(dateRangeQuery): DateRangeInterface { const initialDateRangeParams = initialGraphParams.firstDateRange; if (!dateRangeQuery) { @@ -25,11 +32,12 @@ function processDateRangeQuery(dateRangeQuery) { // If the values are missing, we must use the defaults, in case the user is changing // from a nondefault to default value. - const newDateRangeParams = { + const newDateRangeParams: DateRangeInterface = { date: date || initialDateRangeParams.date, startDate: startDate || initialDateRangeParams.startDate, startTime: startTime || null, endTime: endTime || null, + daysOfTheWeek: null, }; if (daysOfTheWeek) { @@ -97,7 +105,7 @@ function processQuery(getState) { * @returns The query subobject. */ export function dateQueryFromDateRangeParams(params) { - const dateQuery = {}; + const dateQuery: { [key: string]: any } = {}; const initialDateRangeParams = initialGraphParams.firstDateRange; if (!params) { @@ -132,7 +140,7 @@ export function dateQueryFromDateRangeParams(params) { * @returns The query object to dispatch. */ export function fullQueryFromParams(params) { - const query = {}; + const query: { [key: string]: any } = {}; query.firstDateRange = dateQueryFromDateRangeParams(params.firstDateRange); query.secondDateRange = params.secondDateRange || undefined; return query; diff --git a/frontend/src/screens/About.jsx b/frontend/src/screens/About.tsx similarity index 100% rename from frontend/src/screens/About.jsx rename to frontend/src/screens/About.tsx diff --git a/frontend/src/screens/Dashboard.jsx b/frontend/src/screens/Dashboard.tsx similarity index 90% rename from frontend/src/screens/Dashboard.jsx rename to frontend/src/screens/Dashboard.tsx index 7b402fd7..d8e13853 100644 --- a/frontend/src/screens/Dashboard.jsx +++ b/frontend/src/screens/Dashboard.tsx @@ -1,10 +1,7 @@ import React, { useEffect } from 'react'; -import Grid from '@material-ui/core/Grid'; - import { connect } from 'react-redux'; -import MapSpider from '../components/MapSpider'; -import RouteTable from '../components/RouteTable'; - +import { Grid } from '@material-ui/core'; +import { MapSpider, RouteTable } from 'components'; import { fetchRoutes, handleGraphParams } from '../actions'; function Dashboard(props) { diff --git a/frontend/src/screens/DataDiagnostic.jsx b/frontend/src/screens/DataDiagnostic.tsx similarity index 94% rename from frontend/src/screens/DataDiagnostic.jsx rename to frontend/src/screens/DataDiagnostic.tsx index 5ece560e..015e73e3 100644 --- a/frontend/src/screens/DataDiagnostic.jsx +++ b/frontend/src/screens/DataDiagnostic.tsx @@ -1,9 +1,6 @@ import React, { useEffect } from 'react'; - import { connect } from 'react-redux'; - -import QuadrantChart from '../components/QuadrantChart'; - +import { QuadrantChart } from 'components'; import { fetchRoutes, handleGraphParams } from '../actions'; /** diff --git a/frontend/src/screens/Home.jsx b/frontend/src/screens/Home.tsx similarity index 99% rename from frontend/src/screens/Home.jsx rename to frontend/src/screens/Home.tsx index 84f50fbe..08204c10 100644 --- a/frontend/src/screens/Home.jsx +++ b/frontend/src/screens/Home.tsx @@ -1,5 +1,4 @@ import React from 'react'; - import Navlink from 'redux-first-router-link'; import { connect } from 'react-redux'; diff --git a/frontend/src/screens/Isochrone.jsx b/frontend/src/screens/Isochrone.tsx similarity index 95% rename from frontend/src/screens/Isochrone.jsx rename to frontend/src/screens/Isochrone.tsx index 1a907bfc..f1e5c6ab 100644 --- a/frontend/src/screens/Isochrone.jsx +++ b/frontend/src/screens/Isochrone.tsx @@ -15,18 +15,19 @@ import { connect } from 'react-redux'; import { Map, TileLayer } from 'react-leaflet'; import L, { DomEvent } from 'leaflet'; import Control from 'react-leaflet-control'; -import Grid from '@material-ui/core/Grid'; -import List from '@material-ui/core/List'; -import ListItem from '@material-ui/core/ListItem'; -import FormControlLabel from '@material-ui/core/FormControlLabel'; -import Checkbox from '@material-ui/core/Checkbox'; -import Select from '@material-ui/core/Select'; -import MenuItem from '@material-ui/core/MenuItem'; -import Button from '@material-ui/core/Button'; -import FormControl from '@material-ui/core/FormControl'; -import InputLabel from '@material-ui/core/InputLabel'; -import SingleDateControl from '../components/SingleDateControl'; -import TimeRangeControl from '../components/TimeRangeControl'; +import { + Grid, + List, + ListItem, + FormControlLabel, + Checkbox, + Select, + MenuItem, + Button, + FormControl, + InputLabel, +} from '@material-ui/core'; +import { SingleDateControl, TimeRangeControl } from 'components'; import { fetchRoutes } from '../actions'; import { @@ -78,8 +79,40 @@ function getDirectionInfo(directionId, routeInfo) { return routeInfo.directions.find(dirInfo => dirInfo.id === directionId); } -class Isochrone extends React.Component { - constructor(props) { +interface Props { + [key: string]: any; +} + +interface State { + [key: string]: any; +} + +class Isochrone extends React.Component { + agency: any; + + agencyId: any; + + initialZoom: any; + + initialCenter: any; + + isochroneWorker: Worker; + + layers: any[]; + + isochroneLayers: any[]; + + tripLayers: any[]; + + routeLayers: any[]; + + mapRef: React.RefObject; + + container: any; + + boundUpdate: any; + + constructor(props: Props) { super(props); // for now, only supports 1 agency at a time. @@ -272,13 +305,13 @@ class Isochrone extends React.Component { latlng = this.agency.defaultIsochroneCenter; } if (latlng) { - this.computeIsochrones(latlng, null); + this.computeIsochrones(latlng); } }, err => { const defaultLatLng = this.agency.defaultIsochroneCenter; if (defaultLatLng) { - this.computeIsochrones(this.agency.defaultIsochroneCenter, null); + this.computeIsochrones(this.agency.defaultIsochroneCenter); } }, ); @@ -289,7 +322,7 @@ class Isochrone extends React.Component { return; } this.resetMap(); - this.computeIsochrones(event.latlng, null); + this.computeIsochrones(event.latlng); } addReachableLocationsLayer(data) { @@ -472,7 +505,7 @@ class Isochrone extends React.Component { } } - computeIsochrones(latLng, endLatLng) { + computeIsochrones(latLng, endLatLng = '') { if (!isInServiceArea(this.agencyId, latLng)) { return; } @@ -482,16 +515,10 @@ class Isochrone extends React.Component { const endTimeStr = this.props.endTime; const timeStr = startTimeStr && endTimeStr ? `${startTimeStr}-${endTimeStr}` : ''; - const { maxTripMin, enabledRoutes } = this.state; - - const enabledRoutesArr = []; - - this.props.routes.forEach(route => { - if (enabledRoutes[route.id] !== false) { - enabledRoutesArr.push(route.id); - } - }); + const enabledRoutesArr = this.props.routes + .filter(route => enabledRoutes[route.id] !== false) + .map(route => route.id); const computeId = [ latLng.lat, @@ -626,7 +653,7 @@ class Isochrone extends React.Component { const { latLng, endLatLng } = this.state; if (latLng) { this.resetMap(); - this.computeIsochrones(latLng, endLatLng); + this.computeIsochrones(latLng); } } @@ -647,7 +674,7 @@ class Isochrone extends React.Component { this.layers = []; this.isochroneLayers = []; - this.clearTripLayers(); + this.clearTripLayers(true); } clearTripLayers(clearTripInfo) { @@ -757,6 +784,7 @@ class Isochrone extends React.Component { opacity={0.6} /> {/* see http://maps.stamen.com for details */} + // @ts-ignore
diff --git a/frontend/src/screens/NotFound.jsx b/frontend/src/screens/NotFound.tsx similarity index 100% rename from frontend/src/screens/NotFound.jsx rename to frontend/src/screens/NotFound.tsx diff --git a/frontend/src/screens/RouteScreen.jsx b/frontend/src/screens/RouteScreen.tsx similarity index 90% rename from frontend/src/screens/RouteScreen.jsx rename to frontend/src/screens/RouteScreen.tsx index a9b3cd29..8408e8fd 100644 --- a/frontend/src/screens/RouteScreen.jsx +++ b/frontend/src/screens/RouteScreen.tsx @@ -1,15 +1,16 @@ import React, { useEffect } from 'react'; import { connect } from 'react-redux'; - import { makeStyles } from '@material-ui/core/styles'; import { AppBar, Tab, Tabs, Box, Paper, Grid } from '@material-ui/core'; -import MapStops from '../components/MapStops'; -import ControlPanel from '../components/ControlPanel'; -import SummaryStats from '../components/SummaryStats'; -import TripTimesStats from '../components/TripTimesStats'; -import ServiceFrequencyStats from '../components/ServiceFrequencyStats'; -import OnTimePerformanceStats from '../components/OnTimePerformanceStats'; -import MareyChart from '../components/MareyChart'; +import { + MapStops, + ControlPanel, + SummaryStats, + TripTimesStats, + ServiceFrequencyStats, + OnTimePerformanceStats, + MareyChart, +} from 'components'; import { fetchRoutes } from '../actions'; const useStyles = makeStyles(theme => ({ diff --git a/frontend/src/screens/index.ts b/frontend/src/screens/index.ts new file mode 100644 index 00000000..ddb7f302 --- /dev/null +++ b/frontend/src/screens/index.ts @@ -0,0 +1,7 @@ +export { default as About } from './About'; +export { default as Dashboard } from './Dashboard'; +export { default as DataDiagnostic } from './DataDiagnostic'; +export { default as Home } from './Home'; +export { default as Isochrone } from './Isochrone'; +export { default as NotFound } from './NotFound'; +export { default as RouteScreen } from './RouteScreen'; diff --git a/frontend/src/serviceWorker.js b/frontend/src/serviceWorker.ts old mode 100755 new mode 100644 similarity index 99% rename from frontend/src/serviceWorker.js rename to frontend/src/serviceWorker.ts index 17dabb44..8ba0b9ca --- a/frontend/src/serviceWorker.js +++ b/frontend/src/serviceWorker.ts @@ -1,3 +1,5 @@ +export default {}; + // // This optional code is used to register a service worker. // // register() is not called by default. // diff --git a/frontend/src/store.js b/frontend/src/store.ts similarity index 71% rename from frontend/src/store.js rename to frontend/src/store.ts index 81f9bbb0..45216c58 100644 --- a/frontend/src/store.js +++ b/frontend/src/store.ts @@ -1,27 +1,28 @@ +/* eslint-disable import/no-unresolved */ +/* eslint-disable import/extensions */ /* eslint-disable no-underscore-dangle */ import { createStore, applyMiddleware, combineReducers, compose } from 'redux'; import { connectRoutes } from 'redux-first-router'; import qs from 'qs'; import thunk from 'redux-thunk'; - import routesMap from './routesMap'; import * as reducers from './reducers'; // import page from './reducers/page'; import * as actionCreators from './actions'; -const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ - ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ actionCreators }) +const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ + ? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ actionCreators }) : compose; -export default function configureStore(preloadedState) { +export default function configureStore(preloadedState = {}) { const { reducer, middleware, enhancer } = connectRoutes(routesMap, { querySerializer: qs, }); + // @ts-ignore const rootReducer = combineReducers({ ...reducers, location: reducer }); const middlewares = applyMiddleware(thunk, middleware); const enhancers = composeEnhancers(enhancer, middlewares); - const store = createStore(rootReducer, preloadedState, enhancers); return { store }; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 00000000..e110724c --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,38 @@ +{ + "extends": "./tsconfig.paths.json", + "compilerOptions": { + "jsx": "react", + "outDir": "build", + "baseUrl": "./src/", + "allowJs": true, + "target": "es6", + "module": "esnext", + "moduleResolution": "node", + "lib": [ + "esnext", + "dom" + ], + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "alwaysStrict": false, + "strictFunctionTypes": false, + "strictPropertyInitialization": false, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + // remove in the future + "noImplicitAny": false, + "noImplicitThis": false, + "strictNullChecks": false, + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "build" + ] +} diff --git a/frontend/tsconfig.paths.json b/frontend/tsconfig.paths.json new file mode 100644 index 00000000..15681e47 --- /dev/null +++ b/frontend/tsconfig.paths.json @@ -0,0 +1,8 @@ + +{ + "paths": { + "@components": ["components"], + "@helpers": ["helpers"], + "@screens": ["screens"], + } +} \ No newline at end of file diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js deleted file mode 100644 index 15953c96..00000000 --- a/frontend/webpack.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - resolve: {extensions: ['.js','.jsx']} -} \ No newline at end of file diff --git a/frontend/webpack.config.ts b/frontend/webpack.config.ts new file mode 100644 index 00000000..549c83fd --- /dev/null +++ b/frontend/webpack.config.ts @@ -0,0 +1,21 @@ +import { TsconfigPathsPlugin } from "tsconfig-paths-webpack-plugin"; + +module.exports = { + entry: './src/index.tsx', + output: { + filename: './build/bundle.js', + }, + devtool: 'source-map', + resolve: { + extensions: ['', '.ts', '.tsx', '.js', '.jsx'], + plugins: [new TsconfigPathsPlugin()] + }, + module: { + rules: [ + // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'. + { test: /\.tsx?$/, loader: 'awesome-typescript-loader' }, + // All output '.jsx' or '.js' files will have any sourcemaps re-processed by 'source-map-loader'. + { test: /\.jsx?$/, loader: 'source-map-loader' }, + ], + }, +}; diff --git a/package-lock.json b/package-lock.json index 958df32b..92623ebe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,45 @@ { "name": "opentransit", "version": "0.1.0", - "lockfileVersion": 1 + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.0.tgz", + "integrity": "sha512-WE4IOAC6r/yBZss1oQGM5zs2D7RuKR6Q+w+X2SouPofnWn+LbCqClRyhO3ZE7Ix8nmFgo/oVuuE01cJT2XB13A==" + }, + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" + }, + "@types/react": { + "version": "16.9.34", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.34.tgz", + "integrity": "sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow==", + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "@types/react-dom": { + "version": "16.9.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.6.tgz", + "integrity": "sha512-S6ihtlPMDotrlCJE9ST1fRmYrQNNwfgL61UB4I1W7M6kPulUKx9fXAleW5zpdIjUQ4fTaaog8uERezjsGUj9HQ==", + "requires": { + "@types/react": "*" + } + }, + "csstype": { + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz", + "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==" + }, + "typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==" + } + } }