diff --git a/app/.babelrc b/app/.babelrc new file mode 100644 index 00000000..1b96c4f3 --- /dev/null +++ b/app/.babelrc @@ -0,0 +1,14 @@ +{ + "env": { + "development": { + "plugins": [ + "@babel/plugin-syntax-jsx", + "@babel/plugin-syntax-typescript", + ["babel-plugin-typescript-to-proptypes", { + "implicitChildren": true, + "typeCheck": true + }] + ] + } + } +} \ No newline at end of file diff --git a/app/package-lock.json b/app/package-lock.json index 73ffb461..8913e572 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -425,6 +425,15 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-syntax-typescript": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz", + "integrity": "sha512-dGwbSMA1YhVS8+31CnPR7LB4pcbrzcV99wQzby4uAfrkZPYZlQ7ImwdpzLqi6Z6IL02b8IAL379CaMwo0x5Lag==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, "@babel/plugin-transform-arrow-functions": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", @@ -2324,6 +2333,17 @@ "integrity": "sha512-f49NsaohQ1ByY20nUrpc30QFdbeT4ntV4PAL2vSZe6uCB5nqAcqXS/qzU+aI6ZfYhWASx5eIsTFvFrs1B2ffGg==", "dev": true }, + "babel-plugin-typescript-to-proptypes": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/babel-plugin-typescript-to-proptypes/-/babel-plugin-typescript-to-proptypes-0.17.1.tgz", + "integrity": "sha512-yREUfvDlmn6QjM0QbywXUkXBQMD/iFfLVTl+jig4X7ZLUg9lq8ZLuex8HIM2SQ4X3vcjGnWPFowodlMcXhwxdQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-typescript": "^7.2.0" + } + }, "babel-preset-jest": { "version": "23.2.0", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", @@ -9509,7 +9529,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { @@ -13639,7 +13659,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" } } @@ -17045,7 +17065,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -17886,7 +17906,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { diff --git a/app/package.json b/app/package.json index d9630dbb..e1048cfd 100644 --- a/app/package.json +++ b/app/package.json @@ -37,6 +37,8 @@ "sharp": "^0.22.1" }, "devDependencies": { + "@babel/plugin-syntax-jsx": "^7.2.0", + "@babel/plugin-syntax-typescript": "^7.3.3", "@types/express": "^4.17.0", "@types/express-session": "^1.15.13", "@types/jest": "^24.0.17", @@ -51,6 +53,7 @@ "@types/sharp": "^0.22.2", "@types/webpack-env": "^1.14.0", "babel-eslint": "^10.0.2", + "babel-plugin-typescript-to-proptypes": "^0.17.1", "eslint": "^5.16.0", "eslint-plugin-jest": "^22.15.0", "eslint-plugin-react": "^7.14.3", diff --git a/app/razzle.config.js b/app/razzle.config.js index aa6348cd..42bd60aa 100644 --- a/app/razzle.config.js +++ b/app/razzle.config.js @@ -9,6 +9,17 @@ module.exports = { }) config.module.rules = rules; + // find module rule that runs ts-loader for TS(X) files + const tsRule = config.module.rules.find(r => + new RegExp(r.test).test('test.tsx') && Array.isArray(r.use) && r.use.some(u => u.loader.includes('ts-loader'))); + + // run babel-loader before ts-loader to generate propTypes + tsRule.use.push({ + loader: 'babel-loader', + options: { + babelrc: true + } + }) return config; }, }; diff --git a/app/src/frontend/app.tsx b/app/src/frontend/app.tsx index d269c7ac..2d2b357c 100644 --- a/app/src/frontend/app.tsx +++ b/app/src/frontend/app.tsx @@ -1,6 +1,5 @@ import React, { Fragment } from 'react'; import { Route, Switch, Link } from 'react-router-dom'; -import PropTypes from 'prop-types'; import '../../node_modules/bootstrap/dist/css/bootstrap.min.css'; import './app.css'; @@ -22,15 +21,21 @@ import ContactPage from './pages/contact'; import DataAccuracyPage from './pages/data-accuracy'; import OrdnanceSurveyLicencePage from './pages/ordnance-survey-licence'; import OrdnanceSurveyUprnPage from './pages/ordnance-survey-uprn'; +import { Building } from './models/building'; +import { User } from './models/user'; interface AppProps { - user?: any; - building?: any; + user?: User; + building?: Building; building_like?: boolean; revisionId: number; } +interface AppState { + user?: User; +} + /** * App component * @@ -43,13 +48,7 @@ interface AppProps { * map or other pages are rendered, based on the URL. Use a react-router-dom in * child components to navigate without a full page reload. */ -class App extends React.Component { // TODO: add proper types - static propTypes = { // TODO: generate propTypes from TS - user: PropTypes.object, - building: PropTypes.object, - building_like: PropTypes.bool - }; - +class App extends React.Component { static mapAppPaths = ['/', '/:mode(view|edit|multi-edit)/:category/:building(\\d+)?/(history)?']; constructor(props: Readonly) { diff --git a/app/src/frontend/building/building-not-found.tsx b/app/src/frontend/building/building-not-found.tsx index cd549453..07c7c2af 100644 --- a/app/src/frontend/building/building-not-found.tsx +++ b/app/src/frontend/building/building-not-found.tsx @@ -1,5 +1,4 @@ import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; import InfoBox from '../components/info-box'; @@ -18,8 +17,4 @@ const BuildingNotFound: React.FunctionComponent = (props) ); -BuildingNotFound.propTypes = { - mode: PropTypes.string -} - export default BuildingNotFound; diff --git a/app/src/frontend/building/building-view.tsx b/app/src/frontend/building/building-view.tsx index e66a93ee..25fe53b9 100644 --- a/app/src/frontend/building/building-view.tsx +++ b/app/src/frontend/building/building-view.tsx @@ -20,9 +20,9 @@ import { Building } from '../models/building'; interface BuildingViewProps { cat: string; mode: 'view' | 'edit'; - building: Building; - building_like: boolean; - user: any; + building?: Building; + building_like?: boolean; + user?: any; selectBuilding: (building: Building) => void } diff --git a/app/src/frontend/building/categories.tsx b/app/src/frontend/building/categories.tsx index f6e3e2f9..54d07541 100644 --- a/app/src/frontend/building/categories.tsx +++ b/app/src/frontend/building/categories.tsx @@ -1,10 +1,14 @@ import React from 'react'; import { NavLink } from 'react-router-dom'; -import PropTypes from 'prop-types'; import './categories.css' -const Categories = (props) => ( +interface CategoriesProps { + mode: 'view' | 'edit' | 'multi-edit'; + building_id?: number; +} + +const Categories: React.FC = (props) => (
    (
) -Categories.propTypes = { - mode: PropTypes.string, - building_id: PropTypes.number +interface CategoryProps { + mode: 'view' | 'edit' | 'multi-edit'; + building_id?: number; + slug: string; + title: string; + desc: string; + help: string; + inactive: boolean; } -const Category = (props) => { +const Category: React.FC = (props) => { let categoryLink = `/${props.mode}/${props.slug}`; if (props.building_id != undefined) categoryLink += `/${props.building_id}`; @@ -145,14 +154,4 @@ const Category = (props) => { ); } -Category.propTypes = { - title: PropTypes.string, - desc: PropTypes.string, - slug: PropTypes.string, - help: PropTypes.string, - inactive: PropTypes.bool, - mode: PropTypes.string, - building_id: PropTypes.number -} - export default Categories; diff --git a/app/src/frontend/building/container-header.tsx b/app/src/frontend/building/container-header.tsx index 2f88322e..e40d9e4e 100644 --- a/app/src/frontend/building/container-header.tsx +++ b/app/src/frontend/building/container-header.tsx @@ -1,7 +1,7 @@ -import React, { Fragment } from 'react'; -import { Link, NavLink } from 'react-router-dom'; +import React from 'react'; +import { Link } from 'react-router-dom'; -import { BackIcon, EditIcon, ViewIcon }from '../components/icons'; +import { BackIcon }from '../components/icons'; interface ContainerHeaderProps { cat?: string; diff --git a/app/src/frontend/building/data-components/checkbox-data-entry.tsx b/app/src/frontend/building/data-components/checkbox-data-entry.tsx index 82bca162..2434136c 100644 --- a/app/src/frontend/building/data-components/checkbox-data-entry.tsx +++ b/app/src/frontend/building/data-components/checkbox-data-entry.tsx @@ -1,5 +1,4 @@ import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; import { DataTitleCopyable } from './data-title'; import { BaseDataEntryProps } from './data-entry'; @@ -8,7 +7,6 @@ interface CheckboxDataEntryProps extends BaseDataEntryProps { value: boolean; } - const CheckboxDataEntry: React.FunctionComponent = (props) => { return ( @@ -37,18 +35,4 @@ const CheckboxDataEntry: React.FunctionComponent = (prop ); } -CheckboxDataEntry.propTypes = { - title: PropTypes.string, - slug: PropTypes.string, - tooltip: PropTypes.string, - disabled: PropTypes.bool, - value: PropTypes.any, - onChange: PropTypes.func, - copy: PropTypes.shape({ - copying: PropTypes.bool, - copyingKey: PropTypes.func, - toggleCopyAttribute: PropTypes.func - }) -} - export default CheckboxDataEntry; diff --git a/app/src/frontend/building/data-components/data-entry.tsx b/app/src/frontend/building/data-components/data-entry.tsx index c05954f4..a9032885 100644 --- a/app/src/frontend/building/data-components/data-entry.tsx +++ b/app/src/frontend/building/data-components/data-entry.tsx @@ -1,20 +1,20 @@ import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; import { DataTitleCopyable } from './data-title'; +import { CopyProps } from '../data-containers/category-view-props'; interface BaseDataEntryProps { slug: string; title: string; tooltip?: string; disabled?: boolean; - copy?: any; // CopyProps clashes with propTypes + copy?: CopyProps; // CopyProps clashes with propTypes mode?: 'view' | 'edit' | 'multi-edit'; onChange?: (key: string, value: any) => void; } interface DataEntryProps extends BaseDataEntryProps { - value: string; + value?: string; maxLength?: number; placeholder?: string; valueTransform?: (string) => string @@ -49,22 +49,6 @@ const DataEntry: React.FunctionComponent = (props) => { ); } -DataEntry.propTypes = { - title: PropTypes.string, - slug: PropTypes.string, - tooltip: PropTypes.string, - disabled: PropTypes.bool, - value: PropTypes.any, - placeholder: PropTypes.string, - maxLength: PropTypes.number, - onChange: PropTypes.func, - copy: PropTypes.shape({ - copying: PropTypes.bool, - copyingKey: PropTypes.func, - toggleCopyAttribute: PropTypes.func - }) -} - export default DataEntry; export { BaseDataEntryProps diff --git a/app/src/frontend/building/data-components/data-title.tsx b/app/src/frontend/building/data-components/data-title.tsx index afa07efe..fcffd790 100644 --- a/app/src/frontend/building/data-components/data-title.tsx +++ b/app/src/frontend/building/data-components/data-title.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import PropTypes from 'prop-types'; import Tooltip from '../../components/tooltip'; +import { CopyProps } from '../data-containers/category-view-props'; interface DataTitleProps { @@ -18,18 +18,13 @@ const DataTitle: React.FunctionComponent = (props) => { ) } -DataTitle.propTypes = { - title: PropTypes.string, - tooltip: PropTypes.string -} - interface DataTitleCopyableProps { title: string; - tooltip: string; + tooltip?: string; slug: string; disabled?: boolean; - copy?: any; // TODO: type should be CopyProps, but that clashes with propTypes in some obscure way + copy?: CopyProps; } const DataTitleCopyable: React.FunctionComponent = (props) => { @@ -55,18 +50,5 @@ const DataTitleCopyable: React.FunctionComponent = (prop ); } -DataTitleCopyable.propTypes = { - title: PropTypes.string, - tooltip: PropTypes.string, - slug: PropTypes.string, - disabled: PropTypes.bool, - copy: PropTypes.shape({ - copying: PropTypes.bool, - copyingKey: PropTypes.func, - toggleCopyAttribute: PropTypes.func, - toggleCopying: PropTypes.func - }) -} - export default DataTitle; export { DataTitleCopyable } diff --git a/app/src/frontend/building/data-components/like-data-entry.tsx b/app/src/frontend/building/data-components/like-data-entry.tsx index c9719d3f..086dfb7e 100644 --- a/app/src/frontend/building/data-components/like-data-entry.tsx +++ b/app/src/frontend/building/data-components/like-data-entry.tsx @@ -1,6 +1,5 @@ import React, { Fragment } from 'react'; import { NavLink } from 'react-router-dom'; -import PropTypes from 'prop-types'; import Tooltip from '../../components/tooltip'; @@ -49,11 +48,4 @@ const LikeDataEntry: React.FunctionComponent = (props) => { ); } -LikeDataEntry.propTypes = { - // mode: PropTypes.string, - userLike: PropTypes.bool, - totalLikes: PropTypes.number, - onLike: PropTypes.func -}; - export default LikeDataEntry; diff --git a/app/src/frontend/building/data-components/multi-data-entry.tsx b/app/src/frontend/building/data-components/multi-data-entry.tsx index 11e50452..755d0583 100644 --- a/app/src/frontend/building/data-components/multi-data-entry.tsx +++ b/app/src/frontend/building/data-components/multi-data-entry.tsx @@ -1,23 +1,16 @@ import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; import { sanitiseURL } from '../../helpers'; import { DataTitleCopyable } from './data-title'; +import { BaseDataEntryProps } from './data-entry'; -class MultiDataEntry extends Component { // TODO: add proper types - static propTypes = { // TODO: generate propTypes from TS - slug: PropTypes.string, - title: PropTypes.string, - tooltip: PropTypes.string, - value: PropTypes.arrayOf(PropTypes.string), - placeholder: PropTypes.string, - disabled: PropTypes.bool, - onChange: PropTypes.func, - copy: PropTypes.bool, - toggleCopyAttribute: PropTypes.func, - copying: PropTypes.bool - }; +interface MultiDataEntryProps extends BaseDataEntryProps { + value: string[]; + placeholder: string; +} + +class MultiDataEntry extends Component { constructor(props) { super(props); diff --git a/app/src/frontend/building/data-components/numeric-data-entry.tsx b/app/src/frontend/building/data-components/numeric-data-entry.tsx index 42c756d7..f949cb5c 100644 --- a/app/src/frontend/building/data-components/numeric-data-entry.tsx +++ b/app/src/frontend/building/data-components/numeric-data-entry.tsx @@ -1,5 +1,4 @@ import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; import { DataTitleCopyable } from './data-title'; import { BaseDataEntryProps } from './data-entry'; @@ -45,22 +44,4 @@ const NumericDataEntry: React.FunctionComponent = (props) ); } -NumericDataEntry.propTypes = { - title: PropTypes.string, - slug: PropTypes.string, - tooltip: PropTypes.string, - disabled: PropTypes.bool, - value: PropTypes.any, - placeholder: PropTypes.string, - max: PropTypes.number, - min: PropTypes.number, - step: PropTypes.number, - onChange: PropTypes.func, - copy: PropTypes.shape({ - copying: PropTypes.bool, - copyingKey: PropTypes.func, - toggleCopyAttribute: PropTypes.func - }) -} - export default NumericDataEntry; diff --git a/app/src/frontend/building/data-components/select-data-entry.tsx b/app/src/frontend/building/data-components/select-data-entry.tsx index 2e7310e0..969b2de2 100644 --- a/app/src/frontend/building/data-components/select-data-entry.tsx +++ b/app/src/frontend/building/data-components/select-data-entry.tsx @@ -1,5 +1,4 @@ import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; import { DataTitleCopyable } from './data-title'; import { BaseDataEntryProps } from './data-entry'; @@ -44,20 +43,4 @@ const SelectDataEntry: React.FunctionComponent = (props) = ); } -SelectDataEntry.propTypes = { - title: PropTypes.string, - slug: PropTypes.string, - tooltip: PropTypes.string, - disabled: PropTypes.bool, - value: PropTypes.any, - placeholder: PropTypes.string, - options: PropTypes.arrayOf(PropTypes.string), - onChange: PropTypes.func, - copy: PropTypes.shape({ - copying: PropTypes.bool, - copyingKey: PropTypes.func, - toggleCopyAttribute: PropTypes.func - }) -} - export default SelectDataEntry; diff --git a/app/src/frontend/building/data-components/textbox-data-entry.tsx b/app/src/frontend/building/data-components/textbox-data-entry.tsx index 4e7961d1..27f929ed 100644 --- a/app/src/frontend/building/data-components/textbox-data-entry.tsx +++ b/app/src/frontend/building/data-components/textbox-data-entry.tsx @@ -1,5 +1,4 @@ import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; import { DataTitleCopyable } from './data-title'; import { BaseDataEntryProps } from './data-entry'; @@ -42,20 +41,4 @@ const TextboxDataEntry: React.FunctionComponent = (props) ); } -TextboxDataEntry.propTypes = { - title: PropTypes.string, - slug: PropTypes.string, - tooltip: PropTypes.string, - disabled: PropTypes.bool, - value: PropTypes.any, - placeholder: PropTypes.string, - maxLength: PropTypes.number, - onChange: PropTypes.func, - copy: PropTypes.shape({ - copying: PropTypes.bool, - copyingKey: PropTypes.func, - toggleCopyAttribute: PropTypes.func - }) -} - export default TextboxDataEntry; diff --git a/app/src/frontend/building/data-components/uprns-data-entry.tsx b/app/src/frontend/building/data-components/uprns-data-entry.tsx index d71b4870..44c414f9 100644 --- a/app/src/frontend/building/data-components/uprns-data-entry.tsx +++ b/app/src/frontend/building/data-components/uprns-data-entry.tsx @@ -1,10 +1,18 @@ import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; -import Tooltip from '../../components/tooltip'; import DataTitle from './data-title'; -const UPRNsDataEntry = (props) => { + +interface UPRNsDataEntryProps { + title: string; + tooltip: string; + value: { + uprn: string; + parent_uprn?: string; + }[]; +} + +const UPRNsDataEntry: React.FC = (props) => { const uprns = props.value || []; const noParent = uprns.filter(uprn => uprn.parent_uprn == null); const withParent = uprns.filter(uprn => uprn.parent_uprn != null); @@ -46,15 +54,6 @@ const UPRNsDataEntry = (props) => { ) -} - -UPRNsDataEntry.propTypes = { - title: PropTypes.string, - tooltip: PropTypes.string, - value: PropTypes.arrayOf(PropTypes.shape({ - uprn: PropTypes.string.isRequired, - parent_uprn: PropTypes.string - })) -} +}; export default UPRNsDataEntry; diff --git a/app/src/frontend/building/data-components/year-data-entry.tsx b/app/src/frontend/building/data-components/year-data-entry.tsx index bb5fe709..94fcc2cb 100644 --- a/app/src/frontend/building/data-components/year-data-entry.tsx +++ b/app/src/frontend/building/data-components/year-data-entry.tsx @@ -1,23 +1,19 @@ import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; import NumericDataEntry from './numeric-data-entry'; import { dataFields } from '../../data_fields'; +import { CopyProps } from '../data-containers/category-view-props'; -class YearDataEntry extends Component { // TODO: add proper types - static propTypes = { // TODO: generate propTypes from TS - year: PropTypes.number, - upper: PropTypes.number, - lower: PropTypes.number, - mode: PropTypes.string, - onChange: PropTypes.func, - copy: PropTypes.shape({ - copying: PropTypes.bool, - copyingKey: PropTypes.func, - toggleCopyAttribute: PropTypes.func - }) - }; +interface YearDataEntryProps { + year: number; + upper: number; + lower: number; + copy?: CopyProps; + mode?: 'view' | 'edit' | 'multi-edit'; + onChange?: (key: string, value: any) => void; +} +class YearDataEntry extends Component { constructor(props) { super(props); this.state = { diff --git a/app/src/frontend/building/data-container.tsx b/app/src/frontend/building/data-container.tsx index db903baa..c5685fe6 100644 --- a/app/src/frontend/building/data-container.tsx +++ b/app/src/frontend/building/data-container.tsx @@ -1,5 +1,4 @@ import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; import { Redirect, NavLink } from 'react-router-dom'; import ContainerHeader from './container-header'; @@ -19,10 +18,10 @@ interface DataContainerProps { help: string; inactive?: boolean; - user: User; + user?: User; mode: 'view' | 'edit'; - building: Building; - building_like: boolean; + building?: Building; + building_like?: boolean; selectBuilding: (building: Building) => void } @@ -45,15 +44,6 @@ interface DataContainerState { */ const withCopyEdit = (WrappedComponent: React.ComponentType) => { return class DataContainer extends React.Component { - static propTypes = { // TODO: generate propTypes from TS - title: PropTypes.string, - slug: PropTypes.string, - intro: PropTypes.string, - help: PropTypes.string, - inactive: PropTypes.bool, - children: PropTypes.node - }; - constructor(props) { super(props); @@ -359,4 +349,4 @@ const withCopyEdit = (WrappedComponent: React.ComponentType) } } -export default withCopyEdit; \ No newline at end of file +export default withCopyEdit; diff --git a/app/src/frontend/building/multi-edit.tsx b/app/src/frontend/building/multi-edit.tsx index a0712fc3..eddd6c85 100644 --- a/app/src/frontend/building/multi-edit.tsx +++ b/app/src/frontend/building/multi-edit.tsx @@ -1,16 +1,23 @@ -import React, { Fragment } from 'react'; -import { Link, Redirect } from 'react-router-dom'; +import React from 'react'; +import { Link, Redirect, RouteComponentProps } from 'react-router-dom'; import { parse } from 'query-string'; -import PropTypes from 'prop-types'; import Sidebar from './sidebar'; import InfoBox from '../components/info-box'; import { BackIcon }from '../components/icons'; import DataEntry from './data-components/data-entry'; import { dataFields } from '../data_fields'; +import { User } from '../models/user'; +interface MultiEditRouteParams { + cat: string; +} -const MultiEdit = (props) => { +interface MultiEditProps extends RouteComponentProps { + user?: User; +} + +const MultiEdit: React.FC = (props) => { if (!props.user){ return } @@ -85,10 +92,4 @@ const MultiEdit = (props) => { ); } -MultiEdit.propTypes = { - user: PropTypes.object, - match: PropTypes.object, - location: PropTypes.object -} - export default MultiEdit; diff --git a/app/src/frontend/building/sidebar.tsx b/app/src/frontend/building/sidebar.tsx index e502cfb4..21a4ce84 100644 --- a/app/src/frontend/building/sidebar.tsx +++ b/app/src/frontend/building/sidebar.tsx @@ -1,16 +1,11 @@ import React from 'react'; -import PropTypes from 'prop-types'; import './sidebar.css'; -const Sidebar = (props) => ( +const Sidebar: React.FC<{}> = (props) => ( ); -Sidebar.propTypes = { - children: PropTypes.node -} - export default Sidebar; diff --git a/app/src/frontend/components/confirmation-modal.tsx b/app/src/frontend/components/confirmation-modal.tsx index abeb3b39..00067956 100644 --- a/app/src/frontend/components/confirmation-modal.tsx +++ b/app/src/frontend/components/confirmation-modal.tsx @@ -58,4 +58,4 @@ const ConfirmationModal: React.FunctionComponent = ({ ); }; -export default ConfirmationModal; \ No newline at end of file +export default ConfirmationModal; diff --git a/app/src/frontend/components/error-box.tsx b/app/src/frontend/components/error-box.tsx index 0e51e3f1..09e10934 100644 --- a/app/src/frontend/components/error-box.tsx +++ b/app/src/frontend/components/error-box.tsx @@ -1,7 +1,10 @@ import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; -function ErrorBox(props){ +interface ErrorBoxProps { + msg: string; +} + +const ErrorBox: React.FC = (props) => { if (props.msg) { console.error(props.msg); } @@ -12,7 +15,7 @@ function ErrorBox(props){ (
{ - (typeof props.msg === 'string' || props.msg instanceof String)? + typeof props.msg === 'string' ? props.msg : 'Unexpected error' } @@ -21,10 +24,6 @@ function ErrorBox(props){ } ); -} - -ErrorBox.propTypes = { - msg: PropTypes.string -} +}; export default ErrorBox; diff --git a/app/src/frontend/components/info-box.tsx b/app/src/frontend/components/info-box.tsx index 0185b561..0a9e3892 100644 --- a/app/src/frontend/components/info-box.tsx +++ b/app/src/frontend/components/info-box.tsx @@ -1,14 +1,17 @@ import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; -const InfoBox = (props) => ( +interface InfoBoxProps { + msg: string; +} + +const InfoBox: React.FC = (props) => ( { (props.msg || props.children)? (
{ - (typeof props.msg === 'string' || props.msg instanceof String)? + typeof props.msg === 'string' ? props.msg : 'Enjoy the colouring! Usual service should resume shortly.' } @@ -21,9 +24,4 @@ const InfoBox = (props) => ( ); -InfoBox.propTypes = { - msg: PropTypes.string, - children: PropTypes.node -} - export default InfoBox; diff --git a/app/src/frontend/components/tooltip.tsx b/app/src/frontend/components/tooltip.tsx index b9de9267..04cb8a28 100644 --- a/app/src/frontend/components/tooltip.tsx +++ b/app/src/frontend/components/tooltip.tsx @@ -1,14 +1,17 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import './tooltip.css'; import { InfoIcon } from './icons'; -class Tooltip extends Component { // TODO: add proper types - static propTypes = { // TODO: generate propTypes from TS - text: PropTypes.string - }; +interface TooltipProps { + text: string; +} +interface TooltipState { + active: boolean; +} + +class Tooltip extends Component { constructor(props) { super(props); this.state = { diff --git a/app/src/frontend/header.tsx b/app/src/frontend/header.tsx index 861dbb4a..6ca98615 100644 --- a/app/src/frontend/header.tsx +++ b/app/src/frontend/header.tsx @@ -1,13 +1,14 @@ import React, { Fragment } from 'react'; import { NavLink } from 'react-router-dom'; -import PropTypes from 'prop-types'; import { Logo } from './components/logo'; +import { User } from './models/user'; + import './header.css'; interface HeaderProps { - user: any; + user: User; animateLogo: boolean; } @@ -18,14 +19,7 @@ interface HeaderState { /** * Render the main header using a responsive design */ -class Header extends React.Component { // TODO: add proper types - static propTypes = { // TODO: generate propTypes from TS - user: PropTypes.shape({ - username: PropTypes.string - }), - animateLogo: PropTypes.bool - }; - +class Header extends React.Component { constructor(props) { super(props); this.state = {collapseMenu: true}; diff --git a/app/src/frontend/map-app.tsx b/app/src/frontend/map-app.tsx index 7849a56d..cf4cfda9 100644 --- a/app/src/frontend/map-app.tsx +++ b/app/src/frontend/map-app.tsx @@ -1,6 +1,5 @@ import React, { Fragment } from 'react'; import { Switch, Route, RouteComponentProps, Redirect } from 'react-router-dom'; -import PropTypes from 'prop-types'; import Welcome from './pages/welcome'; import Sidebar from './building/sidebar'; @@ -19,10 +18,10 @@ interface MapAppRouteParams { } interface MapAppProps extends RouteComponentProps { - building: Building; - building_like: boolean; - user: any; - revisionId: number; + building?: Building; + building_like?: boolean; + user?: any; + revisionId?: number; } interface MapAppState { @@ -33,13 +32,6 @@ interface MapAppState { } class MapApp extends React.Component { - static propTypes = { - category: PropTypes.string, - revision_id: PropTypes.number, - building: PropTypes.object, - building_like: PropTypes.bool, - user: PropTypes.object - }; constructor(props: Readonly) { super(props); @@ -235,7 +227,7 @@ class MapApp extends React.Component { - + ( diff --git a/app/src/frontend/map/legend.tsx b/app/src/frontend/map/legend.tsx index b1e8bddb..75ea3d44 100644 --- a/app/src/frontend/map/legend.tsx +++ b/app/src/frontend/map/legend.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import './legend.css'; import { Logo } from '../components/logo'; @@ -114,13 +113,15 @@ const LEGEND_CONFIG = { }; -class Legend extends React.Component { // TODO: add proper types - static propTypes = { // TODO: generate propTypes from TS - slug: PropTypes.string, - color: PropTypes.string, - text: PropTypes.string - }; +interface LegendProps { + slug: string; +} +interface LegendState { + collapseList: boolean; +} + +class Legend extends React.Component { constructor(props) { super(props); this.state = {collapseList: false}; diff --git a/app/src/frontend/map/map.tsx b/app/src/frontend/map/map.tsx index 4a57efa5..f8199f1b 100644 --- a/app/src/frontend/map/map.tsx +++ b/app/src/frontend/map/map.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React, { Component, Fragment } from 'react'; import { Map, TileLayer, ZoomControl, AttributionControl, GeoJSON } from 'react-leaflet-universal'; import { GeoJsonObject } from 'geojson'; @@ -15,12 +14,12 @@ import { Building } from '../models/building'; const OS_API_KEY = 'NVUxtY5r8eA6eIfwrPTAGKrAAsoeI9E9'; interface ColouringMapProps { - building: Building; + building?: Building; mode: 'basic' | 'view' | 'edit' | 'multi-edit'; category: string; revision_id: number; - selectBuilding: any; - colourBuilding: any; + selectBuilding: (building: Building) => void; + colourBuilding: (building: Building) => void; } interface ColouringMapState { @@ -34,15 +33,6 @@ interface ColouringMapState { * Map area */ class ColouringMap extends Component { - static propTypes = { // TODO: generate propTypes from TS - building: PropTypes.object, - mode: PropTypes.string, - category: PropTypes.string, - revision_id: PropTypes.number, - selectBuilding: PropTypes.func, - colourBuilding: PropTypes.func - }; - constructor(props) { super(props); this.state = { diff --git a/app/src/frontend/map/search-box.tsx b/app/src/frontend/map/search-box.tsx index 06d2b67b..79fd03f2 100644 --- a/app/src/frontend/map/search-box.tsx +++ b/app/src/frontend/map/search-box.tsx @@ -1,16 +1,35 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import './search-box.css'; import { SearchIcon } from '../components/icons'; +import { Point } from 'geojson'; + +interface SearchResult { + type: string; + attributes: { + label: string; + zoom: number; + }; + geometry: Point +} + + +interface SearchBoxProps { + onLocate: (lat: number, lng: number, zoom: number) => void +} + +interface SearchBoxState { + q: string; + results: SearchResult[]; + fetching: boolean; + collapsedSearch: boolean; + smallScreen: boolean; +} + /** * Search for location */ -class SearchBox extends Component { // TODO: add proper types - static propTypes = { // TODO: generate propTypes from TS - onLocate: PropTypes.func - }; - +class SearchBox extends Component { constructor(props) { super(props); this.state = { diff --git a/app/src/frontend/map/theme-switcher.tsx b/app/src/frontend/map/theme-switcher.tsx index 3d87478d..c1db9815 100644 --- a/app/src/frontend/map/theme-switcher.tsx +++ b/app/src/frontend/map/theme-switcher.tsx @@ -1,9 +1,13 @@ import React from 'react'; -import PropTypes from 'prop-types'; import './theme-switcher.css'; -const ThemeSwitcher = (props) => ( +interface ThemeSwitcherProps { + currentTheme: string; + onSubmit: (e: React.FormEvent) => void; +} + +const ThemeSwitcher: React.FC = (props) => (