Merge pull request #489 from mz8i/feature/generate-prop-types

Auto-generate propTypes
This commit is contained in:
mz8i 2019-11-06 20:01:05 +00:00 committed by GitHub
commit 6a42961eaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 249 additions and 337 deletions

13
app/.babelrc Normal file
View File

@ -0,0 +1,13 @@
{
"plugins": [
"@babel/plugin-syntax-jsx",
"@babel/plugin-syntax-typescript",
[
"babel-plugin-typescript-to-proptypes",
{
"implicitChildren": true,
"typeCheck": true
}
]
]
}

28
app/package-lock.json generated
View File

@ -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": {

View File

@ -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",

View File

@ -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;
},
};

View File

@ -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 <Link /> in
* child components to navigate without a full page reload.
*/
class App extends React.Component<AppProps, any> { // 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<AppProps, AppState> {
static mapAppPaths = ['/', '/:mode(view|edit|multi-edit)/:category/:building(\\d+)?/(history)?'];
constructor(props: Readonly<AppProps>) {

View File

@ -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<BuildingNotFoundProps> = (props)
</Fragment>
);
BuildingNotFound.propTypes = {
mode: PropTypes.string
}
export default BuildingNotFound;

View File

@ -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
}

View File

@ -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<CategoriesProps> = (props) => (
<ol className="data-category-list">
<Category
title="Location"
@ -117,12 +121,17 @@ const Categories = (props) => (
</ol>
)
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<CategoryProps> = (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;

View File

@ -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;

View File

@ -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<CheckboxDataEntryProps> = (props) => {
return (
<Fragment>
@ -37,18 +35,4 @@ const CheckboxDataEntry: React.FunctionComponent<CheckboxDataEntryProps> = (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;

View File

@ -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<DataEntryProps> = (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

View File

@ -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<DataTitleProps> = (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<DataTitleCopyableProps> = (props) => {
@ -55,18 +50,5 @@ const DataTitleCopyable: React.FunctionComponent<DataTitleCopyableProps> = (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 }

View File

@ -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<LikeDataEntryProps> = (props) => {
);
}
LikeDataEntry.propTypes = {
// mode: PropTypes.string,
userLike: PropTypes.bool,
totalLikes: PropTypes.number,
onLike: PropTypes.func
};
export default LikeDataEntry;

View File

@ -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<any, any> { // 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<MultiDataEntryProps> {
constructor(props) {
super(props);

View File

@ -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<NumericDataEntryProps> = (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;

View File

@ -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<SelectDataEntryProps> = (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;

View File

@ -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<TextboxDataEntryProps> = (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;

View File

@ -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<UPRNsDataEntryProps> = (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) => {
</dd>
</Fragment>
)
}
UPRNsDataEntry.propTypes = {
title: PropTypes.string,
tooltip: PropTypes.string,
value: PropTypes.arrayOf(PropTypes.shape({
uprn: PropTypes.string.isRequired,
parent_uprn: PropTypes.string
}))
}
};
export default UPRNsDataEntry;

View File

@ -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<any, any> { // 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<YearDataEntryProps, any> {
constructor(props) {
super(props);
this.state = {

View File

@ -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<CategoryViewProps>) => {
return class DataContainer extends React.Component<DataContainerProps, DataContainerState> {
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<CategoryViewProps>)
}
}
export default withCopyEdit;
export default withCopyEdit;

View File

@ -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<MultiEditRouteParams> {
user?: User;
}
const MultiEdit: React.FC<MultiEditProps> = (props) => {
if (!props.user){
return <Redirect to="/sign-up.html" />
}
@ -85,10 +92,4 @@ const MultiEdit = (props) => {
);
}
MultiEdit.propTypes = {
user: PropTypes.object,
match: PropTypes.object,
location: PropTypes.object
}
export default MultiEdit;

View File

@ -1,16 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import './sidebar.css';
const Sidebar = (props) => (
const Sidebar: React.FC<{}> = (props) => (
<div id="sidebar" className="info-container">
{ props.children }
</div>
);
Sidebar.propTypes = {
children: PropTypes.node
}
export default Sidebar;

View File

@ -58,4 +58,4 @@ const ConfirmationModal: React.FunctionComponent<ConfirmationModalProps> = ({
);
};
export default ConfirmationModal;
export default ConfirmationModal;

View File

@ -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<ErrorBoxProps> = (props) => {
if (props.msg) {
console.error(props.msg);
}
@ -12,7 +15,7 @@ function ErrorBox(props){
(
<div className="alert alert-danger" role="alert">
{
(typeof props.msg === 'string' || props.msg instanceof String)?
typeof props.msg === 'string' ?
props.msg
: 'Unexpected error'
}
@ -21,10 +24,6 @@ function ErrorBox(props){
}
</Fragment>
);
}
ErrorBox.propTypes = {
msg: PropTypes.string
}
};
export default ErrorBox;

View File

@ -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<InfoBoxProps> = (props) => (
<Fragment>
{
(props.msg || props.children)?
(
<div className="alert alert-info" role="alert">
{
(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) => (
</Fragment>
);
InfoBox.propTypes = {
msg: PropTypes.string,
children: PropTypes.node
}
export default InfoBox;

View File

@ -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<any, any> { // 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<TooltipProps, TooltipState> {
constructor(props) {
super(props);
this.state = {

View File

@ -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<HeaderProps, HeaderState> { // 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<HeaderProps, HeaderState> {
constructor(props) {
super(props);
this.state = {collapseMenu: true};

View File

@ -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<MapAppRouteParams> {
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<MapAppProps, MapAppState> {
static propTypes = {
category: PropTypes.string,
revision_id: PropTypes.number,
building: PropTypes.object,
building_like: PropTypes.bool,
user: PropTypes.object
};
constructor(props: Readonly<MapAppProps>) {
super(props);
@ -235,7 +227,7 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
</Route>
<Route exact path="/:mode/categories/:building?">
<Sidebar>
<Categories mode={mode} building_id={building_id} />
<Categories mode={mode || 'view'} building_id={building_id} />
</Sidebar>
</Route>
<Route exact path="/multi-edit/:cat" render={(props) => (

View File

@ -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<any, any> { // 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<LegendProps, LegendState> {
constructor(props) {
super(props);
this.state = {collapseList: false};

View File

@ -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<ColouringMapProps, ColouringMapState> {
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 = {

View File

@ -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<any, any> { // TODO: add proper types
static propTypes = { // TODO: generate propTypes from TS
onLocate: PropTypes.func
};
class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
constructor(props) {
super(props);
this.state = {

View File

@ -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<HTMLFormElement>) => void;
}
const ThemeSwitcher: React.FC<ThemeSwitcherProps> = (props) => (
<form className={`theme-switcher ${props.currentTheme}`} onSubmit={props.onSubmit}>
<button className="btn btn-outline btn-outline-dark"
type="submit">
@ -12,9 +16,4 @@ const ThemeSwitcher = (props) => (
</form>
);
ThemeSwitcher.propTypes = {
currentTheme: PropTypes.string,
onSubmit: PropTypes.func.isRequired
}
export default ThemeSwitcher;

View File

@ -1,5 +1,9 @@
interface User {
username: string;
username?: string;
email?: string;
registered?: Date;
api_key?: string;
error?: string;
// TODO: add other fields as needed
}

View File

@ -1,17 +1,17 @@
import React, { Component } from 'react';
import { Redirect, Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import ErrorBox from '../components/error-box';
import InfoBox from '../components/info-box';
import SupporterLogos from '../components/supporter-logos';
import { User } from '../models/user';
class Login extends Component<any, any> { // TODO: add proper types
static propTypes = { // TODO: generate propTypes from TS
login: PropTypes.func,
user: PropTypes.object
};
interface LoginProps {
user: User,
login: (user: User) => void;
}
class Login extends Component<LoginProps, any> {
constructor(props) {
super(props);
this.state = {

View File

@ -1,23 +1,23 @@
import React, { Component, FormEvent } from 'react';
import { Link, Redirect } from 'react-router-dom';
import PropTypes from 'prop-types';
import ConfirmationModal from '../components/confirmation-modal';
import ErrorBox from '../components/error-box';
import { User } from '../models/user';
class MyAccountPage extends Component<any, any> { // TODO: add proper types
static propTypes = { // TODO: generate propTypes from TS
user: PropTypes.shape({
username: PropTypes.string,
email: PropTypes.string,
registered: PropTypes.instanceOf(Date), // TODO: check if fix correct
api_key: PropTypes.string,
error: PropTypes.object
}),
updateUser: PropTypes.func,
logout: PropTypes.func
};
interface MyAccountPageProps {
user: User;
updateUser: (user: User) => void;
logout: () => void;
}
interface MyAccountPageState {
showDeleteConfirm: boolean;
error: string;
}
class MyAccountPage extends Component<MyAccountPageProps, MyAccountPageState> {
constructor(props) {
super(props);
this.state = {

View File

@ -1,4 +1,4 @@
import React, { ChangeEvent, FormEvent } from 'react';
import React, { FormEvent } from 'react';
import { RouteComponentProps, Redirect } from 'react-router';
import ErrorBox from '../components/error-box';

View File

@ -1,17 +1,27 @@
import React, { Component } from 'react';
import { Redirect, Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import ErrorBox from '../components/error-box';
import InfoBox from '../components/info-box';
import SupporterLogos from '../components/supporter-logos';
import { User } from '../models/user';
class SignUp extends Component<any, any> { // TODO: add proper types
static propTypes = { // TODO: generate propTypes from TS
login: PropTypes.func.isRequired,
user: PropTypes.object
};
interface SignUpProps {
login: (user: User) => void;
user: User;
}
interface SignUpState {
username: string;
email: string;
confirm_email: string;
show_password: boolean;
password: string;
confirm_conditions: boolean;
error: string;
}
class SignUp extends Component<SignUpProps, SignUpState> {
constructor(props) {
super(props);
this.state = {
@ -19,7 +29,7 @@ class SignUp extends Component<any, any> { // TODO: add proper types
email: '',
confirm_email: '',
password: '',
show_password: '',
show_password: false,
confirm_conditions: false,
error: undefined
};
@ -31,11 +41,11 @@ class SignUp extends Component<any, any> { // TODO: add proper types
handleChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
const name: keyof SignUpState = target.name;
this.setState({
[name]: value
});
} as Pick<SignUpState, keyof SignUpState>);
}
handleSubmit(event) {