Procházet zdrojové kódy

Flow type integration

All React components now have Flow integrated.
Sergiu Miclea před 8 roky
rodič
revize
dcc2942f2c
100 změnil soubory, kde provedl 1669 přidání a 1179 odebrání
  1. 1 0
      .babelrc
  2. 3 1
      .eslintignore
  3. 62 2
      .eslintrc
  4. 17 0
      .flowconfig
  5. 3 1
      .gitignore
  6. 7 0
      flow-typed/module_vx.x.x.js
  7. 8 2
      package.json
  8. 2 0
      src/actions/EndpointActions.js
  9. 2 0
      src/actions/InstanceActions.js
  10. 2 0
      src/actions/MigrationActions.js
  11. 2 0
      src/actions/NetworkActions.js
  12. 2 0
      src/actions/NotificationActions.js
  13. 2 0
      src/actions/ProjectActions.js
  14. 2 0
      src/actions/ProviderActions.js
  15. 2 0
      src/actions/ReplicaActions.js
  16. 2 0
      src/actions/ScheduleActions.js
  17. 2 0
      src/actions/UserActions.js
  18. 2 0
      src/actions/WizardActions.js
  19. 14 14
      src/components/App.jsx
  20. 11 10
      src/components/atoms/Arrow/index.jsx
  21. 3 6
      src/components/atoms/Button/index.jsx
  22. 10 10
      src/components/atoms/Checkbox/index.jsx
  23. 3 1
      src/components/atoms/CopyButton/index.jsx
  24. 10 10
      src/components/atoms/CopyValue/index.jsx
  25. 14 15
      src/components/atoms/DropdownButton/index.jsx
  26. 9 9
      src/components/atoms/EndpointLogos/index.jsx
  27. 5 1
      src/components/atoms/Fonts/index.js
  28. 7 8
      src/components/atoms/InfoIcon/index.jsx
  29. 8 8
      src/components/atoms/Logo/index.jsx
  30. 9 6
      src/components/atoms/PasswordValue/index.jsx
  31. 9 7
      src/components/atoms/ProgressBar/index.jsx
  32. 6 6
      src/components/atoms/RadioInput/index.jsx
  33. 8 5
      src/components/atoms/ReloadButton/index.jsx
  34. 7 7
      src/components/atoms/SearchButton/index.jsx
  35. 11 10
      src/components/atoms/StatusIcon/index.jsx
  36. 11 9
      src/components/atoms/StatusImage/index.jsx
  37. 12 12
      src/components/atoms/StatusPill/index.jsx
  38. 23 20
      src/components/atoms/Switch/index.jsx
  39. 3 1
      src/components/atoms/TextArea/index.jsx
  40. 16 8
      src/components/atoms/TextInput/index.jsx
  41. 9 8
      src/components/atoms/ToggleButtonBar/index.jsx
  42. 3 1
      src/components/atoms/Tooltip/index.jsx
  43. 21 13
      src/components/molecules/DatetimePicker/index.jsx
  44. 9 9
      src/components/molecules/DetailsNavigation/index.jsx
  45. 62 49
      src/components/molecules/Dropdown/index.jsx
  46. 20 10
      src/components/molecules/DropdownLink/index.jsx
  47. 23 25
      src/components/molecules/EndpointField/index.jsx
  48. 13 11
      src/components/molecules/EndpointListItem/index.jsx
  49. 9 9
      src/components/molecules/LoadingButton/index.jsx
  50. 10 10
      src/components/molecules/LoginFormField/index.jsx
  51. 5 9
      src/components/molecules/LoginOptions/index.jsx
  52. 20 16
      src/components/molecules/MainListFilter/index.jsx
  53. 21 17
      src/components/molecules/MainListItem/index.jsx
  54. 36 21
      src/components/molecules/Modal/index.jsx
  55. 22 9
      src/components/molecules/NewItemDropdown/index.jsx
  56. 15 8
      src/components/molecules/NotificationDropdown/index.jsx
  57. 17 12
      src/components/molecules/PropertiesTable/index.jsx
  58. 21 10
      src/components/molecules/SearchInput/index.jsx
  59. 7 1
      src/components/molecules/SideMenu/index.jsx
  60. 10 10
      src/components/molecules/Table/index.jsx
  61. 18 13
      src/components/molecules/TaskItem/index.jsx
  62. 20 11
      src/components/molecules/Timeline/index.jsx
  63. 16 8
      src/components/molecules/UserDropdown/index.jsx
  64. 8 8
      src/components/molecules/WizardBreadcrumbs/index.jsx
  65. 22 17
      src/components/molecules/WizardOptionsField/index.jsx
  66. 8 8
      src/components/molecules/WizardType/index.jsx
  67. 17 14
      src/components/organisms/AlertModal/index.jsx
  68. 12 10
      src/components/organisms/ChooseProvider/index.jsx
  69. 28 26
      src/components/organisms/DetailsContentHeader/index.jsx
  70. 13 11
      src/components/organisms/DetailsPageHeader/index.jsx
  71. 64 48
      src/components/organisms/Endpoint/index.jsx
  72. 22 14
      src/components/organisms/EndpointDetailsContent/index.jsx
  73. 13 11
      src/components/organisms/EndpointValidation/index.jsx
  74. 41 17
      src/components/organisms/Executions/index.jsx
  75. 53 44
      src/components/organisms/FilterList/index.jsx
  76. 25 22
      src/components/organisms/LoginForm/index.jsx
  77. 26 17
      src/components/organisms/MainDetails/index.jsx
  78. 28 20
      src/components/organisms/MainList/index.jsx
  79. 17 11
      src/components/organisms/MigrationDetailsContent/index.jsx
  80. 5 8
      src/components/organisms/Navigation/index.jsx
  81. 12 4
      src/components/organisms/Notifications/index.jsx
  82. 1 1
      src/components/organisms/Notifications/style.js
  83. 31 25
      src/components/organisms/PageHeader/index.jsx
  84. 35 24
      src/components/organisms/ReplicaDetailsContent/index.jsx
  85. 19 16
      src/components/organisms/ReplicaExecutionOptions/index.jsx
  86. 21 11
      src/components/organisms/ReplicaMigrationOptions/index.jsx
  87. 71 61
      src/components/organisms/Schedule/index.jsx
  88. 19 13
      src/components/organisms/Tasks/index.jsx
  89. 24 19
      src/components/organisms/WizardEndpointList/index.jsx
  90. 34 29
      src/components/organisms/WizardInstances/index.jsx
  91. 16 13
      src/components/organisms/WizardNetworks/index.jsx
  92. 19 15
      src/components/organisms/WizardOptions/index.jsx
  93. 53 49
      src/components/organisms/WizardPageContent/index.jsx
  94. 30 22
      src/components/organisms/WizardSummary/index.jsx
  95. 36 28
      src/components/pages/EndpointDetailsPage/index.jsx
  96. 40 27
      src/components/pages/EndpointsPage/index.jsx
  97. 15 9
      src/components/pages/LoginPage/index.jsx
  98. 20 16
      src/components/pages/MigrationDetailsPage/index.jsx
  99. 29 11
      src/components/pages/MigrationsPage/index.jsx
  100. 3 1
      src/components/pages/NotFoundPage/index.jsx

+ 1 - 0
.babelrc

@@ -6,6 +6,7 @@
         "modules": false
       }
     ],
+    "flow",
     "react",
     "stage-1"
   ],

+ 3 - 1
.eslintignore

@@ -1 +1,3 @@
-server.js
+# flow-typed
+flow-typed/npm/*
+!flow-typed/npm/module_vx.x.x.js

+ 62 - 2
.eslintrc

@@ -22,6 +22,20 @@
     }
   },
   "rules": {
+    "react/sort-comp": [1, {
+      "order": [
+        "static-methods",
+        "type-annotations",
+        "lifecycle",
+        "/^set.+$/",
+        "/^get.+$/",
+        "/^is.+$/",
+        "/^has.+$/",
+        "/^handle.+$/",
+        "everything-else",
+        "render"
+      ]
+    }],
     "semi": [2, "never"],
     "comma-dangle": [2, "always-multiline"],
     "newline-per-chained-call": 0,
@@ -45,6 +59,52 @@
     "import/prefer-default-export": 0,
     "react/require-default-props": 0,
     "react/forbid-prop-types": 0,
-    "jsx-a11y/href-no-hash": 0
-  }
+    "jsx-a11y/href-no-hash": 0,
+    "flowtype/boolean-style": [
+      "error",
+      "boolean"
+    ],
+    "flowtype/define-flow-type": "warn",
+    "flowtype/delimiter-dangle": [
+      "error",
+      "only-multiline"
+    ],
+    "flowtype/generic-spacing": [
+      "error",
+      "never"
+    ],
+    "flowtype/no-primitive-constructor-types": "error",
+    "flowtype/object-type-delimiter": [
+      "error",
+      "comma"
+    ],
+    "flowtype/require-parameter-type": "off",
+    "flowtype/require-return-type": "off",
+    "flowtype/require-valid-file-annotation": "off",
+    "flowtype/semi": [
+      "error",
+      "never"
+    ],
+    "flowtype/space-after-type-colon": [
+      "error",
+      "always"
+    ],
+    "flowtype/space-before-generic-bracket": [
+      "error",
+      "never"
+    ],
+    "flowtype/space-before-type-colon": [
+      "error",
+      "never"
+    ],
+    "flowtype/union-intersection-spacing": [
+      "error",
+      "always"
+    ],
+    "flowtype/use-flow-type": "error",
+    "flowtype/valid-syntax": "error"
+  },
+  "plugins": [
+    "flowtype"
+  ]
 }

+ 17 - 0
.flowconfig

@@ -0,0 +1,17 @@
+[ignore]
+<PROJECT_ROOT>/node_modules/*
+<PROJECT_ROOT>/dist/.*
+
+[libs]
+
+[options]
+esproposal.class_static_fields=enable
+esproposal.class_instance_fields=enable
+esproposal.export_star_as=enable
+module.name_mapper.extension='css' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'
+module.name_mapper.extension='styl' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'
+module.name_mapper.extension='scss' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'
+module.name_mapper.extension='png' -> '<PROJECT_ROOT>/internals/flow/WebpackAsset.js.flow'
+module.name_mapper.extension='jpg' -> '<PROJECT_ROOT>/internals/flow/WebpackAsset.js.flow'
+suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
+suppress_comment=\\(.\\|\n\\)*\\$FlowIgnore

+ 3 - 1
.gitignore

@@ -3,4 +3,6 @@
 dist
 *.log
 node_modules
-.vscode
+.vscode
+flow-typed/npm/*
+!flow-typed/npm/module_vx.x.x.js

+ 7 - 0
flow-typed/module_vx.x.x.js

@@ -0,0 +1,7 @@
+declare module 'module' {
+  declare module.exports: any;
+}
+
+declare module 'moment/locale/en-gb' {
+  declare module.exports: any;
+}

+ 8 - 2
package.json

@@ -12,7 +12,10 @@
     "build:clean": "rimraf \"dist/!(.git*|Procfile)**\"",
     "build:copy": "copyfiles -u 1 public/* public/**/* dist",
     "prebuild": "npm run build:clean && npm run build:copy",
-    "build": "npm run env:prod -- webpack"
+    "build": "npm run env:prod -- webpack",
+    "postinstall": "npm run flow-typed",
+    "flow": "flow",
+    "flow-typed": "rimraf flow-typed/npm && flow-typed install --overwrite || true"
   },
   "jest": {
     "verbose": true,
@@ -37,9 +40,12 @@
     "enzyme-adapter-react-16": "^1.0.4",
     "eslint": "^4.8.0",
     "eslint-config-airbnb": "^15.1.0",
+    "eslint-plugin-flowtype": "^2.46.1",
     "eslint-plugin-import": "^2.7.0",
     "eslint-plugin-jsx-a11y": "^6.0.2",
     "eslint-plugin-react": "^7.4.0",
+    "flow-bin": "^0.66.0",
+    "flow-typed": "^2.3.0",
     "jest": "^21.2.1",
     "react-test-renderer": "^16.0.0",
     "sinon": "^4.1.2",
@@ -56,6 +62,7 @@
     "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
     "babel-plugin-transform-react-remove-prop-types": "^0.4.9",
     "babel-preset-env": "^1.6.0",
+    "babel-preset-flow": "^6.23.0",
     "babel-preset-react": "^6.24.1",
     "babel-preset-stage-1": "^6.24.1",
     "babel-register": "^6.26.0",
@@ -70,7 +77,6 @@
     "lodash": "^4.17.4",
     "moment": "^2.18.1",
     "path": "^0.12.7",
-    "prop-types": "^15.6.0",
     "raw-loader": "^0.5.1",
     "react": "^16.0.0",
     "react-collapse": "^4.0.3",

+ 2 - 0
src/actions/EndpointActions.js

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import alt from '../alt'
 
 import EndpointSource from '../sources/EndpointSource'

+ 2 - 0
src/actions/InstanceActions.js

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import alt from '../alt'
 
 import InstanceSource from '../sources/InstanceSource'

+ 2 - 0
src/actions/MigrationActions.js

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import alt from '../alt'
 
 import MigrationSource from '../sources/MigrationSource'

+ 2 - 0
src/actions/NetworkActions.js

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import alt from '../alt'
 
 import NetworkSource from '../sources/NetworkSource'

+ 2 - 0
src/actions/NotificationActions.js

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import alt from '../alt'
 
 import NotificationSource from '../sources/NotificationSource'

+ 2 - 0
src/actions/ProjectActions.js

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import alt from '../alt'
 
 import PojectSource from '../sources/ProjectSource'

+ 2 - 0
src/actions/ProviderActions.js

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import alt from '../alt'
 
 import ProviderSource from '../sources/ProviderSource'

+ 2 - 0
src/actions/ReplicaActions.js

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import alt from '../alt'
 
 import ReplicaSource from '../sources/ReplicaSource'

+ 2 - 0
src/actions/ScheduleActions.js

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import alt from '../alt'
 
 import ScheduleSource from '../sources/ScheduleSource'

+ 2 - 0
src/actions/UserActions.js

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import alt from '../alt'
 
 import UserSource from '../sources/UserSource'

+ 2 - 0
src/actions/WizardActions.js

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import alt from '../alt'
 
 import WizardSource from '../sources/WizardSource'

+ 14 - 14
src/components/App.jsx

@@ -12,23 +12,23 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import { Switch, Route } from 'react-router-dom'
 import styled, { injectGlobal } from 'styled-components'
 
-import {
-  LoginPage,
-  Fonts,
-  Notifications,
-  NotFoundPage,
-  ReplicasPage,
-  ReplicaDetailsPage,
-  MigrationsPage,
-  MigrationDetailsPage,
-  EndpointsPage,
-  EndpointDetailsPage,
-  WizardPage,
-} from 'components'
+import Fonts from './atoms/Fonts'
+import Notifications from './organisms/Notifications'
+import LoginPage from './pages/LoginPage'
+import ReplicasPage from './pages/ReplicasPage'
+import NotFoundPage from './pages/NotFoundPage'
+import ReplicaDetailsPage from './pages/ReplicaDetailsPage'
+import MigrationsPage from './pages/MigrationsPage'
+import MigrationDetailsPage from './pages/MigrationDetailsPage'
+import EndpointsPage from './pages/EndpointsPage'
+import EndpointDetailsPage from './pages/EndpointDetailsPage'
+import WizardPage from './pages/WizardPage'
 
 import Palette from './styleUtils/Palette'
 import StyleProps from './styleUtils/StyleProps'
@@ -48,7 +48,7 @@ injectGlobal`
 `
 const Wrapper = styled.div``
 
-class App extends React.Component {
+class App extends React.Component<{}> {
   componentWillMount() {
     UserActions.tokenLogin()
   }

+ 11 - 10
src/components/atoms/Arrow/Arrow.jsx → src/components/atoms/Arrow/index.jsx

@@ -12,9 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -39,16 +40,16 @@ const Wrapper = styled.div`
   ${props => getOrientation(props)}
 `
 
-class Arrow extends React.Component {
-  static propTypes = {
-    primary: PropTypes.bool,
-    useDefaultCursor: PropTypes.bool,
-    orientation: PropTypes.string,
-    opacity: PropTypes.number,
-    disabled: PropTypes.bool,
-  }
+type Props = {
+  primary?: boolean,
+  useDefaultCursor?: boolean,
+  orientation: 'left' | 'down' | 'up' | 'right',
+  opacity: number,
+  disabled?: boolean,
+}
 
-  static defaultProps = {
+class Arrow extends React.Component<Props> {
+  static defaultProps: Props = {
     orientation: 'right',
     opacity: 1,
   }

+ 3 - 6
src/components/atoms/Button/Button.jsx → src/components/atoms/Button/index.jsx

@@ -12,8 +12,9 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 
 import Palette from '../../styleUtils/Palette'
@@ -106,14 +107,10 @@ const StyledButton = styled.button`
   }
 `
 
-const Button = ({ ...props }) => {
+const Button = (props: any) => {
   return (
     <StyledButton {...props} onFocus={e => { e.target.blur() }} />
   )
 }
 
-Button.propTypes = {
-  children: PropTypes.node,
-}
-
 export default Button

+ 10 - 10
src/components/atoms/Checkbox/Checkbox.jsx → src/components/atoms/Checkbox/index.jsx

@@ -12,9 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled, { css } from 'styled-components'
-import PropTypes from 'prop-types'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -48,16 +49,15 @@ const Wrapper = styled.div`
   ` : ''}
 `
 
-class Checkbox extends React.Component {
-  static propTypes = {
-    className: PropTypes.string,
-    checked: PropTypes.bool,
-    disabled: PropTypes.bool,
-    onChange: PropTypes.func.isRequired,
-  }
-
+type Props = {
+  className?: string,
+  checked?: boolean,
+  disabled?: boolean,
+  onChange?: (checked: boolean) => void,
+}
+class Checkbox extends React.Component<Props> {
   handleClick() {
-    if (this.props.disabled) {
+    if (this.props.disabled || !this.props.onChange) {
       return
     }
 

+ 3 - 1
src/components/atoms/CopyButton/CopyButton.jsx → src/components/atoms/CopyButton/index.jsx

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
 
@@ -29,7 +31,7 @@ const Wrapper = styled.span`
   transition: all ${StyleProps.animations.swift};
 `
 
-class CopyButton extends React.Component {
+class CopyButton extends React.Component<{}> {
   render() {
     return (
       <Wrapper {...this.props} />

+ 10 - 10
src/components/atoms/CopyValue/CopyValue.jsx → src/components/atoms/CopyValue/index.jsx

@@ -12,11 +12,12 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { CopyButton } from 'components'
+import CopyButton from '../CopyButton'
 import NotificationActions from '../../../actions/NotificationActions'
 import DomUtils from '../../../utils/DomUtils'
 
@@ -37,14 +38,13 @@ const Value = styled.span`
   margin-right: 4px;
 `
 
-class CopyValue extends React.Component {
-  static propTypes = {
-    value: PropTypes.string,
-    width: PropTypes.string,
-    maxWidth: PropTypes.string,
-  }
-
-  handleCopyIdClick(e) {
+type Props = {
+  value: string,
+  width?: string,
+  maxWidth?: string,
+}
+class CopyValue extends React.Component<Props> {
+  handleCopyIdClick(e: Event) {
     e.stopPropagation()
 
     let succesful = DomUtils.copyTextToClipboard(this.props.value)

+ 14 - 15
src/components/atoms/DropdownButton/DropdownButton.jsx → src/components/atoms/DropdownButton/index.jsx

@@ -12,8 +12,9 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 
 import arrowImage from './images/arrow.js'
@@ -112,26 +113,24 @@ const Arrow = styled.div`
   top: 12px;
   display: flex;
 `
-
-const DropdownButton = ({ value, onClick, className, disabled, ...props }) => {
+type Props = {
+  value: string,
+  onClick?: (event: Event) => void,
+  className?: string,
+  disabled?: boolean,
+}
+const DropdownButton = (props: Props) => {
   return (
     <Wrapper
-      onClick={e => { disabled ? null : onClick(e) }}
-      className={className}
-      disabled={disabled}
+      onClick={e => { props.disabled ? null : props.onClick && props.onClick(e) }}
+      className={props.className}
+      disabled={props.disabled}
       {...props}
     >
-      <Label {...props} disabled={disabled}>{value}</Label>
-      <Arrow {...props} disabled={disabled} dangerouslySetInnerHTML={{ __html: arrowImage }} />
+      <Label {...props} disabled={props.disabled}>{props.value}</Label>
+      <Arrow {...props} disabled={props.disabled} dangerouslySetInnerHTML={{ __html: arrowImage }} />
     </Wrapper>
   )
 }
 
-DropdownButton.propTypes = {
-  value: PropTypes.string,
-  className: PropTypes.string,
-  onClick: PropTypes.func,
-  disabled: PropTypes.bool,
-}
-
 export default DropdownButton

+ 9 - 9
src/components/atoms/EndpointLogos/EndpointLogos.jsx → src/components/atoms/EndpointLogos/index.jsx

@@ -12,8 +12,9 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 
 import aws32Image from './images/aws-32.svg'
@@ -108,14 +109,13 @@ const widthHeights = [
   { w: 192, h: 64 },
 ]
 
-class EndpointLogos extends React.Component {
-  static propTypes = {
-    endpoint: PropTypes.string,
-    height: PropTypes.number,
-    disabled: PropTypes.bool,
-  }
-
-  static defaultProps = {
+type Props = {
+  endpoint?: string,
+  height: number,
+  disabled?: boolean,
+}
+class EndpointLogos extends React.Component<Props> {
+  static defaultProps: Props = {
     height: 64,
   }
 

+ 5 - 1
src/components/atoms/Fonts/Fonts.js → src/components/atoms/Fonts/index.js

@@ -12,6 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
+import { css } from 'styled-components'
+
 import RubikRegular from './Rubik-Regular.woff'
 import RubikItalic from './Rubik-Italic.woff'
 import RubikBold from './Rubik-Bold.woff'
@@ -20,7 +24,7 @@ import RubikLightItalic from './Rubik-LightItalic.woff'
 import RubikMedium from './Rubik-Medium.woff'
 import RubikMediumItalic from './Rubik-MediumItalic.woff'
 
-const Fonts = `
+const Fonts = css`
   @font-face {
     font-family: 'Rubik';
     src: url('${RubikRegular}') format('woff');

+ 7 - 8
src/components/atoms/InfoIcon/InfoIcon.jsx → src/components/atoms/InfoIcon/index.jsx

@@ -12,9 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
 import questionImage from './images/question.svg'
 
@@ -26,13 +27,11 @@ const Wrapper = styled.div`
   margin-bottom: -4px;
   margin-left: ${props => props.marginLeft ? `${props.marginLeft}px` : '4px'};
 `
-
-class InfoIcon extends React.Component {
-  static propTypes = {
-    text: PropTypes.string,
-    marginLeft: PropTypes.number,
-  }
-
+type Props = {
+  text: string,
+  marginLeft: number,
+}
+class InfoIcon extends React.Component<Props> {
   render() {
     return (
       <Wrapper data-tip={this.props.text} marginLeft={this.props.marginLeft} />

+ 8 - 8
src/components/atoms/Logo/Logo.jsx → src/components/atoms/Logo/index.jsx

@@ -12,8 +12,9 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled, { css } from 'styled-components'
 
 import StyleProps from '../../styleUtils/StyleProps'
@@ -42,17 +43,16 @@ const Coriolis = styled.div`
   ` : ''}
 `
 
-const Logo = ({ large, small, ...props }) => {
+type Props = {
+  large?: boolean,
+  small?: boolean,
+}
+const Logo = (props: Props) => {
   return (
     <Wrapper {...props}>
-      <Coriolis large={large} small={small} />
+      <Coriolis large={props.large} small={props.small} />
     </Wrapper>
   )
 }
 
-Logo.propTypes = {
-  large: PropTypes.bool,
-  small: PropTypes.bool,
-}
-
 export default Logo

+ 9 - 6
src/components/atoms/PasswordValue/PasswordValue.jsx → src/components/atoms/PasswordValue/index.jsx

@@ -12,9 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
 import StyleProps from '../../styleUtils/StyleProps'
 
@@ -44,11 +45,13 @@ const Value = styled.span`
   margin-right: 4px;
 `
 
-class PasswordValue extends React.Component {
-  static propTypes = {
-    value: PropTypes.string,
-  }
-
+type Props = {
+  value: string,
+}
+type State = {
+  show: boolean,
+}
+class PasswordValue extends React.Component<Props, State> {
   constructor() {
     super()
 

+ 9 - 7
src/components/atoms/ProgressBar/ProgressBar.jsx → src/components/atoms/ProgressBar/index.jsx

@@ -12,13 +12,19 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 
+type Props = {
+  progress: number,
+  width?: number,
+}
+
 const Wrapper = styled.div`
   background: white;
 `
@@ -26,14 +32,10 @@ const Progress = styled.div`
   height: 2px;
   background: ${Palette.primary};
   transition: all ${StyleProps.animations.swift};
-  width: ${props => props.width}%;
+  width: ${(props: Props) => props.width}%;
 `
 
-class ProgressBar extends React.Component {
-  static propTypes = {
-    progress: PropTypes.number,
-  }
-
+class ProgressBar extends React.Component<Props> {
   render() {
     return (
       <Wrapper {...this.props}>

+ 6 - 6
src/components/atoms/RadioInput/RadioInput.jsx → src/components/atoms/RadioInput/index.jsx

@@ -12,9 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -49,11 +50,10 @@ const InputStyled = styled.input`
   }
 `
 
-class RadioInput extends React.Component {
-  static propTypes = {
-    label: PropTypes.string,
-  }
-
+type Props = {
+  label: string,
+}
+class RadioInput extends React.Component<Props> {
   render() {
     return (
       <Wrapper {...this.props}>

+ 8 - 5
src/components/atoms/ReloadButton/ReloadButton.jsx → src/components/atoms/ReloadButton/index.jsx

@@ -12,9 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled, { injectGlobal } from 'styled-components'
-import PropTypes from 'prop-types'
 
 import reloadImage from './images/reload.svg'
 
@@ -32,10 +33,12 @@ injectGlobal`
   }
 `
 
-class ReloadButton extends React.Component {
-  static propTypes = {
-    onClick: PropTypes.func,
-  }
+type Props = {
+  onClick: () => void,
+}
+class ReloadButton extends React.Component<Props> {
+  wrapper: HTMLElement
+  timeout: ?TimeoutID
 
   onClick() {
     if (this.timeout) {

+ 7 - 7
src/components/atoms/SearchButton/SearchButton.jsx → src/components/atoms/SearchButton/index.jsx

@@ -12,8 +12,9 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 
 import Palette from '../../styleUtils/Palette'
@@ -31,12 +32,11 @@ const Icon = styled.div`
   align-items: center;
 `
 
-class SearchButton extends React.Component {
-  static propTypes = {
-    className: PropTypes.string,
-    primary: PropTypes.bool,
-  }
-
+type Props = {
+  className: string,
+  primary: boolean,
+}
+class SearchButton extends React.Component<Props> {
   render() {
     return (
       <Wrapper className={this.props.className} {...this.props}>

+ 11 - 10
src/components/atoms/StatusIcon/StatusIcon.jsx → src/components/atoms/StatusIcon/index.jsx

@@ -12,9 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled, { css } from 'styled-components'
-import PropTypes from 'prop-types'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -26,14 +27,19 @@ import successImage from './images/success.svg'
 import warningImage from './images/warning.svg'
 import pendingImage from './images/pending.svg'
 
-const getRunningImageUrl = props => {
+type Props = {
+  status: string,
+  useBackground?: boolean,
+}
+
+const getRunningImageUrl = (props: Props) => {
   const smallCircleColor = props.secondary ? Palette.grayscale[0] : Palette.primary
 
   if (props.useBackground) {
-    return `url('${progressWithBackgroundImage}')`
+    return css`url('${progressWithBackgroundImage}')`
   }
 
-  return `url('data:image/svg+xml;utf8,${encodeURIComponent(progressImage(Palette.grayscale[3], smallCircleColor))}')`
+  return css`url('data:image/svg+xml;utf8,${encodeURIComponent(progressImage(Palette.grayscale[3], smallCircleColor))}')`
 }
 
 const statuses = props => {
@@ -69,12 +75,7 @@ const Wrapper = styled.div`
   ${props => statuses(props)[props.status]}
 `
 
-class StatusIcon extends React.Component {
-  static propTypes = {
-    status: PropTypes.string.isRequired,
-    useBackground: PropTypes.bool,
-  }
-
+class StatusIcon extends React.Component<Props> {
   render() {
     return (
       <Wrapper {...this.props} />

+ 11 - 9
src/components/atoms/StatusImage/StatusImage.jsx → src/components/atoms/StatusImage/index.jsx

@@ -12,15 +12,21 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled, { css } from 'styled-components'
-import PropTypes from 'prop-types'
 import StyleProps from '../../styleUtils/StyleProps'
 
 import errorImage from './images/error.svg'
 import successImage from './images/success.svg'
 import loadingImage from './images/loading.svg'
 
+type Props = {
+  status?: string,
+  loading?: boolean,
+}
+
 const statuses = () => {
   return {
     ERROR: css`
@@ -45,16 +51,12 @@ const Wrapper = styled.div`
   ${StyleProps.exactSize('96px')}
   background-repeat: no-repeat;
   background-position: center;
-  ${props => statuses(props)[props.status]}
+  ${ // $FlowIssue
+  (props: Props) => statuses()[props.status]}
 `
 
-class StatusImage extends React.Component {
-  static propTypes = {
-    status: PropTypes.string,
-    loading: PropTypes.bool,
-  }
-
-  static defaultPropTypes = {
+class StatusImage extends React.Component<Props> {
+  static defaultProps: $Shape<Props> = {
     status: 'RUNNING',
   }
 

+ 12 - 12
src/components/atoms/StatusPill/StatusPill.jsx → src/components/atoms/StatusPill/index.jsx

@@ -12,8 +12,9 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled, { css } from 'styled-components'
 
 import Palette from '../../styleUtils/Palette'
@@ -93,17 +94,16 @@ const Wrapper = styled.div`
   ${props => props.status === 'INFO' ? getInfoStatusColor(props) : ''}
 `
 
-class StatusPill extends React.Component {
-  static propTypes = {
-    status: PropTypes.string,
-    label: PropTypes.string,
-    primary: PropTypes.bool,
-    secondary: PropTypes.bool,
-    alert: PropTypes.bool,
-    small: PropTypes.bool,
-  }
-
-  static defaultProps = {
+type Props = {
+  status: ?string,
+  label: string,
+  primary: boolean,
+  secondary: boolean,
+  alert: boolean,
+  small: boolean,
+}
+class StatusPill extends React.Component<Props> {
+  static defaultProps: $Shape<Props> = {
     status: 'INFO',
   }
 

+ 23 - 20
src/components/atoms/Switch/Switch.jsx → src/components/atoms/Switch/index.jsx

@@ -12,9 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -110,25 +111,27 @@ const LeftLabel = styled.div`
   white-space: nowrap;
 `
 
-class Switch extends React.Component {
-  static propTypes = {
-    onChange: PropTypes.func,
-    checked: PropTypes.bool,
-    disabled: PropTypes.bool,
-    triState: PropTypes.bool,
-    leftLabel: PropTypes.bool,
-    secondary: PropTypes.bool,
-    noLabel: PropTypes.bool,
-    height: PropTypes.number,
-    width: PropTypes.string,
-    justifyContent: PropTypes.string,
-    big: PropTypes.bool,
-    checkedLabel: PropTypes.string,
-    uncheckedLabel: PropTypes.string,
-    style: PropTypes.object,
-  }
-
-  static defaultProps = {
+type Props = {
+  onChange: (checked: ?boolean) => void,
+  checked: boolean,
+  disabled: boolean,
+  triState: boolean,
+  leftLabel: boolean,
+  secondary: boolean,
+  noLabel: boolean,
+  height: number,
+  width: string,
+  justifyContent: string,
+  big: boolean,
+  checkedLabel: string,
+  uncheckedLabel: string,
+  style: {[string]: mixed},
+}
+type State = {
+  lastChecked: ?boolean,
+}
+class Switch extends React.Component<Props, State> {
+  static defaultProps: $Shape<Props> = {
     checkedLabel: 'Yes',
     uncheckedLabel: 'No',
     height: 24,

+ 3 - 1
src/components/atoms/TextArea/TextArea.jsx → src/components/atoms/TextArea/index.jsx

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
 import Palette from '../../styleUtils/Palette'
@@ -58,7 +60,7 @@ const Input = styled.textarea`
   }
 `
 
-class TextArea extends React.Component {
+class TextArea extends React.Component<{}> {
   render() {
     return (
       <Input {...this.props} />

+ 16 - 8
src/components/atoms/TextInput/TextInput.jsx → src/components/atoms/TextInput/index.jsx

@@ -12,8 +12,9 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import React from 'react'
-import PropTypes from 'prop-types'
+// @flow
+
+import * as React from 'react'
 import styled from 'styled-components'
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -72,7 +73,19 @@ const Required = styled.div`
   background: url('${starImage}') center no-repeat;
 `
 
-const TextInput = ({ _ref, required, ...props }) => {
+type Props = {
+  _ref?: (ref: HTMLElement) => void,
+  required?: boolean,
+  disabled?: boolean,
+  highlight?: boolean,
+  large?: boolean,
+  onChange?: (e: SyntheticInputEvent<EventTarget>) => void,
+  placeholder?: string,
+  type?: string,
+  value?: string,
+}
+const TextInput = (props: Props) => {
+  const { _ref, required } = props
   return (
     <Wrapper>
       <Input innerRef={_ref} type="text" customRequired={required} {...props} />
@@ -81,9 +94,4 @@ const TextInput = ({ _ref, required, ...props }) => {
   )
 }
 
-TextInput.propTypes = {
-  _ref: PropTypes.func,
-  required: PropTypes.bool,
-}
-
 export default TextInput

+ 9 - 8
src/components/atoms/ToggleButtonBar/ToggleButtonBar.jsx → src/components/atoms/ToggleButtonBar/index.jsx

@@ -12,9 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -49,13 +50,13 @@ const Item = styled.div`
   }
 `
 
-class ToggleButtonBar extends React.Component {
-  static propTypes = {
-    items: PropTypes.array,
-    selectedValue: PropTypes.string,
-    onChange: PropTypes.func,
-  }
-
+type ItemType = { value: string, label: string }
+type Props = {
+  items: Array<ItemType>,
+  selectedValue: string,
+  onChange: (item: ItemType) => void,
+}
+class ToggleButtonBar extends React.Component<Props> {
   render() {
     if (!this.props.items) {
       return null

+ 3 - 1
src/components/atoms/Tooltip/Tooltip.jsx → src/components/atoms/Tooltip/index.jsx

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import { injectGlobal } from 'styled-components'
 import ReactTooltip from 'react-tooltip'
@@ -38,7 +40,7 @@ injectGlobal`
   }
 `
 
-class Tooltip extends React.Component {
+class Tooltip extends React.Component<{}> {
   static rebuild = () => {
     ReactTooltip.rebuild()
   }

+ 21 - 13
src/components/molecules/DatetimePicker/DatetimePicker.jsx → src/components/molecules/DatetimePicker/index.jsx

@@ -12,13 +12,14 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled, { injectGlobal } from 'styled-components'
 import Datetime from 'react-datetime'
-import PropTypes from 'prop-types'
 import moment from 'moment'
 
-import { DropdownButton } from 'components'
+import DropdownButton from '../../atoms/DropdownButton'
 
 import DomUtils from '../../../utils/DomUtils'
 import DateUtils from '../../../utils/DateUtils'
@@ -48,13 +49,18 @@ const DatetimeStyled = styled(Datetime)`
   }
 `
 
-class DatetimePicker extends React.Component {
-  static propTypes = {
-    value: PropTypes.object,
-    onChange: PropTypes.func.isRequired,
-    isValidDate: PropTypes.func,
-    timezone: PropTypes.string,
-  }
+type Props = {
+  value: ?Date,
+  onChange: (date: Date) => void,
+  isValidDate: (currentDate: Date, selectedDate: Date) => boolean,
+  timezone: 'utc' | 'local'
+}
+type State = {
+  showPicker: boolean,
+  date: ?Date,
+}
+class DatetimePicker extends React.Component<Props, State> {
+  itemMouseDown: boolean
 
   constructor() {
     super()
@@ -63,7 +69,9 @@ class DatetimePicker extends React.Component {
       showPicker: false,
       date: null,
     }
-    this.handlePageClick = this.handlePageClick.bind(this)
+
+    const self: any = this
+    self.handlePageClick = this.handlePageClick.bind(this)
   }
 
   componentWillMount() {
@@ -78,7 +86,7 @@ class DatetimePicker extends React.Component {
     window.removeEventListener('mousedown', this.handlePageClick, false)
   }
 
-  isValidDate(currentDate, selectedDate) {
+  isValidDate(currentDate: Date, selectedDate: Date): boolean {
     if (!this.props.isValidDate) {
       return true
     }
@@ -86,7 +94,7 @@ class DatetimePicker extends React.Component {
     return this.props.isValidDate(currentDate, selectedDate)
   }
 
-  handlePageClick(e) {
+  handlePageClick(e: Event) {
     let path = DomUtils.getEventPath(e)
 
     if (!this.itemMouseDown && !path.find(n => n.className === 'rdtPicker')) {
@@ -105,7 +113,7 @@ class DatetimePicker extends React.Component {
     this.setState({ showPicker: !this.state.showPicker })
   }
 
-  handleChange(date) {
+  handleChange(date: Date) {
     if (this.props.timezone === 'utc') {
       date = DateUtils.getLocalTime(date)
     }

+ 9 - 9
src/components/molecules/DetailsNavigation/DetailsNavigation.jsx → src/components/molecules/DetailsNavigation/index.jsx

@@ -12,9 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -32,14 +33,13 @@ const Item = styled.a`
   text-decoration: none;
 `
 
-class DetailsNavigation extends React.Component {
-  static propTypes = {
-    items: PropTypes.array.isRequired,
-    selectedValue: PropTypes.string,
-    itemId: PropTypes.string,
-    itemType: PropTypes.string,
-  }
-
+type Props = {
+  items: { label: string, value: string }[],
+  selectedValue: string,
+  itemId: string,
+  itemType: string,
+}
+class DetailsNavigation extends React.Component<Props> {
   renderItems() {
     return (
       this.props.items.map(item => (

+ 62 - 49
src/components/molecules/Dropdown/Dropdown.jsx → src/components/molecules/Dropdown/index.jsx

@@ -12,12 +12,13 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 import ReactDOM from 'react-dom'
 
-import { DropdownButton } from 'components'
+import DropdownButton from '../../atoms/DropdownButton'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -87,31 +88,42 @@ const ListItem = styled.div`
   }
 `
 
-class Dropdown extends React.Component {
-  static propTypes = {
-    selectedItem: PropTypes.any,
-    items: PropTypes.array,
-    labelField: PropTypes.string,
-    className: PropTypes.string,
-    onChange: PropTypes.func,
-    noItemsMessage: PropTypes.string,
-    noSelectionMessage: PropTypes.string,
-    disabled: PropTypes.bool,
-    width: PropTypes.number,
-  }
-
-  static defaultProps = {
+type Props = {
+  selectedItem: any,
+  items: any[],
+  labelField: string,
+  className: string,
+  onChange: (item: any) => void,
+  noItemsMessage: string,
+  noSelectionMessage: string,
+  disabled: boolean,
+  width: number,
+}
+type State = {
+  showDropdownList: boolean,
+  firstItemHover: boolean
+}
+class Dropdown extends React.Component<Props, State> {
+  static defaultProps: $Shape<Props> = {
     noSelectionMessage: 'Select an item',
   }
 
+  buttonRef: HTMLElement
+  listRef: HTMLElement
+  tipRef: HTMLElement
+  buttonRect: ClientRect
+  itemMouseDown: boolean
+
   constructor() {
     super()
 
     this.state = {
       showDropdownList: false,
+      firstItemHover: false,
     }
 
-    this.handlePageClick = this.handlePageClick.bind(this)
+    const self: any = this
+    self.handlePageClick = this.handlePageClick.bind(this)
   }
 
   componentDidMount() {
@@ -131,7 +143,7 @@ class Dropdown extends React.Component {
     window.removeEventListener('mousedown', this.handlePageClick, false)
   }
 
-  getLabel(item) {
+  getLabel(item: any) {
     let labelField = this.props.labelField || 'label'
 
     if (item === null || item === undefined) {
@@ -141,33 +153,6 @@ class Dropdown extends React.Component {
     return (item[labelField] !== null && item[labelField] !== undefined && item[labelField].toString()) || item.toString()
   }
 
-  updateListPosition() {
-    if (!this.state.showDropdownList || !this.listRef || !this.buttonRef) {
-      return
-    }
-
-    let buttonHeight = this.buttonRef.offsetHeight
-    let tipHeight = 8
-    let listTop = this.buttonRect.top + buttonHeight + tipHeight
-    let listHeight = this.listRef.offsetHeight
-
-    if (listTop + listHeight > window.innerHeight) {
-      listTop = window.innerHeight - listHeight - 10
-      this.tipRef.style.display = 'none'
-    } else {
-      this.tipRef.style.display = 'block'
-    }
-
-    // If a modal is opened, body scroll is removed and body top is set to replicate scroll position
-    let scrollOffset = 0
-    if (parseInt(document.body.style.top, 10) < 0) {
-      scrollOffset = -parseInt(document.body.style.top, 10)
-    }
-
-    this.listRef.style.top = `${listTop + (window.pageYOffset || scrollOffset)}px`
-    this.listRef.style.left = `${this.buttonRect.left}px`
-  }
-
   handlePageClick() {
     if (!this.itemMouseDown) {
       this.setState({ showDropdownList: false })
@@ -182,7 +167,7 @@ class Dropdown extends React.Component {
     this.setState({ showDropdownList: !this.state.showDropdownList })
   }
 
-  handleItemClick(item) {
+  handleItemClick(item: any) {
     this.setState({ showDropdownList: false, firstItemHover: false })
 
     if (this.props.onChange) {
@@ -190,23 +175,51 @@ class Dropdown extends React.Component {
     }
   }
 
-  handleItemMouseEnter(index) {
+  handleItemMouseEnter(index: number) {
     if (index === 0) {
       this.setState({ firstItemHover: true })
     }
   }
 
-  handleItemMouseLeave(index) {
+  handleItemMouseLeave(index: number) {
     if (index === 0) {
       this.setState({ firstItemHover: false })
     }
   }
 
+  updateListPosition() {
+    if (!this.state.showDropdownList || !this.listRef || !this.buttonRef || !document.body) {
+      return
+    }
+
+    let buttonHeight = this.buttonRef.offsetHeight
+    let tipHeight = 8
+    let listTop = this.buttonRect.top + buttonHeight + tipHeight
+    let listHeight = this.listRef.offsetHeight
+
+    if (listTop + listHeight > window.innerHeight) {
+      listTop = window.innerHeight - listHeight - 10
+      this.tipRef.style.display = 'none'
+    } else {
+      this.tipRef.style.display = 'block'
+    }
+
+    // If a modal is opened, body scroll is removed and body top is set to replicate scroll position
+    let scrollOffset = 0
+    if (parseInt(document.body.style.top, 10) < 0) {
+      scrollOffset = -parseInt(document.body && document.body.style.top, 10)
+    }
+
+    this.listRef.style.top = `${listTop + (window.pageYOffset || scrollOffset)}px`
+    this.listRef.style.left = `${this.buttonRect.left}px`
+  }
+
   renderList() {
     if (!this.props.items || this.props.items.length === 0 || !this.state.showDropdownList) {
       return null
     }
 
+    const body: any = document.body
     let selectedLabel = this.getLabel(this.props.selectedItem)
     let list = ReactDOM.createPortal((
       <List {...this.props} innerRef={ref => { this.listRef = ref }}>
@@ -231,7 +244,7 @@ class Dropdown extends React.Component {
           })}
         </ListItems>
       </List>
-    ), document.body)
+    ), body)
 
     return list
   }

+ 20 - 10
src/components/molecules/DropdownLink/DropdownLink.jsx → src/components/molecules/DropdownLink/index.jsx

@@ -12,9 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
 import Palette from '../../styleUtils/Palette'
 
@@ -87,12 +88,18 @@ const Arrow = styled.div`
   margin-top: -1px;
 `
 
-class DropdownLink extends React.Component {
-  static propTypes = {
-    selectedItem: PropTypes.string.isRequired,
-    items: PropTypes.array.isRequired,
-    onChange: PropTypes.func.isRequired,
-  }
+type ItemType = {
+  label: string,
+  value: string
+}
+type Props = {
+  selectedItem: string,
+  items: ItemType[],
+  onChange: (item: ItemType) => void
+}
+type State = {showDropdownList: boolean}
+class DropdownLink extends React.Component<Props, State> {
+  itemMouseDown: boolean
 
   constructor() {
     super()
@@ -101,7 +108,8 @@ class DropdownLink extends React.Component {
       showDropdownList: false,
     }
 
-    this.handlePageClick = this.handlePageClick.bind(this)
+    const self: any = this
+    self.handlePageClick = this.handlePageClick.bind(this)
   }
 
   componentDidMount() {
@@ -122,7 +130,7 @@ class DropdownLink extends React.Component {
     this.setState({ showDropdownList: !this.state.showDropdownList })
   }
 
-  handleItemClick(item) {
+  handleItemClick(item: ItemType) {
     this.setState({ showDropdownList: false })
 
     if (this.props.onChange) {
@@ -158,6 +166,8 @@ class DropdownLink extends React.Component {
   }
 
   render() {
+    let selectedItem = this.props.items.find(i => i.value === this.props.selectedItem)
+
     return (
       <Wrapper>
         <LinkButton
@@ -165,7 +175,7 @@ class DropdownLink extends React.Component {
           onMouseUp={() => { this.itemMouseDown = false }}
           onClick={() => this.handleButtonClick()}
         >
-          <Label>{this.props.items.find(i => i.value === this.props.selectedItem).label}</Label>
+          <Label>{selectedItem ? selectedItem.label : ''}</Label>
           <Arrow />
         </LinkButton>
         {this.renderList()}

+ 23 - 25
src/components/molecules/EndpointField/EndpointField.jsx → src/components/molecules/EndpointField/index.jsx

@@ -12,17 +12,16 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import {
-  Switch,
-  TextInput,
-  Dropdown,
-  RadioInput,
-  InfoIcon,
-} from 'components'
+import Switch from '../../atoms/Switch'
+import TextInput from '../../atoms/TextInput'
+import RadioInput from '../../atoms/RadioInput'
+import InfoIcon from '../../atoms/InfoIcon'
+import Dropdown from '../../molecules/Dropdown'
 
 import LabelDictionary from '../../../utils/LabelDictionary'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -40,23 +39,22 @@ const LabelText = styled.span`
   margin-right: 24px;
 `
 
-class Field extends React.Component {
-  static propTypes = {
-    name: PropTypes.string,
-    type: PropTypes.string,
-    value: PropTypes.any,
-    onChange: PropTypes.func,
-    className: PropTypes.string,
-    minimum: PropTypes.number,
-    maximum: PropTypes.number,
-    password: PropTypes.bool,
-    required: PropTypes.bool,
-    large: PropTypes.bool,
-    highlight: PropTypes.bool,
-    disabled: PropTypes.bool,
-    enum: PropTypes.array,
-  }
-
+type Props = {
+  name: string,
+  type: string,
+  value: any,
+  onChange: (value: any) => void,
+  className: string,
+  minimum: number,
+  maximum: number,
+  password: boolean,
+  required: boolean,
+  large: boolean,
+  highlight: boolean,
+  disabled: boolean,
+  enum: string[],
+}
+class Field extends React.Component<Props> {
   renderSwitch() {
     return (
       <Switch

+ 13 - 11
src/components/molecules/EndpointListItem/EndpointListItem.jsx → src/components/molecules/EndpointListItem/index.jsx

@@ -12,11 +12,14 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 
-import { Checkbox, EndpointLogos } from 'components'
+import type { Endpoint } from '../../../types/Endpoint'
+import Checkbox from '../../atoms/Checkbox'
+import EndpointLogos from '../../atoms/EndpointLogos'
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 import DateUtils from '../../../utils/DateUtils'
@@ -89,15 +92,14 @@ const Created = styled.div`
 const Usage = styled.div`
   min-width: 244px;
 `
-class EndpointListItem extends React.Component {
-  static propTypes = {
-    item: PropTypes.object.isRequired,
-    onClick: PropTypes.func,
-    selected: PropTypes.bool,
-    onSelectedChange: PropTypes.func,
-    getUsage: PropTypes.func.isRequired,
-  }
-
+type Props = {
+  item: Endpoint,
+  onClick: () => void,
+  selected: boolean,
+  onSelectedChange: (value: boolean) => void,
+  getUsage: (item: Endpoint) => { replicasCount: number, migrationsCount: number },
+}
+class EndpointListItem extends React.Component<Props> {
   render() {
     return (
       <Wrapper>

+ 9 - 9
src/components/molecules/LoadingButton/LoadingButton.jsx → src/components/molecules/LoadingButton/index.jsx

@@ -12,18 +12,19 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import React from 'react'
+// @flow
+
+import * as React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { Button } from 'components'
+import Button from '../../atoms/Button'
 
 import StyleProps from '../../styleUtils/StyleProps'
 
 import loadingImage from './images/loading.svg'
 
 const ButtonStyled = styled(Button)`
-  position: relative
+  position: relative;
 `
 const Loading = styled.span`
   position: absolute;
@@ -35,11 +36,10 @@ const Loading = styled.span`
   ${StyleProps.animations.rotation}
 `
 
-class LoadingButton extends React.Component {
-  static propTypes = {
-    children: PropTypes.node,
-  }
-
+type Props = {
+  children: React.Node,
+}
+class LoadingButton extends React.Component<Props> {
   render() {
     return (
       <ButtonStyled {...this.props} disabled>

+ 10 - 10
src/components/molecules/LoginFormField/LoginFormField.jsx → src/components/molecules/LoginFormField/index.jsx

@@ -12,12 +12,12 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 
-import { TextInput } from 'components'
-
+import TextInput from '../../atoms/TextInput'
 import StyleProps from '../../styleUtils/StyleProps'
 
 const Wrapper = styled.div`
@@ -35,17 +35,17 @@ const StyledTextInput = styled(TextInput) `
   width: ${StyleProps.inputSizes.regular.width}px;
 `
 
-const LoginFormField = ({ label, ...props }) => {
+type Props = {
+  label: string,
+  onChange: (e: SyntheticInputEvent<EventTarget>) => void,
+}
+const LoginFormField = (props: Props) => {
   return (
     <Wrapper>
-      <FormFieldLabel>{label}</FormFieldLabel>
-      <StyledTextInput {...props} />
+      <FormFieldLabel>{props.label}</FormFieldLabel>
+      <StyledTextInput {...props} onChange={props.onChange} />
     </Wrapper>
   )
 }
 
-LoginFormField.propTypes = {
-  label: PropTypes.string,
-}
-
 export default LoginFormField

+ 5 - 9
src/components/molecules/LoginOptions/LoginOptions.jsx → src/components/molecules/LoginOptions/index.jsx

@@ -12,10 +12,11 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled, { css } from 'styled-components'
 
-import PropTypes from 'prop-types'
 import StyleProps from '../../styleUtils/StyleProps'
 import Palette from '../../styleUtils/Palette'
 import { loginButtons } from '../../../config'
@@ -94,14 +95,13 @@ const Logo = styled.div`
   ${props => buttonStyle(props.id, true)}
 `
 
-const LoginOptions = ({ buttons }) => {
-  if (loginButtons.length === 0 && (!buttons || buttons.length === 0)) {
+const LoginOptions = () => {
+  if (loginButtons.length === 0) {
     return null
   }
 
-  let useButtons = buttons && buttons.length > 0 ? buttons : loginButtons
   return (
-    <Wrapper>{useButtons.map((button) => {
+    <Wrapper>{loginButtons.map((button) => {
       return (
         <Button key={button.id} id={button.id}>
           <Logo id={button.id} />Sign in with {button.name}
@@ -111,8 +111,4 @@ const LoginOptions = ({ buttons }) => {
   )
 }
 
-LoginOptions.propTypes = {
-  buttons: PropTypes.array,
-}
-
 export default LoginOptions

+ 20 - 16
src/components/molecules/MainListFilter/MainListFilter.jsx → src/components/molecules/MainListFilter/index.jsx

@@ -12,11 +12,15 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { Checkbox, SearchInput, Dropdown, ReloadButton } from 'components'
+import Checkbox from '../../atoms/Checkbox'
+import SearchInput from '../SearchInput'
+import Dropdown from '../Dropdown'
+import ReloadButton from '../../atoms/ReloadButton'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -69,20 +73,20 @@ const SelectionText = styled.div`
   white-space: nowrap;
 `
 
-class MainListFilter extends React.Component {
-  static propTypes = {
-    onFilterItemClick: PropTypes.func,
-    onReloadButtonClick: PropTypes.func,
-    onSearchChange: PropTypes.func,
-    onSelectAllChange: PropTypes.func,
-    onActionChange: PropTypes.func,
-    actions: PropTypes.array,
-    selectedValue: PropTypes.string,
-    selectionInfo: PropTypes.object.isRequired,
-    selectAllSelected: PropTypes.bool,
-    items: PropTypes.array.isRequired,
-  }
-
+type DictItem = { value: string, label: string }
+type Props = {
+  onFilterItemClick: (item: DictItem) => void,
+  onReloadButtonClick: () => void,
+  onSearchChange: (value: string) => void,
+  onSelectAllChange: (checked: boolean) => void,
+  onActionChange: (action: string) => void,
+  actions: DictItem[],
+  selectedValue: string,
+  selectionInfo: { total: number, selected: number, label: string },
+  selectAllSelected: ?boolean,
+  items: DictItem[],
+}
+class MainListFilter extends React.Component<Props> {
   renderFilterGroup() {
     return (
       <FilterGroup>

+ 21 - 17
src/components/molecules/MainListItem/MainListItem.jsx → src/components/molecules/MainListItem/index.jsx

@@ -12,14 +12,19 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 
-import { Checkbox, StatusPill, EndpointLogos } from 'components'
+import Checkbox from '../../atoms/Checkbox'
+import StatusPill from '../../atoms/StatusPill'
+import EndpointLogos from '../../atoms/EndpointLogos'
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 import DateUtils from '../../../utils/DateUtils'
+import type { MainItem } from '../../../types/MainItem'
+import type { Execution } from '../../../types/Execution'
 
 import arrowImage from './images/arrow.svg'
 
@@ -99,18 +104,17 @@ const TasksRemaining = styled.div`
   min-width: 114px;
 `
 
-class MainListItem extends React.Component {
-  static propTypes = {
-    item: PropTypes.object.isRequired,
-    onClick: PropTypes.func,
-    selected: PropTypes.bool,
-    useTasksRemaining: PropTypes.bool,
-    image: PropTypes.string,
-    endpointType: PropTypes.func,
-    onSelectedChange: PropTypes.func,
-  }
-
-  getLastExecution() {
+type Props = {
+  item: MainItem,
+  onClick: () => void,
+  selected: boolean,
+  useTasksRemaining?: boolean,
+  image: string,
+  endpointType: (endpointId: string) => string,
+  onSelectedChange: (value: boolean) => void,
+}
+class MainListItem extends React.Component<Props> {
+  getLastExecution(): ?Execution | ?MainItem {
     if (this.props.item.executions && this.props.item.executions.length) {
       return this.props.item.executions[this.props.item.executions.length - 1]
     }
@@ -119,7 +123,7 @@ class MainListItem extends React.Component {
       return this.props.item
     }
 
-    return {}
+    return null
   }
 
   getStatus() {
@@ -160,8 +164,8 @@ class MainListItem extends React.Component {
 
     if (this.props.item.executions === undefined) {
       label = 'Created'
-      time = DateUtils.getLocalTime(lastExecution.created_at).format('DD MMMM YYYY, HH:mm')
-    } else if (lastExecution.created_at || lastExecution.updated_at) {
+      time = DateUtils.getLocalTime(lastExecution && lastExecution.created_at).format('DD MMMM YYYY, HH:mm')
+    } else if (lastExecution && (lastExecution.created_at || lastExecution.updated_at)) {
       time = DateUtils.getLocalTime(lastExecution.updated_at || lastExecution.created_at).format('DD MMMM YYYY, HH:mm')
     }
 

+ 36 - 21
src/components/molecules/Modal/Modal.jsx → src/components/molecules/Modal/index.jsx

@@ -12,9 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import React from 'react'
+// @flow
+
+import * as React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 import Modal from 'react-modal'
 
 import Palette from '../../styleUtils/Palette'
@@ -30,33 +31,37 @@ const Title = styled.div`
   line-height: 48px;
 `
 
-class NewModal extends React.Component {
-  static defaultProps = {
+type Props = {
+  children: React.Node,
+  isOpen: boolean,
+  contentLabel: string,
+  onRequestClose: () => void,
+  contentStyle: { [string]: mixed },
+  topBottomMargin: number,
+  title: string,
+}
+class NewModal extends React.Component<Props> {
+  static defaultProps: $Shape<Props> = {
     topBottomMargin: 8,
   }
 
-  static propTypes = {
-    children: PropTypes.node.isRequired,
-    isOpen: PropTypes.bool.isRequired,
-    contentLabel: PropTypes.string,
-    onRequestClose: PropTypes.func,
-    contentStyle: PropTypes.object,
-    topBottomMargin: PropTypes.number,
-    title: PropTypes.string,
-  }
+  scrollableRef: HTMLDivElement
+  windowScrollY: number
+  modalDiv: ?HTMLDivElement
 
   constructor() {
     super()
 
-    this.positionModal = this.positionModal.bind(this)
+    const self :any = this
+    self.positionModal = this.positionModal.bind(this)
   }
 
   componentDidMount() {
     window.addEventListener('resize', this.positionModal, true)
-    setTimeout(this.positionModal, 100)
+    setTimeout(() => { this.positionModal(0) }, 100)
   }
 
-  componentWillReceiveProps(newProps) {
+  componentWillReceiveProps(newProps: Props) {
     if (!this.props.isOpen && newProps.isOpen) {
       KeyboardManager.onKeyDown('modal', null, 1)
     } else if (this.props.isOpen && !newProps.isOpen) {
@@ -66,14 +71,14 @@ class NewModal extends React.Component {
   }
 
   componentWillUpdate() {
-    setTimeout(this.positionModal, 100)
+    setTimeout(() => { this.positionModal(0) }, 100)
   }
 
   componentWillUnmount() {
     window.removeEventListener('resize', this.positionModal, true)
   }
 
-  handleChildUpdate(scrollableRef, scrollOffset) {
+  handleChildUpdate(scrollableRef: HTMLDivElement, scrollOffset: number) {
     if (scrollableRef) {
       this.scrollableRef = scrollableRef
     }
@@ -84,6 +89,10 @@ class NewModal extends React.Component {
   }
 
   handleModalOpen() {
+    if (!document.body) {
+      return
+    }
+
     this.windowScrollY = window.scrollY
     document.body.style.top = `${-this.windowScrollY}px`
     document.body.style.position = 'fixed'
@@ -92,6 +101,10 @@ class NewModal extends React.Component {
   }
 
   handleModalClose() {
+    if (!document.body) {
+      return
+    }
+
     document.body.style.top = ''
     document.body.style.position = ''
     document.body.style.width = ''
@@ -99,10 +112,11 @@ class NewModal extends React.Component {
     window.scroll(0, this.windowScrollY)
   }
 
-  positionModal(scrollOffset) {
+  positionModal(scrollOffset: number) {
+    // $FlowIssue
     let pageNode = this.modalDiv && this.modalDiv.node.firstChild
     let contentNode = pageNode && pageNode.firstChild
-    if (!contentNode) {
+    if (!contentNode || !pageNode) {
       return
     }
     let scrollableNode = this.scrollableRef || contentNode
@@ -121,6 +135,7 @@ class NewModal extends React.Component {
     contentNode.style.top = `${top}px`
     contentNode.style.height = height
     contentNode.style.opacity = 1
+    // $FlowIssue
     scrollableNode.scrollTo(0, scrollTop + scrollOffset)
   }
 
@@ -173,7 +188,7 @@ class NewModal extends React.Component {
 
     return (
       <Modal
-        ref={m => { this.modalDiv = m }}
+        ref={(m: ?HTMLDivElement) => { this.modalDiv = m }}
         isOpen={this.props.isOpen}
         contentLabel={this.props.contentLabel || this.props.title}
         style={modalStyle}

+ 22 - 9
src/components/molecules/NewItemDropdown/NewItemDropdown.jsx → src/components/molecules/NewItemDropdown/index.jsx

@@ -12,11 +12,12 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 
-import { DropdownButton } from 'components'
+import DropdownButton from '../../atoms/DropdownButton'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -102,13 +103,24 @@ const Title = styled.div`
 `
 const Description = styled.div`
   font-size: 12px;
-  color: ${Palette.grayscale[4]}
+  color: ${Palette.grayscale[4]};
 `
 
-class NewItemDropdown extends React.Component {
-  static propTypes = {
-    onChange: PropTypes.func,
-  }
+export type ItemType = {
+  href?: string,
+  icon: { migration?: boolean, replica?: boolean, endpoint?: boolean },
+  title: string,
+  description: string,
+  value?: string,
+}
+type Props = {
+  onChange: (item: ItemType) => void,
+}
+type State = {
+  showDropdownList: boolean,
+}
+class NewItemDropdown extends React.Component<Props, State> {
+  itemMouseDown: boolean
 
   constructor() {
     super()
@@ -117,6 +129,7 @@ class NewItemDropdown extends React.Component {
       showDropdownList: false,
     }
 
+    // $FlowIssue
     this.handlePageClick = this.handlePageClick.bind(this)
   }
 
@@ -138,7 +151,7 @@ class NewItemDropdown extends React.Component {
     this.setState({ showDropdownList: !this.state.showDropdownList })
   }
 
-  handleItemClick(item) {
+  handleItemClick(item: ItemType) {
     this.setState({ showDropdownList: false })
 
     if (this.props.onChange) {
@@ -151,7 +164,7 @@ class NewItemDropdown extends React.Component {
       return null
     }
 
-    let items = [{
+    let items: ItemType[] = [{
       title: 'Migration',
       href: '/#/wizard/migration',
       description: 'Migrate VMs between two clouds',

+ 15 - 8
src/components/molecules/NotificationDropdown/NotificationDropdown.jsx → src/components/molecules/NotificationDropdown/index.jsx

@@ -12,11 +12,13 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 import moment from 'moment'
 
+import type { NotificationItem } from '../../../types/NotificationItem'
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 
@@ -134,12 +136,16 @@ const NoItems = styled.div`
   text-align: center;
 `
 
-class NotificationDropdown extends React.Component {
-  static propTypes = {
-    white: PropTypes.bool,
-    items: PropTypes.array,
-    onClose: PropTypes.func,
-  }
+type Props = {
+  white?: boolean,
+  items: NotificationItem[],
+  onClose: () => void,
+}
+type State = {
+  showDropdownList: boolean,
+}
+class NotificationDropdown extends React.Component<Props, State> {
+  itemMouseDown: boolean
 
   constructor() {
     super()
@@ -148,6 +154,7 @@ class NotificationDropdown extends React.Component {
       showDropdownList: false,
     }
 
+    // $FlowIssue
     this.handlePageClick = this.handlePageClick.bind(this)
   }
 
@@ -214,7 +221,7 @@ class NotificationDropdown extends React.Component {
               key={item.id}
               onMouseDown={() => { this.itemMouseDown = true }}
               onMouseUp={() => { this.itemMouseDown = false }}
-              onClick={() => { this.handleItemClick(item) }}
+              onClick={() => { this.handleItemClick() }}
             >
               <Title>
                 <TypeIcon level={item.level} />

+ 17 - 12
src/components/molecules/PropertiesTable/PropertiesTable.jsx → src/components/molecules/PropertiesTable/index.jsx

@@ -12,11 +12,13 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled, { css } from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { Switch, TextInput } from 'components'
+import Switch from '../../atoms/Switch'
+import TextInput from '../../atoms/TextInput'
 
 import LabelDictionary from '../../../utils/LabelDictionary'
 import Palette from '../../styleUtils/Palette'
@@ -54,14 +56,17 @@ const Row = styled.div`
   }
 `
 
-class PropertiesTable extends React.Component {
-  static propTypes = {
-    properties: PropTypes.array.isRequired,
-    onChange: PropTypes.func,
-    valueCallback: PropTypes.func.isRequired,
-  }
-
-  renderSwitch(prop) {
+type PropertyType = {
+  name: string,
+  type: string,
+}
+type Props = {
+  properties: PropertyType[],
+  onChange: (property: PropertyType, value: any) => void,
+  valueCallback: (property: PropertyType) => any,
+}
+class PropertiesTable extends React.Component<Props> {
+  renderSwitch(prop: PropertyType) {
     return (
       <Switch
         secondary
@@ -78,14 +83,14 @@ class PropertiesTable extends React.Component {
     )
   }
 
-  renderInput(prop) {
+  renderInput(prop: PropertyType) {
     let input = null
     switch (prop.type) {
       case 'boolean':
         input = this.renderSwitch(prop)
         break
       case 'string':
-        input = this.renderTextInput(prop)
+        input = this.renderTextInput()
         break
       default:
     }

+ 21 - 10
src/components/molecules/SearchInput/SearchInput.jsx → src/components/molecules/SearchInput/index.jsx

@@ -12,11 +12,14 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled, { css } from 'styled-components'
 
-import { SearchButton, TextInput, StatusIcon } from 'components'
+import SearchButton from '../../atoms/SearchButton'
+import TextInput from '../../atoms/TextInput'
+import StatusIcon from '../../atoms/StatusIcon'
 
 import StyleProps from '../../styleUtils/StyleProps'
 
@@ -49,18 +52,25 @@ const StatusIconStyled = styled(StatusIcon)`
   top: 0;
 `
 
-class SearchInput extends React.Component {
-  static propTypes = {
-    onChange: PropTypes.func,
-    alwaysOpen: PropTypes.bool,
-    loading: PropTypes.bool,
-    placeholder: PropTypes.string,
-  }
-
+type Props = {
+  onChange: (value: string) => void,
+  alwaysOpen?: boolean,
+  loading?: boolean,
+  placeholder: string,
+}
+type State = {
+  open: boolean,
+  hover?: boolean,
+  focus?: boolean,
+}
+class SearchInput extends React.Component<Props, State> {
   static defaultProps = {
     placeholder: 'Search',
   }
 
+  input: HTMLElement
+  itemMouseDown: boolean
+
   constructor() {
     super()
 
@@ -68,6 +78,7 @@ class SearchInput extends React.Component {
       open: false,
     }
 
+    // $FlowIssue
     this.handlePageClick = this.handlePageClick.bind(this)
   }
 

+ 7 - 1
src/components/molecules/SideMenu/SideMenu.jsx → src/components/molecules/SideMenu/index.jsx

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled, { css } from 'styled-components'
 
@@ -81,7 +83,11 @@ const MenuItem = styled.a`
   }
 `
 
-class SideMenu extends React.Component {
+type Props = {}
+type State = {
+  open: boolean,
+}
+class SideMenu extends React.Component<Props, State> {
   constructor() {
     super()
 

+ 10 - 10
src/components/molecules/Table/Table.jsx → src/components/molecules/Table/index.jsx

@@ -12,8 +12,9 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import React from 'react'
-import PropTypes from 'prop-types'
+// @flow
+
+import * as React from 'react'
 import styled from 'styled-components'
 
 import StyleProps from '../../styleUtils/StyleProps'
@@ -53,14 +54,13 @@ const RowData = styled.div`
   ${props => props.customStyle}
 `
 
-class Table extends React.Component {
-  static propTypes = {
-    header: PropTypes.array.isRequired,
-    items: PropTypes.array.isRequired,
-    columnsStyle: PropTypes.array,
-    className: PropTypes.string,
-  }
-
+type Props = {
+  header: string[],
+  items: Array<Array<React.Node>>,
+  columnsStyle: { [string]: mixed }[],
+  className: string,
+}
+class Table extends React.Component<Props> {
   renderHeader() {
     let dataWidth = `${100 / this.props.header.length}%`
     return (

+ 18 - 13
src/components/molecules/TaskItem/TaskItem.jsx → src/components/molecules/TaskItem/index.jsx

@@ -12,13 +12,19 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled, { css } from 'styled-components'
-import PropTypes from 'prop-types'
 import { Collapse } from 'react-collapse'
 
-import { StatusIcon, Arrow, StatusPill, CopyValue, ProgressBar, CopyButton } from 'components'
-
+import type { Task } from '../../../types/Task'
+import StatusIcon from '../../atoms/StatusIcon'
+import Arrow from '../../atoms/Arrow'
+import StatusPill from '../../atoms/StatusPill'
+import CopyValue from '../../atoms/CopyValue'
+import ProgressBar from '../../atoms/ProgressBar'
+import CopyButton from '../../atoms/CopyButton'
 import NotificationActions from '../../../actions/NotificationActions'
 import DomUtils from '../../../utils/DomUtils'
 import Palette from '../../styleUtils/Palette'
@@ -126,14 +132,13 @@ const ProgressUpdateValue = styled.div`
   margin-right: 32px;
 `
 
-class TaskItem extends React.Component {
-  static propTypes = {
-    columnWidths: PropTypes.array.isRequired,
-    item: PropTypes.object.isRequired,
-    open: PropTypes.bool,
-    onDependsOnClick: PropTypes.func,
-  }
-
+type Props = {
+  columnWidths: string[],
+  item: Task,
+  open: boolean,
+  onDependsOnClick: (id: string) => void,
+}
+class TaskItem extends React.Component<Props> {
   getLastMessage() {
     let message
     if (this.props.item.progress_updates.length) {
@@ -145,12 +150,12 @@ class TaskItem extends React.Component {
     return message
   }
 
-  getMessageProgress(message) {
+  getMessageProgress(message: string) {
     let match = message.match(/.*progress.*?(100|\d{1,2})%/)
     return match && match[1]
   }
 
-  handleExceptionTextClick(exceptionText) {
+  handleExceptionTextClick(exceptionText: string) {
     let succesful = DomUtils.copyTextToClipboard(exceptionText)
 
     if (succesful) {

+ 20 - 11
src/components/molecules/Timeline/Timeline.jsx → src/components/molecules/Timeline/index.jsx

@@ -12,11 +12,14 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { Arrow, StatusIcon } from 'components'
+import type { Execution } from '../../../types/Execution'
+import Arrow from '../../atoms/Arrow'
+import StatusIcon from '../../atoms/StatusIcon'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -78,14 +81,19 @@ const ItemLabel = styled.div`
   ${props => props.selected ? `font-weight: ${StyleProps.fontWeights.medium};` : ''}
 `
 
-class Timeline extends React.Component {
-  static propTypes = {
-    items: PropTypes.array,
-    selectedItem: PropTypes.object,
-    onPreviousClick: PropTypes.func,
-    onNextClick: PropTypes.func,
-    onItemClick: PropTypes.func,
-  }
+type Props = {
+  items: Execution[],
+  selectedItem: ?Execution,
+  onPreviousClick: () => void,
+  onNextClick: () => void,
+  onItemClick: (item: Execution) => void,
+}
+class Timeline extends React.Component<Props> {
+  itemsRef: HTMLElement
+  progressLineRef: HTMLElement
+  wrapperRef: HTMLElement
+  itemRef: HTMLElement
+  endLineRef: HTMLElement
 
   componentDidMount() {
     this.moveToSelectedItem()
@@ -110,11 +118,12 @@ class Timeline extends React.Component {
     }
 
     if (!this.itemRef || !this.props.selectedItem || !this.itemsRef) {
-      this.progressLineRef.style.width = 0
+      this.progressLineRef.style.width = '0'
       this.endLineRef.style.width = '100%'
       return
     }
 
+    // $FlowIssue
     let itemIndex = this.props.items.findIndex(i => i.id === this.props.selectedItem.id)
     let halfWidth = this.wrapperRef.offsetWidth / 2
     let itemGap = this.itemRef.offsetWidth + 90

+ 16 - 8
src/components/molecules/UserDropdown/UserDropdown.jsx → src/components/molecules/UserDropdown/index.jsx

@@ -12,8 +12,9 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled, { css } from 'styled-components'
 
 import Palette from '../../styleUtils/Palette'
@@ -95,12 +96,18 @@ const Email = styled.div`
   border-bottom: 1px solid ${Palette.grayscale[3]};
 `
 
-class UserDropdown extends React.Component {
-  static propTypes = {
-    onItemClick: PropTypes.func,
-    user: PropTypes.object,
-    white: PropTypes.bool,
-  }
+type User = { name: string, email: string }
+type DictItem = { label: string, value: string }
+type Props = {
+  onItemClick: (item: DictItem) => void,
+  user: User,
+  white?: boolean,
+}
+type State = {
+  showDropdownList: boolean,
+}
+class UserDropdown extends React.Component<Props, State> {
+  itemMouseDown: boolean
 
   constructor() {
     super()
@@ -109,6 +116,7 @@ class UserDropdown extends React.Component {
       showDropdownList: false,
     }
 
+    // $FlowIssue
     this.handlePageClick = this.handlePageClick.bind(this)
   }
 
@@ -120,7 +128,7 @@ class UserDropdown extends React.Component {
     window.removeEventListener('mousedown', this.handlePageClick, false)
   }
 
-  handleItemClick(item) {
+  handleItemClick(item: DictItem) {
     if (this.props.onItemClick) {
       this.props.onItemClick(item)
     }

+ 8 - 8
src/components/molecules/WizardBreadcrumbs/WizardBreadcrumbs.jsx → src/components/molecules/WizardBreadcrumbs/index.jsx

@@ -12,11 +12,12 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { Arrow } from 'components'
+import Arrow from '../../atoms/Arrow'
 
 import { wizardConfig } from '../../../config'
 import Palette from '../../styleUtils/Palette'
@@ -39,12 +40,11 @@ const Name = styled.div`
   color: ${props => props.selected ? Palette.primary : Palette.black};
 `
 
-class WizardBreadcrumbs extends React.Component {
-  static propTypes = {
-    selected: PropTypes.object.isRequired,
-    wizardType: PropTypes.string.isRequired,
-  }
-
+type Props = {
+  selected: { id: string },
+  wizardType: 'migration' | 'replica',
+}
+class WizardBreadcrumbs extends React.Component<Props> {
   render() {
     let pages = wizardConfig.pages.filter(p => !p.excludeFrom || p.excludeFrom !== this.props.wizardType)
     return (

+ 22 - 17
src/components/molecules/WizardOptionsField/WizardOptionsField.jsx → src/components/molecules/WizardOptionsField/index.jsx

@@ -12,11 +12,16 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { Switch, TextInput, PropertiesTable, Dropdown, InfoIcon } from 'components'
+import Switch from '../../atoms/Switch'
+import TextInput from '../../atoms/TextInput'
+import Dropdown from '../../molecules/Dropdown'
+import InfoIcon from '../../atoms/InfoIcon'
+import PropertiesTable from '../../molecules/PropertiesTable'
 
 import StyleProps from '../../styleUtils/StyleProps'
 import LabelDictionary from '../../../utils/LabelDictionary'
@@ -41,25 +46,25 @@ const LabelText = styled.span`
   margin-right: 24px;
 `
 
-class WizardOptionsField extends React.Component {
-  static propTypes = {
-    type: PropTypes.string,
-    name: PropTypes.string.isRequired,
-    value: PropTypes.any,
-    onChange: PropTypes.func,
-    valueCallback: PropTypes.func,
-    className: PropTypes.string,
-    properties: PropTypes.array,
-    enum: PropTypes.array,
-    required: PropTypes.bool,
-  }
-
-  renderSwitch({ triState }) {
+type PropertyType = { name: string, type: string }
+type Props = {
+  type: 'replica' | 'migration',
+  name: string,
+  value: any,
+  onChange: (value: any) => void,
+  valueCallback: (prop: PropertyType, value: any) => void,
+  className: string,
+  properties: PropertyType[],
+  enum: string[],
+  required: boolean,
+}
+class WizardOptionsField extends React.Component<Props> {
+  renderSwitch(propss: { triState: boolean }) {
     return (
       <Switch
         width="112px"
         justifyContent="flex-end"
-        triState={triState}
+        triState={propss.triState}
         checked={this.props.value}
         onChange={checked => { this.props.onChange(checked) }}
         style={{ marginTop: '-8px' }}

+ 8 - 8
src/components/molecules/WizardType/WizardType.jsx → src/components/molecules/WizardType/index.jsx

@@ -12,11 +12,12 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { Switch } from 'components'
+import Switch from '../../atoms/Switch'
 
 import StyleProps from '../../styleUtils/StyleProps'
 import Palette from '../../styleUtils/Palette'
@@ -60,12 +61,11 @@ const Message = styled.div`
   opacity: ${props => props.selected ? 1 : 0.6};
 `
 
-class WizardType extends React.Component {
-  static propTypes = {
-    selected: PropTypes.string,
-    onChange: PropTypes.func,
-  }
-
+type Props = {
+  selected: 'replica' | 'migration',
+  onChange: (checked: ?boolean) => void,
+}
+class WizardType extends React.Component<Props> {
   render() {
     return (
       <Wrapper>

+ 17 - 14
src/components/organisms/AlertModal/AlertModal.jsx → src/components/organisms/AlertModal/index.jsx

@@ -12,11 +12,14 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { Modal, Button, StatusImage } from 'components'
+import Modal from '../../molecules/Modal'
+import Button from '../../atoms/Button'
+import StatusImage from '../../atoms/StatusImage'
 
 import Palette from '../../styleUtils/Palette'
 import KeyboardManager from '../../../utils/KeyboardManager'
@@ -52,21 +55,21 @@ const Buttons = styled.div`
   width: 100%;
 `
 
-class AlertModal extends React.Component {
-  static propTypes = {
-    message: PropTypes.string,
-    extraMessage: PropTypes.string,
-    type: PropTypes.string,
-    isOpen: PropTypes.bool,
-    onRequestClose: PropTypes.func,
-    onConfirmation: PropTypes.func,
-  }
-
-  static defaultProps = {
+type AlertType = 'error' | 'confirmation' | 'loading'
+type Props = {
+  message: string,
+  extraMessage: string,
+  type: AlertType,
+  isOpen: boolean,
+  onRequestClose: () => void,
+  onConfirmation: () => void,
+}
+class AlertModal extends React.Component<Props> {
+  static defaultProps: $Shape<Props> = {
     type: 'confirmation',
   }
 
-  componentWillReceiveProps(newProps) {
+  componentWillReceiveProps(newProps: Props) {
     if (newProps.isOpen && !this.props.isOpen) {
       KeyboardManager.onEnter('alert', () => { this.props.onConfirmation() }, 2)
     } else if (!newProps.isOpen && this.props.isOpen) {

+ 12 - 10
src/components/organisms/ChooseProvider/ChooseProvider.jsx → src/components/organisms/ChooseProvider/index.jsx

@@ -12,11 +12,14 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { EndpointLogos, Button, StatusImage } from 'components'
+import EndpointLogos from '../../atoms/EndpointLogos'
+import Button from '../../atoms/Button'
+import StatusImage from '../../atoms/StatusImage'
 
 import StyleProps from '../../styleUtils/StyleProps'
 
@@ -49,14 +52,13 @@ const LoadingText = styled.div`
   margin-top: 32px;
 `
 
-class ChooseProvider extends React.Component {
-  static propTypes = {
-    providers: PropTypes.object,
-    onCancelClick: PropTypes.func,
-    onProviderClick: PropTypes.func,
-    loading: PropTypes.bool,
-  }
-
+type Props = {
+  providers: { [string]: any },
+  onCancelClick: () => void,
+  onProviderClick: (provider: string) => void,
+  loading: boolean,
+}
+class ChooseProvider extends React.Component<Props> {
   renderLoading() {
     if (!this.props.loading) {
       return null

+ 28 - 26
src/components/organisms/DetailsContentHeader/DetailsContentHeader.jsx → src/components/organisms/DetailsContentHeader/index.jsx

@@ -12,11 +12,15 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { StatusPill, Button } from 'components'
+import type { MainItem } from '../../../types/MainItem'
+import type { Execution } from '../../../types/Execution'
+import StatusPill from '../../atoms/StatusPill'
+import Button from '../../atoms/Button'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -68,30 +72,29 @@ const MockButton = styled.div`
   ${StyleProps.exactWidth(`${StyleProps.inputSizes.regular.width}px`)}
 `
 
-class DetailsContentHeader extends React.Component {
-  static propTypes = {
-    onBackButonClick: PropTypes.func,
-    onActionButtonClick: PropTypes.func,
-    onCancelClick: PropTypes.func,
-    typeImage: PropTypes.string,
-    buttonLabel: PropTypes.string,
-    item: PropTypes.object.isRequired,
-    alertInfoPill: PropTypes.bool,
-    primaryInfoPill: PropTypes.bool,
-    alertButton: PropTypes.bool,
-    hollowButton: PropTypes.bool,
-    actionButtonDisabled: PropTypes.bool,
-    noSidemenuSpace: PropTypes.bool,
-  }
-
-  getLastExecution() {
-    if (this.props.item.executions && this.props.item.executions.length) {
+type Props = {
+  onBackButonClick: () => void,
+  onActionButtonClick?: () => void,
+  onCancelClick: (?Execution | ?MainItem) => void,
+  typeImage: string,
+  buttonLabel?: string,
+  item: ?MainItem,
+  alertInfoPill?: boolean,
+  primaryInfoPill?: boolean,
+  alertButton?: boolean,
+  hollowButton?: boolean,
+  actionButtonDisabled?: boolean,
+  noSidemenuSpace?: boolean,
+}
+class DetailsContentHeader extends React.Component<Props> {
+  getLastExecution(): ?MainItem | ?Execution {
+    if (this.props.item && this.props.item.executions && this.props.item.executions.length) {
       return this.props.item.executions[this.props.item.executions.length - 1]
-    } else if (typeof this.props.item.executions === 'undefined') {
+    } else if (this.props.item && typeof this.props.item.executions === 'undefined') {
       return this.props.item
     }
 
-    return {}
+    return null
   }
 
   getStatus() {
@@ -107,12 +110,11 @@ class DetailsContentHeader extends React.Component {
     if (!this.getStatus()) {
       return null
     }
-
     return (
       <StatusPills>
         <StatusPill
           status="INFO"
-          label={this.props.item.type && this.props.item.type.toUpperCase()}
+          label={this.props.item ? this.props.item.type && this.props.item.type.toUpperCase() : ''}
           alert={this.props.alertInfoPill}
           primary={this.props.primaryInfoPill}
         />
@@ -147,7 +149,7 @@ class DetailsContentHeader extends React.Component {
   }
 
   renderDescription() {
-    if (!this.props.item.description) {
+    if (!this.props.item || !this.props.item.description) {
       return null
     }
 
@@ -157,7 +159,7 @@ class DetailsContentHeader extends React.Component {
   }
 
   render() {
-    let title = (this.props.item.instances && this.props.item.instances[0]) || this.props.item.name
+    let title = this.props.item ? (this.props.item.instances && this.props.item.instances[0]) || this.props.item.name : ''
 
     return (
       <Wrapper noSidemenuSpace={this.props.noSidemenuSpace}>

+ 13 - 11
src/components/organisms/DetailsPageHeader/DetailsPageHeader.jsx → src/components/organisms/DetailsPageHeader/index.jsx

@@ -12,12 +12,15 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 import connectToStores from 'alt-utils/lib/connectToStores'
 
-import { SideMenu, NotificationDropdown, UserDropdown } from 'components'
+import SideMenu from '../../molecules/SideMenu'
+import NotificationDropdown from '../../molecules/NotificationDropdown'
+import UserDropdown from '../../molecules/UserDropdown'
 
 import NotificationActions from '../../../actions/NotificationActions'
 import NotificationStore from '../../../stores/NotificationStore'
@@ -51,18 +54,17 @@ const User = styled.div`
   align-items: center;
 `
 
-export class DetailsPageHeader extends React.Component {
-  static propTypes = {
-    user: PropTypes.object,
-    onUserItemClick: PropTypes.func,
-    notificationStore: PropTypes.object,
-  }
-
+type Props = {
+  user: { username: string, email: string },
+  onUserItemClick: (userItem: { label: string, value: string }) => void,
+  notificationStore?: any,
+}
+export class DetailsPageHeader extends React.Component<Props> {
   static getStores() {
     return [NotificationStore]
   }
 
-  static getPropsFromStores() {
+  static getPropsFromStores(): $Shape<Props> {
     return {
       notificationStore: NotificationStore.getState(),
     }
@@ -84,7 +86,7 @@ export class DetailsPageHeader extends React.Component {
           <Logo href="/#/replicas" />
         </Menu>
         <User>
-          <NotificationDropdown white items={this.props.notificationStore.persistedNotifications} onClose={() => this.handleNotificationsClose()} />
+          <NotificationDropdown white items={this.props.notificationStore ? this.props.notificationStore.persistedNotifications : []} onClose={() => this.handleNotificationsClose()} />
           <UserDropdownStyled
             white
             user={this.props.user}

+ 64 - 48
src/components/organisms/Endpoint/Endpoint.jsx → src/components/organisms/Endpoint/index.jsx

@@ -12,20 +12,21 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 import connectToStores from 'alt-utils/lib/connectToStores'
 
-import {
-  EndpointLogos,
-  StatusIcon,
-  CopyButton,
-  Tooltip,
-  StatusImage,
-  Button,
-  LoadingButton,
-} from 'components'
+import EndpointLogos from '../../atoms/EndpointLogos'
+import StatusIcon from '../../atoms/StatusIcon'
+import CopyButton from '../../atoms/CopyButton'
+import Tooltip from '../../atoms/Tooltip'
+import StatusImage from '../../atoms/StatusImage'
+import Button from '../../atoms/Button'
+import LoadingButton from '../../molecules/LoadingButton'
+
+import type { Endpoint as EndpointType } from '../../../types/Endpoint'
 import NotificationActions from '../../../actions/NotificationActions'
 import EndpointStore from '../../../stores/EndpointStore'
 import EndpointActions from '../../../actions/EndpointActions'
@@ -35,6 +36,7 @@ import ObjectUtils from '../../../utils/ObjectUtils'
 import Palette from '../../styleUtils/Palette'
 import DomUtils from '../../../utils/DomUtils'
 import { ContentPlugin } from '../../../plugins/endpoint'
+import DefaultContentPlugin from '../../../plugins/endpoint/default/ContentPlugin'
 import KeyboardManager from '../../../utils/KeyboardManager'
 
 const Wrapper = styled.div`
@@ -102,20 +104,26 @@ const Buttons = styled.div`
   flex-shrink: 0;
 `
 
-class Endpoint extends React.Component {
-  static propTypes = {
-    type: PropTypes.string,
-    cancelButtonText: PropTypes.string,
-    deleteOnCancel: PropTypes.bool,
-    endpoint: PropTypes.object,
-    connectionInfo: PropTypes.object,
-    onCancelClick: PropTypes.func,
-    onResizeUpdate: PropTypes.func,
-    endpointStore: PropTypes.object,
-    providerStore: PropTypes.object,
-  }
-
-  static defaultProps = {
+type Props = {
+  type: string,
+  cancelButtonText: string,
+  deleteOnCancel: boolean,
+  endpoint: EndpointType,
+  connectionInfo: { [string]: mixed },
+  onCancelClick: (opts?: { autoClose?: boolean }) => void,
+  onResizeUpdate: (scrollableRef: HTMLElement, scrollOffset?: number) => void,
+  endpointStore: any,
+  providerStore: any,
+}
+type State = {
+  invalidFields: any[],
+  validating: boolean,
+  showErrorMessage: boolean,
+  endpoint: EndpointType | {},
+  isNew: ?boolean,
+}
+class Endpoint extends React.Component<Props, State> {
+  static defaultProps: $Shape<Props> = {
     cancelButtonText: 'Cancel',
   }
 
@@ -130,6 +138,11 @@ class Endpoint extends React.Component {
     }
   }
 
+  scrollableRef: HTMLElement
+  closeTimeout: TimeoutID
+  contentPluginRef: DefaultContentPlugin
+  isValidateButtonEnabled: boolean
+
   constructor() {
     super()
 
@@ -190,6 +203,9 @@ class Endpoint extends React.Component {
   }
 
   getFieldValue(field) {
+    if (!field) {
+      return ''
+    }
     if (this.state.endpoint[field.name]) {
       return this.state.endpoint[field.name]
     }
@@ -205,30 +221,8 @@ class Endpoint extends React.Component {
     return this.state.validating
   }
 
-  highlightRequired() {
-    let invalidFields = this.contentPluginRef.findInvalidFields()
-    this.setState({ invalidFields })
-    return invalidFields.length > 0
-  }
-
-  update() {
-    EndpointActions.update(this.state.endpoint).promise.then(() => {
-      NotificationActions.notify('Validating endpoint ...')
-      EndpointActions.validate(this.state.endpoint)
-    })
-  }
-
-  add() {
-    EndpointActions.add(this.state.endpoint).promise.then(() => {
-      let endpoint = EndpointStore.getState().endpoints[0]
-      this.setState({ isNew: false, endpoint })
-      NotificationActions.notify('Validating endpoint ...')
-      EndpointActions.validate(endpoint)
-    })
-  }
-
   handleFieldsChange(items) {
-    let endpoint = { ...this.state.endpoint }
+    let endpoint: EndpointType = { ...this.state.endpoint }
 
     items.forEach(item => {
       endpoint[item.field.name] = item.value
@@ -238,7 +232,7 @@ class Endpoint extends React.Component {
   }
 
   handleValidateClick() {
-    if (!this.highlightRequired(true)) {
+    if (!this.highlightRequired()) {
       this.setState({ validating: true })
 
       NotificationActions.notify('Saving endpoint ...')
@@ -273,6 +267,28 @@ class Endpoint extends React.Component {
     this.props.onCancelClick()
   }
 
+  highlightRequired() {
+    let invalidFields = this.contentPluginRef.findInvalidFields()
+    this.setState({ invalidFields })
+    return invalidFields.length > 0
+  }
+
+  update() {
+    EndpointActions.update(this.state.endpoint).promise.then(() => {
+      NotificationActions.notify('Validating endpoint ...')
+      EndpointActions.validate(this.state.endpoint)
+    })
+  }
+
+  add() {
+    EndpointActions.add(this.state.endpoint).promise.then(() => {
+      let endpoint = EndpointStore.getState().endpoints[0]
+      this.setState({ isNew: false, endpoint })
+      NotificationActions.notify('Validating endpoint ...')
+      EndpointActions.validate(endpoint)
+    })
+  }
+
   renderEndpointStatus() {
     let validation = this.props.endpointStore.validation
     if (!this.isValidating() && !validation) {

+ 22 - 14
src/components/organisms/EndpointDetailsContent/EndpointDetailsContent.jsx → src/components/organisms/EndpointDetailsContent/index.jsx

@@ -12,11 +12,18 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { EndpointLogos, PasswordValue, Button, CopyValue, StatusImage } from 'components'
+import EndpointLogos from '../../atoms/EndpointLogos'
+import PasswordValue from '../../atoms/PasswordValue'
+import Button from '../../atoms/Button'
+import CopyValue from '../../atoms/CopyValue'
+import StatusImage from '../../atoms/StatusImage'
+
+import type { Endpoint } from '../../../types/Endpoint'
 import StyleProps from '../../styleUtils/StyleProps'
 import Palette from '../../styleUtils/Palette'
 import DateUtils from '../../../utils/DateUtils'
@@ -64,15 +71,16 @@ const LoadingWrapper = styled.div`
   margin: 32px 0 64px 0;
 `
 
-class EndpointDetailsContent extends React.Component {
-  static propTypes = {
-    item: PropTypes.object.isRequired,
-    connectionInfo: PropTypes.object,
-    loading: PropTypes.bool,
-    onDeleteClick: PropTypes.func,
-    onValidateClick: PropTypes.func,
-    onEditClick: PropTypes.func,
-  }
+type Props = {
+  item: ?Endpoint,
+  connectionInfo: { [string]: mixed },
+  loading: boolean,
+  onDeleteClick: () => void,
+  onValidateClick: () => void,
+  onEditClick: () => void,
+}
+class EndpointDetailsContent extends React.Component<Props> {
+  renderedKeys: { [string]: boolean }
 
   renderConnectionInfoLoading() {
     if (!this.props.loading) {
@@ -86,7 +94,7 @@ class EndpointDetailsContent extends React.Component {
     )
   }
 
-  renderConnectionInfo(connectionInfo) {
+  renderConnectionInfo(connectionInfo: any) {
     if (!connectionInfo) {
       return null
     }
@@ -147,13 +155,13 @@ class EndpointDetailsContent extends React.Component {
     )
   }
 
-  renderValue(value) {
+  renderValue(value: string) {
     return <CopyValue value={value} maxWidth="90%" />
   }
 
   render() {
     this.renderedKeys = {}
-    const { type, name, description, created_at } = this.props.item
+    const { type, name, description, created_at } = this.props.item || {}
     return (
       <Wrapper>
         <EndpointLogos endpoint={type} />

+ 13 - 11
src/components/organisms/EndpointValidation/EndpointValidation.jsx → src/components/organisms/EndpointValidation/index.jsx

@@ -12,11 +12,14 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled, { css } from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { Button, CopyButton, StatusImage } from 'components'
+import Button from '../../atoms/Button'
+import CopyButton from '../../atoms/CopyButton'
+import StatusImage from '../../atoms/StatusImage'
 
 import Palette from '../../styleUtils/Palette'
 
@@ -66,15 +69,14 @@ const Error = styled.div`
   }
 `
 
-class EndpointValidation extends React.Component {
-  static propTypes = {
-    loading: PropTypes.bool,
-    validation: PropTypes.object,
-    onCancelClick: PropTypes.func,
-    onRetryClick: PropTypes.func,
-  }
-
-  handleCopyClick(message) {
+type Props = {
+  loading: boolean,
+  validation?: { valid: boolean, message: string },
+  onCancelClick: () => void,
+  onRetryClick: () => void,
+}
+class EndpointValidation extends React.Component<Props> {
+  handleCopyClick(message: string) {
     let succesful = DomUtils.copyTextToClipboard(message)
 
     if (succesful) {

+ 41 - 17
src/components/organisms/Executions/Executions.jsx → src/components/organisms/Executions/index.jsx

@@ -12,12 +12,19 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { Timeline, StatusPill, CopyValue, Button, Tasks } from 'components'
+import StatusPill from '../../atoms/StatusPill'
+import CopyValue from '../../atoms/CopyValue'
+import Button from '../../atoms/Button'
+import Timeline from '../../molecules/Timeline'
+import Tasks from '../../organisms/Tasks'
 
+import type { MainItem } from '../../../types/MainItem'
+import type { Execution } from '../../../types/Execution'
 import Palette from '../../styleUtils/Palette'
 import DateUtils from '../../../utils/DateUtils'
 
@@ -68,14 +75,16 @@ const NoExecutionText = styled.div`
   margin-bottom: 48px;
 `
 
-class Executions extends React.Component {
-  static propTypes = {
-    item: PropTypes.object.isRequired,
-    onCancelExecutionClick: PropTypes.func,
-    onDeleteExecutionClick: PropTypes.func,
-    onExecuteClick: PropTypes.func,
-  }
-
+type Props = {
+  item: MainItem,
+  onCancelExecutionClick: (execution: ?Execution) => void,
+  onDeleteExecutionClick: (execution: ?Execution) => void,
+  onExecuteClick: () => void,
+}
+type State = {
+  selectedExecution: ?Execution,
+}
+class Executions extends React.Component<Props, State> {
   constructor() {
     super()
 
@@ -88,11 +97,11 @@ class Executions extends React.Component {
     this.setSelectedExecution(this.props)
   }
 
-  componentWillReceiveProps(props) {
+  componentWillReceiveProps(props: Props) {
     this.setSelectedExecution(props)
   }
 
-  setSelectedExecution(props) {
+  setSelectedExecution(props: Props) {
     let lastExecution = this.getLastExecution(props)
     let selectExecution = null
 
@@ -103,8 +112,10 @@ class Executions extends React.Component {
       }
 
       if (this.props.item.executions.length > props.item.executions.length) {
+        // $FlowIssue
         let isSelectedAvailable = props.item.executions.find(e => e.id === this.state.selectedExecution.id)
         if (!isSelectedAvailable) {
+          // $FlowIssue
           let lastIndex = this.props.item.executions.findIndex(e => e.id === this.state.selectedExecution.id)
 
           if (props.item.executions.length) {
@@ -127,24 +138,26 @@ class Executions extends React.Component {
         selectedExecution: selectExecution,
       })
     } else if (this.hasExecutions(props)) {
+      // $FlowIssue
       selectExecution = props.item.executions.find(e => e.id === this.state.selectedExecution.id) || lastExecution
       this.setState({
-        selectedExecution: selectExecution,
+        selectedExecution: selectExecution || null,
       })
     } else {
       this.setState({ selectedExecution: null })
     }
   }
 
-  getLastExecution(props) {
+  getLastExecution(props: Props) {
     return this.hasExecutions(props) && props.item.executions[props.item.executions.length - 1]
   }
 
-  hasExecutions(props) {
+  hasExecutions(props: Props) {
     return props.item.executions && props.item.executions.length
   }
 
   handlePreviousExecutionClick() {
+    // $FlowIssue
     let selectedIndex = this.props.item.executions.findIndex(e => e.id === this.state.selectedExecution.id)
 
     if (selectedIndex === 0) {
@@ -155,6 +168,7 @@ class Executions extends React.Component {
   }
 
   handleNextExecutionClick() {
+    // $FlowIssue
     let selectedIndex = this.props.item.executions.findIndex(e => e.id === this.state.selectedExecution.id)
 
     if (selectedIndex >= this.props.item.executions.length - 1) {
@@ -164,7 +178,7 @@ class Executions extends React.Component {
     this.setState({ selectedExecution: this.props.item.executions[selectedIndex + 1] })
   }
 
-  handleTimelineItemClick(item) {
+  handleTimelineItemClick(item: Execution) {
     this.setState({ selectedExecution: item })
   }
 
@@ -185,6 +199,10 @@ class Executions extends React.Component {
   }
 
   renderExecutionInfoButton() {
+    if (!this.state.selectedExecution) {
+      return null
+    }
+
     if (this.state.selectedExecution.status === 'RUNNING') {
       return (
         <Button
@@ -216,7 +234,13 @@ class Executions extends React.Component {
           {DateUtils.getLocalTime(this.state.selectedExecution.created_at).format('DD MMMM YYYY HH:mm')}
         </ExecutionInfoDate>
         <ExecutionInfoId>
-          ID:&nbsp;<CopyValue width="107px" value={this.state.selectedExecution.id} />
+          ID:&nbsp;<CopyValue
+            width="107px"
+            value={
+              // $FlowIssue
+              this.state.selectedExecution.status
+            }
+          />
         </ExecutionInfoId>
         {this.renderExecutionInfoButton()}
       </ExecutionInfo>

+ 53 - 44
src/components/organisms/FilterList/FilterList.jsx → src/components/organisms/FilterList/index.jsx

@@ -12,34 +12,44 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import React from 'react'
-import PropTypes from 'prop-types'
-import styled from 'styled-components'
+// @flow
 
-import { MainListFilter, MainList } from 'components'
-
-const Wrapper = styled.div`
-`
-
-class FilterList extends React.Component {
-  static propTypes = {
-    items: PropTypes.array.isRequired,
-    actions: PropTypes.array,
-    loading: PropTypes.bool,
-    onReloadButtonClick: PropTypes.func,
-    onItemClick: PropTypes.func,
-    onActionChange: PropTypes.func,
-    selectionLabel: PropTypes.string,
-    renderItemComponent: PropTypes.func,
-    itemFilterFunction: PropTypes.func.isRequired,
-    filterItems: PropTypes.array.isRequired,
-    emptyListImage: PropTypes.string,
-    emptyListMessage: PropTypes.string,
-    emptyListExtraMessage: PropTypes.string,
-    emptyListButtonLabel: PropTypes.string,
-    onEmptyListButtonClick: PropTypes.func,
-  }
+import * as React from 'react'
+import styled from 'styled-components'
 
+import type { MainItem } from '../../../types/MainItem'
+import MainListFilter from '../../molecules/MainListFilter'
+import type { ItemComponentProps } from '../../organisms/MainList'
+import MainList from '../../organisms/MainList'
+
+const Wrapper = styled.div``
+
+type DictItem = { value: string, label: string }
+type Props = {
+  items: MainItem[],
+  actions: DictItem[],
+  loading: boolean,
+  onReloadButtonClick: () => void,
+  onItemClick: (item: MainItem) => void,
+  onActionChange: (selectedItems: MainItem[], actionValue: string) => void,
+  selectionLabel: string,
+  renderItemComponent: (componentProps: ItemComponentProps) => React.Node,
+  itemFilterFunction: (item: MainItem, filterStatus?: ?string, filterState?: string) => boolean,
+  filterItems: DictItem[],
+  emptyListImage: string,
+  emptyListMessage: string,
+  emptyListExtraMessage: string,
+  emptyListButtonLabel: string,
+  onEmptyListButtonClick: () => void,
+}
+type State = {
+  items: MainItem[],
+  filterStatus: string,
+  filterText: string,
+  selectedItems: MainItem[],
+  selectAllSelected?: boolean,
+}
+class FilterList extends React.Component<Props, State> {
   constructor() {
     super()
 
@@ -55,7 +65,7 @@ class FilterList extends React.Component {
     this.setState({ items: this.props.items })
   }
 
-  componentWillReceiveProps(props) {
+  componentWillReceiveProps(props: Props) {
     if (props.items.length !== this.props.items.length) {
       this.setState({
         items: props.items,
@@ -69,7 +79,7 @@ class FilterList extends React.Component {
     this.setState({ items: this.filterItems(props.items) })
   }
 
-  handleFilterItemClick(item) {
+  handleFilterItemClick(item: DictItem) {
     let items = this.filterItems(this.props.items, item.value)
     let selectedItems = this.state.selectedItems.filter(selItem => {
       if (items.find(i => selItem.id === i.id)) {
@@ -88,24 +98,13 @@ class FilterList extends React.Component {
     })
   }
 
-  handleSearchChange(text) {
+  handleSearchChange(text: string) {
     this.setState({
       filterText: text,
       items: this.filterItems(this.props.items, null, text),
     })
   }
-
-  filterItems(items, filterStatus, filterText) {
-    filterStatus = filterStatus || this.state.filterStatus
-    filterText = typeof filterText === 'undefined' ? this.state.filterText : filterText
-    let filteredItems = items.filter(item => {
-      return this.props.itemFilterFunction(item, filterStatus, filterText)
-    })
-
-    return filteredItems
-  }
-
-  handleItemSelectedChange(item, selected) {
+  handleItemSelectedChange(item: MainItem, selected: boolean) {
     let items = this.state.selectedItems.slice(0)
     let selectedItems = items.filter(i => item.id !== i.id) || []
 
@@ -116,7 +115,7 @@ class FilterList extends React.Component {
     this.setState({ selectedItems, selectAllSelected: false })
   }
 
-  handleSelectAllChange(selected) {
+  handleSelectAllChange(selected: boolean) {
     let selectedItems = []
     if (selected) {
       selectedItems = this.state.items.slice(0)
@@ -125,8 +124,18 @@ class FilterList extends React.Component {
     this.setState({ selectedItems, selectAllSelected: selected })
   }
 
-  handleActionChange(action) {
-    this.props.onActionChange(this.state.selectedItems, action)
+  handleActionChange(actionValue: string) {
+    this.props.onActionChange(this.state.selectedItems, actionValue)
+  }
+
+  filterItems(items: MainItem[], filterStatus?: ?string, filterText?: string): MainItem[] {
+    filterStatus = filterStatus || this.state.filterStatus
+    filterText = typeof filterText === 'undefined' ? this.state.filterText : filterText
+    let filteredItems = items.filter(item => {
+      return this.props.itemFilterFunction(item, filterStatus, filterText)
+    })
+
+    return filteredItems
   }
 
   render() {

+ 25 - 22
src/components/organisms/LoginForm/LoginForm.jsx → src/components/organisms/LoginForm/index.jsx

@@ -12,11 +12,15 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled, { css } from 'styled-components'
 
-import { LoginFormField, Button, LoginOptions, LoadingButton } from 'components'
+import Button from '../../atoms/Button'
+import LoginOptions from '../../molecules/LoginOptions'
+import LoadingButton from '../../molecules/LoadingButton'
+import LoginFormField from '../../molecules/LoginFormField'
 
 import StyleProps from '../../styleUtils/StyleProps'
 
@@ -78,14 +82,17 @@ const LoginErrorText = styled.div`
   text-align: center;
 `
 
-class LoginForm extends React.Component {
-  static propTypes = {
-    className: PropTypes.string,
-    loading: PropTypes.bool,
-    loginFailedResponse: PropTypes.object,
-    onFormSubmit: PropTypes.func,
-  }
-
+type Props = {
+  className: string,
+  loading: boolean,
+  loginFailedResponse: { status: string },
+  onFormSubmit: (credentials: { username: string, password: string }) => void,
+}
+type State = {
+  username: string,
+  password: string,
+}
+class LoginForm extends React.Component<Props, State> {
   static defaultProps = {
     className: '',
   }
@@ -93,25 +100,21 @@ class LoginForm extends React.Component {
   constructor() {
     super()
 
-    this.handleUsernameChange = this.handleUsernameChange.bind(this)
-    this.handlePasswordChange = this.handlePasswordChange.bind(this)
-    this.handleFormSubmit = this.handleFormSubmit.bind(this)
-
     this.state = {
       username: '',
       password: '',
     }
   }
 
-  handleUsernameChange(e) {
-    this.setState({ username: e.target.value })
+  handleUsernameChange(username: string) {
+    this.setState({ username })
   }
 
-  handlePasswordChange(e) {
-    this.setState({ password: e.target.value })
+  handlePasswordChange(password: string) {
+    this.setState({ password })
   }
 
-  handleFormSubmit(e) {
+  handleFormSubmit(e: Event) {
     e.preventDefault()
 
     if (this.state.username.length === 0 || this.state.password.length === 0) {
@@ -162,7 +165,7 @@ class LoginForm extends React.Component {
       : <Button style={buttonStyle}>Login</Button>
 
     return (
-      <Form className={this.props.className} onSubmit={this.handleFormSubmit}>
+      <Form className={this.props.className} onSubmit={(e) => { this.handleFormSubmit(e) }}>
         {this.renderErrorMessage()}
         <LoginOptions />
         {loginSeparator}
@@ -171,12 +174,12 @@ class LoginForm extends React.Component {
             label="Username"
             value={this.state.username}
             name="username"
-            onChange={this.handleUsernameChange}
+            onChange={e => { this.handleUsernameChange(e.target.value) }}
           />
           <LoginFormField
             label="Password"
             value={this.state.password}
-            onChange={this.handlePasswordChange}
+            onChange={e => { this.handlePasswordChange(e.target.value) }}
             name="password"
             type="password"
           />

+ 26 - 17
src/components/organisms/MainDetails/MainDetails.jsx → src/components/organisms/MainDetails/index.jsx

@@ -12,12 +12,19 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import React from 'react'
+// @flow
+
+import * as React from 'react'
 import styled, { css } from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { EndpointLogos, Table, CopyValue, StatusIcon, StatusImage } from 'components'
+import EndpointLogos from '../../atoms/EndpointLogos'
+import CopyValue from '../../atoms/CopyValue'
+import StatusIcon from '../../atoms/StatusIcon'
+import StatusImage from '../../atoms/StatusImage'
+import Table from '../../molecules/Table'
 
+import type { MainItem } from '../../../types/MainItem'
+import type { Endpoint } from '../../../types/Endpoint'
 import StyleProps from '../../styleUtils/StyleProps'
 import Palette from '../../styleUtils/Palette'
 import DateUtils from '../../../utils/DateUtils'
@@ -77,20 +84,19 @@ const Loading = styled.div`
   height: 200px;
 `
 
-class MainDetails extends React.Component {
-  static propTypes = {
-    item: PropTypes.object.isRequired,
-    endpoints: PropTypes.array.isRequired,
-    bottomControls: PropTypes.node,
-    loading: PropTypes.bool,
-  }
-
-  getSourceEndpoint() {
+type Props = {
+  item: MainItem,
+  endpoints: Endpoint[],
+  bottomControls: React.Node,
+  loading: boolean,
+}
+class MainDetails extends React.Component<Props> {
+  getSourceEndpoint(): ?Endpoint {
     let endpoint = this.props.endpoints.find(e => e.id === this.props.item.origin_endpoint_id)
     return endpoint
   }
 
-  getDestinationEndpoint() {
+  getDestinationEndpoint(): ?Endpoint {
     let endpoint = this.props.endpoints.find(e => e.id === this.props.item.destination_endpoint_id)
     return endpoint
   }
@@ -112,7 +118,7 @@ class MainDetails extends React.Component {
     return '-'
   }
 
-  getConnectedVms(networkId) {
+  getConnectedVms(networkId: string) {
     let vms = []
     Object.keys(this.props.item.info).forEach(key => {
       let instance = this.props.item.info[key]
@@ -139,6 +145,7 @@ class MainDetails extends React.Component {
         newItem = [
           this.props.item.destination_environment.network_map[key].source_network,
           this.getConnectedVms(key),
+          // $FlowIssue
           this.props.item.destination_environment.network_map[key].destination_network,
           'Existing network',
         ]
@@ -176,7 +183,7 @@ class MainDetails extends React.Component {
     )
   }
 
-  renderEndpointLink(type) {
+  renderEndpointLink(type: string): React.Node {
     let endpointIsMissing = (
       <Value flex>
         <StatusIcon style={{ marginRight: '8px' }} status="ERROR" />Endpoint is missing
@@ -196,6 +203,8 @@ class MainDetails extends React.Component {
     if (this.props.loading) {
       return null
     }
+    const sourceEndpoint = this.getSourceEndpoint()
+    const destinationEndpoint = this.getDestinationEndpoint()
 
     return (
       <ColumnsLayout>
@@ -207,7 +216,7 @@ class MainDetails extends React.Component {
             </Field>
           </Row>
           <Row>
-            <EndpointLogos endpoint={this.getSourceEndpoint() && this.getSourceEndpoint().type} />
+            <EndpointLogos endpoint={sourceEndpoint ? sourceEndpoint.type : ''} />
           </Row>
           <Row marginBottom>
             <Field>
@@ -239,7 +248,7 @@ class MainDetails extends React.Component {
             </Field>
           </Row>
           <Row>
-            <EndpointLogos endpoint={this.getDestinationEndpoint() && this.getDestinationEndpoint().type} />
+            <EndpointLogos endpoint={destinationEndpoint ? destinationEndpoint.type : ''} />
           </Row>
           <Row marginBottom>
             <Field>

+ 28 - 20
src/components/organisms/MainList/MainList.jsx → src/components/organisms/MainList/index.jsx

@@ -12,12 +12,15 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import React from 'react'
+// @flow
+
+import * as React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { StatusImage, Button } from 'components'
+import StatusImage from '../../atoms/StatusImage'
+import Button from '../../atoms/Button'
 
+import type { MainItem } from '../../../types/MainItem'
 import Palette from '../../styleUtils/Palette'
 
 const Wrapper = styled.div`
@@ -63,23 +66,28 @@ const EmptyListExtraMessage = styled.div`
   text-align: center;
   margin: 10px 0 25px 0;
 `
-
-class MainList extends React.Component {
-  static propTypes = {
-    items: PropTypes.array,
-    selectedItems: PropTypes.array,
-    loading: PropTypes.bool,
-    onSelectedChange: PropTypes.func,
-    onItemClick: PropTypes.func,
-    renderItemComponent: PropTypes.func,
-    showEmptyList: PropTypes.bool,
-    emptyListImage: PropTypes.string,
-    emptyListMessage: PropTypes.string,
-    emptyListExtraMessage: PropTypes.string,
-    emptyListButtonLabel: PropTypes.string,
-    onEmptyListButtonClick: PropTypes.func,
-  }
-
+export type ItemComponentProps = {
+  key: string,
+  item: MainItem,
+  selected: boolean,
+  onClick: () => void,
+  onSelectedChange: (checked: boolean) => void
+}
+type Props = {
+  items: MainItem[],
+  selectedItems: MainItem[],
+  loading: boolean,
+  onSelectedChange: (item: MainItem, checked: boolean) => void,
+  onItemClick: (item: MainItem) => void,
+  renderItemComponent: (componentProps: ItemComponentProps) => React.Node,
+  showEmptyList: boolean,
+  emptyListImage: string,
+  emptyListMessage: string,
+  emptyListExtraMessage: string,
+  emptyListButtonLabel: string,
+  onEmptyListButtonClick: () => void,
+}
+class MainList extends React.Component<Props> {
   renderList() {
     if (!this.props.items || this.props.items.length === 0) {
       return null

+ 17 - 11
src/components/organisms/MigrationDetailsContent/MigrationDetailsContent.jsx → src/components/organisms/MigrationDetailsContent/index.jsx

@@ -12,11 +12,18 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { DetailsNavigation, MainDetails, Button, Tasks } from 'components'
+import Button from '../../atoms/Button'
+import DetailsNavigation from '../../molecules/DetailsNavigation'
+import MainDetails from '../../organisms/MainDetails'
+import Tasks from '../../organisms/Tasks'
+
+import type { MainItem } from '../../../types/MainItem'
+import type { Endpoint } from '../../../types/Endpoint'
 
 const Wrapper = styled.div`
   display: flex;
@@ -43,15 +50,14 @@ const NavigationItems = [
   },
 ]
 
-class MigrationDetailsContent extends React.Component {
-  static propTypes = {
-    item: PropTypes.object,
-    detailsLoading: PropTypes.bool,
-    endpoints: PropTypes.array,
-    page: PropTypes.string,
-    onDeleteMigrationClick: PropTypes.func,
-  }
-
+type Props = {
+  item: MainItem,
+  detailsLoading: boolean,
+  endpoints: Endpoint[],
+  page: string,
+  onDeleteMigrationClick: () => void,
+}
+class MigrationDetailsContent extends React.Component<Props> {
   renderBottomControls() {
     return (
       <Buttons>

+ 5 - 8
src/components/organisms/Navigation/Navigation.jsx → src/components/organisms/Navigation/index.jsx

@@ -12,11 +12,12 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 
-import { Logo } from 'components'
+import Logo from '../../atoms/Logo'
 
 import backgroundImage from './images/star-bg.jpg'
 
@@ -32,7 +33,7 @@ const LogoStyled = styled(Logo) `
   cursor: pointer;
 `
 
-const Menu = styled.div`margin-top:32px`
+const Menu = styled.div`margin-top:32px;`
 
 const MenuItem = styled.a`
   font-size: 18px;
@@ -58,11 +59,7 @@ const MenuItems = [
   },
 ]
 
-class Navigation extends React.Component {
-  static propTypes = {
-    currentPage: PropTypes.string,
-  }
-
+class Navigation extends React.Component<{ currentPage: string }> {
   renderMenu() {
     return (
       <Menu>

+ 12 - 4
src/components/organisms/Notifications/Notifications.jsx → src/components/organisms/Notifications/index.jsx

@@ -12,21 +12,29 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled, { injectGlobal } from 'styled-components'
 import NotificationSystem from 'react-notification-system'
 
-import NotificationsStyle from './NotificationsStyle.js'
-
 import NotificationStore from '../../../stores/NotificationStore'
 
+import NotificationsStyle from './style.js'
+
 injectGlobal`
   ${NotificationsStyle}
 `
 
 const Wrapper = styled.div``
 
-class Notifications extends React.Component {
+type StoreState = {
+  notifications: { message: string, level: string, action: string }[],
+}
+class Notifications extends React.Component<{}> {
+  notificationsCount: number
+  notificationSystem: NotificationSystem
+
   constructor() {
     super()
     this.notificationsCount = 0
@@ -40,7 +48,7 @@ class Notifications extends React.Component {
     NotificationStore.unlisten(this.onStoreChange.bind(this))
   }
 
-  onStoreChange(state) {
+  onStoreChange(state: StoreState) {
     if (!state.notifications.length || state.notifications.length <= this.notificationsCount) {
       return
     }

+ 1 - 1
src/components/organisms/Notifications/NotificationsStyle.js → src/components/organisms/Notifications/style.js

@@ -12,7 +12,7 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-/* eslint-disable max-len */
+// @flow
 
 import { css } from 'styled-components'
 import Palette from '../../styleUtils/Palette'

+ 31 - 25
src/components/organisms/PageHeader/PageHeader.jsx → src/components/organisms/PageHeader/index.jsx

@@ -12,20 +12,20 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 import connectToStores from 'alt-utils/lib/connectToStores'
 
-import {
-  Dropdown,
-  NewItemDropdown,
-  NotificationDropdown,
-  UserDropdown,
-  Modal,
-  ChooseProvider,
-  Endpoint,
-} from 'components'
+import Dropdown from '../../molecules/Dropdown'
+import NewItemDropdown from '../../molecules/NewItemDropdown'
+import type { ItemType } from '../../molecules/NewItemDropdown'
+import NotificationDropdown from '../../molecules/NotificationDropdown'
+import UserDropdown from '../../molecules/UserDropdown'
+import Modal from '../../molecules/Modal'
+import ChooseProvider from '../../organisms/ChooseProvider'
+import Endpoint from '../../organisms/Endpoint'
 
 import ProjectStore from '../../../stores/ProjectStore'
 import UserStore from '../../../stores/UserStore'
@@ -37,6 +37,8 @@ import ProviderStore from '../../../stores/ProviderStore'
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 
+import type { Project } from '../../../types/Project'
+
 const Wrapper = styled.div`
   display: flex;
   margin: 48px 0;
@@ -59,23 +61,27 @@ const Controls = styled.div`
   }
 `
 
-class PageHeader extends React.Component {
-  static propTypes = {
-    title: PropTypes.string.isRequired,
-    onProjectChange: PropTypes.func,
-    onModalOpen: PropTypes.func,
-    onModalClose: PropTypes.func,
-    projectStore: PropTypes.object,
-    userStore: PropTypes.object,
-    providerStore: PropTypes.object,
-    notificationStore: PropTypes.object,
-  }
-
+type Props = {
+  title: string,
+  onProjectChange: (project: Project) => void,
+  onModalOpen: () => void,
+  onModalClose: () => void,
+  projectStore: any,
+  userStore: any,
+  providerStore: any,
+  notificationStore: any,
+}
+type State = {
+  showChooseProviderModal: boolean,
+  showEndpointModal: boolean,
+  providerType?: string,
+}
+class PageHeader extends React.Component<Props, State> {
   static getStores() {
     return [UserStore, ProjectStore, ProviderStore, NotificationStore]
   }
 
-  static getPropsFromStores() {
+  static getPropsFromStores(): $Shape<Props> {
     return {
       userStore: UserStore.getState(),
       projectStore: ProjectStore.getState(),
@@ -117,7 +123,7 @@ class PageHeader extends React.Component {
     }
   }
 
-  handleNewItem(item) {
+  handleNewItem(item: ItemType) {
     switch (item.value) {
       case 'endpoint':
         ProviderActions.loadProviders()
@@ -141,7 +147,7 @@ class PageHeader extends React.Component {
     this.setState({ showChooseProviderModal: false })
   }
 
-  handleProviderClick(providerType) {
+  handleProviderClick(providerType: string) {
     this.setState({
       showChooseProviderModal: false,
       showEndpointModal: true,

+ 35 - 24
src/components/organisms/ReplicaDetailsContent/ReplicaDetailsContent.jsx → src/components/organisms/ReplicaDetailsContent/index.jsx

@@ -12,11 +12,19 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { DetailsNavigation, MainDetails, Button, Executions, Schedule } from 'components'
+import Button from '../../atoms/Button'
+import DetailsNavigation from '../../molecules/DetailsNavigation'
+import MainDetails from '../../organisms/MainDetails'
+import Executions from '../../organisms/Executions'
+import Schedule from '../../organisms/Schedule'
+import type { MainItem } from '../../../types/MainItem'
+import type { Endpoint } from '../../../types/Endpoint'
+import type { Execution } from '../../../types/Execution'
 
 const Wrapper = styled.div`
   display: flex;
@@ -56,24 +64,27 @@ const NavigationItems = [
   },
 ]
 
-class ReplicaDetailsContent extends React.Component {
-  static propTypes = {
-    item: PropTypes.object,
-    endpoints: PropTypes.array,
-    scheduleStore: PropTypes.object,
-    page: PropTypes.string,
-    detailsLoading: PropTypes.bool,
-    onCancelExecutionClick: PropTypes.func,
-    onDeleteExecutionClick: PropTypes.func,
-    onExecuteClick: PropTypes.func,
-    onCreateMigrationClick: PropTypes.func,
-    onDeleteReplicaClick: PropTypes.func,
-    onDeleteReplicaDisksClick: PropTypes.func,
-    onAddScheduleClick: PropTypes.func,
-    onScheduleChange: PropTypes.func,
-    onScheduleRemove: PropTypes.func,
-  }
-
+type TimezoneValue = 'utc' | 'local'
+type Props = {
+  item: MainItem,
+  endpoints: Endpoint[],
+  scheduleStore: any,
+  page: string,
+  detailsLoading: boolean,
+  onCancelExecutionClick: (execution: ?Execution) => void,
+  onDeleteExecutionClick: (execution: ?Execution) => void,
+  onExecuteClick: () => void,
+  onCreateMigrationClick: () => void,
+  onDeleteReplicaClick: () => void,
+  onDeleteReplicaDisksClick: () => void,
+  onAddScheduleClick: () => void,
+  onScheduleChange: () => void,
+  onScheduleRemove: () => void,
+}
+type State = {
+  timezone: TimezoneValue,
+}
+class ReplicaDetailsContent extends React.Component<Props, State> {
   constructor() {
     super()
 
@@ -92,10 +103,6 @@ class ReplicaDetailsContent extends React.Component {
     return lastExecution && lastExecution.status
   }
 
-  handleTimezoneChange(timezone) {
-    this.setState({ timezone: timezone.value })
-  }
-
   isEndpointMissing() {
     let originEndpoint = this.props.endpoints.find(e => e.id === this.props.item.origin_endpoint_id)
     let targetEndpoint = this.props.endpoints.find(e => e.id === this.props.item.destination_endpoint_id)
@@ -103,6 +110,10 @@ class ReplicaDetailsContent extends React.Component {
     return Boolean(!originEndpoint || !targetEndpoint)
   }
 
+  handleTimezoneChange(timezone: TimezoneValue) {
+    this.setState({ timezone })
+  }
+
   renderBottomControls() {
     return (
       <Buttons>

+ 19 - 16
src/components/organisms/ReplicaExecutionOptions/ReplicaExecutionOptions.jsx → src/components/organisms/ReplicaExecutionOptions/index.jsx

@@ -12,19 +12,21 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { WizardOptionsField, Button } from 'components'
+import Button from '../../atoms/Button'
+import WizardOptionsField from '../../molecules/WizardOptionsField'
 
 import LabelDictionary from '../../../utils/LabelDictionary'
 import KeyboardManager from '../../../utils/KeyboardManager'
 import { executionOptions } from '../../../config'
+import type { Field } from '../../../types/Field'
 
 import executionImage from './images/execution.svg'
 
-
 const Wrapper = styled.div`
   display: flex;
   flex-direction: column;
@@ -50,17 +52,18 @@ const WizardOptionsFieldStyled = styled(WizardOptionsField) `
   width: 319px;
   justify-content: space-between;
 `
-
-class ReplicaExecutionOptions extends React.Component {
-  static propTypes = {
-    options: PropTypes.object,
-    onChange: PropTypes.func,
-    executionLabel: PropTypes.string,
-    onCancelClick: PropTypes.func,
-    onExecuteClick: PropTypes.func,
-  }
-
-  static defaultProps = {
+type Props = {
+  options: ?{ [string]: mixed },
+  onChange: (fieldName: string, fieldValue: string) => void,
+  executionLabel: string,
+  onCancelClick: () => void,
+  onExecuteClick: (options: Field[]) => void,
+}
+type State = {
+  fields: Field[],
+}
+class ReplicaExecutionOptions extends React.Component<Props, State> {
+  static defaultProps: $Shape<Props> = {
     executionLabel: 'Execute',
   }
 
@@ -80,7 +83,7 @@ class ReplicaExecutionOptions extends React.Component {
     KeyboardManager.removeKeyDown('execution-options')
   }
 
-  getFieldValue(field) {
+  getFieldValue(field: Field) {
     if (!this.props.options || this.props.options[field.name] === null || this.props.options[field.name] === undefined) {
       return field.value
     }
@@ -88,7 +91,7 @@ class ReplicaExecutionOptions extends React.Component {
     return this.props.options[field.name]
   }
 
-  handleValueChange(field, value) {
+  handleValueChange(field: Field, value: string) {
     let fields = this.state.fields.map(f => {
       if (f.name === field.name) {
         return { ...f, value }

+ 21 - 11
src/components/organisms/ReplicaMigrationOptions/ReplicaMigrationOptions.jsx → src/components/organisms/ReplicaMigrationOptions/index.jsx

@@ -12,15 +12,18 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { Button, WizardOptionsField } from 'components'
+import Button from '../../atoms/Button'
+import WizardOptionsField from '../../molecules/WizardOptionsField'
 
 import LabelDictionary from '../../../utils/LabelDictionary'
 import KeyboardManager from '../../../utils/KeyboardManager'
 import replicaMigrationImage from './images/replica-migration.svg'
+import type { Field } from '../../../types/Field'
 
 const Wrapper = styled.div`
   display: flex;
@@ -52,12 +55,14 @@ const WizardOptionsFieldStyled = styled(WizardOptionsField)`
   margin-bottom: 32px;
 `
 
-class ReplicaMigrationOptions extends React.Component {
-  static propTypes = {
-    onCancelClick: PropTypes.func,
-    onMigrateClick: PropTypes.func,
-  }
-
+type Props = {
+  onCancelClick: () => void,
+  onMigrateClick: (fields: Field[]) => void,
+}
+type State = {
+  fields: Field[],
+}
+class ReplicaMigrationOptions extends React.Component<Props, State> {
   constructor() {
     super()
 
@@ -88,9 +93,14 @@ class ReplicaMigrationOptions extends React.Component {
     KeyboardManager.removeKeyDown('migration-options')
   }
 
-  handleValueChange(field, value) {
-    this.state.fields.find(f => f.name === field.name).value = value
-    this.setState({ fields: this.state.fields })
+  handleValueChange(field: Field, value: boolean) {
+    let foundField = this.state.fields.find(f => f.name === field.name)
+    if (!foundField) {
+      return
+    }
+    foundField.value = value
+
+    this.setState({ fields: [...this.state.fields] })
   }
 
   render() {

+ 71 - 61
src/components/organisms/Schedule/Schedule.jsx → src/components/organisms/Schedule/index.jsx

@@ -12,27 +12,28 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 import moment from 'moment'
 
-import {
-  Switch,
-  Dropdown,
-  Button,
-  ReplicaExecutionOptions,
-  Modal,
-  DropdownLink,
-  StatusImage,
-  AlertModal,
-} from 'components'
-
-import DatetimePicker from '../../molecules/DatetimePicker/DatetimePicker'
+import Button from '../../atoms/Button'
+import StatusImage from '../../atoms/StatusImage'
+import Switch from '../../atoms/Switch'
+import Dropdown from '../../molecules/Dropdown'
+import Modal from '../../molecules/Modal'
+import DropdownLink from '../../molecules/DropdownLink'
+import DatetimePicker from '../../molecules/DatetimePicker'
+import AlertModal from '../../organisms/AlertModal'
+import ReplicaExecutionOptions from '../../organisms/ReplicaExecutionOptions'
+
 import StyleProps from '../../styleUtils/StyleProps'
 import Palette from '../../styleUtils/Palette'
 import NotificationActions from '../../../actions/NotificationActions'
 import DateUtils from '../../../utils/DateUtils'
+import type { Schedule as ScheduleType, ScheduleInfo as ScheduleInfoType } from '../../../types/Schedule'
+import type { Field } from '../../../types/Field'
 
 import deleteImage from './images/delete.svg'
 import deleteHoverImage from './images/delete-hover.svg'
@@ -141,21 +142,29 @@ const TimezoneLabel = styled.div`
   margin-right: 4px;
 `
 
+type TimeZoneValue = 'local' | 'utc'
+type DictItem = { label: string, value: any }
+type Props = {
+  schedules: ScheduleType[],
+  timezone: TimeZoneValue,
+  onTimezoneChange: (timezone: TimeZoneValue) => void,
+  onAddScheduleClick: (schedule: ScheduleType) => void,
+  onChange: (scheduleId: ?string, schedule: ScheduleType) => void,
+  onRemove: (scheduleId: ?string) => void,
+  adding?: boolean,
+  loading?: boolean,
+  secondaryEmpty?: boolean,
+}
+type State = {
+  showOptionsModal: boolean,
+  showDeleteConfirmation: boolean,
+  selectedSchedule: ?ScheduleType,
+  executionOptions: ?{ [string]: mixed },
+}
+
 const colWidths = ['6%', '18%', '10%', '18%', '10%', '10%', '23%', '5%']
 const daysInMonths = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
-class Schedule extends React.Component {
-  static propTypes = {
-    schedules: PropTypes.array,
-    timezone: PropTypes.string,
-    onTimezoneChange: PropTypes.func,
-    onAddScheduleClick: PropTypes.func,
-    onChange: PropTypes.func,
-    onRemove: PropTypes.func,
-    adding: PropTypes.bool,
-    loading: PropTypes.bool,
-    secondaryEmpty: PropTypes.bool,
-  }
-
+class Schedule extends React.Component<Props, State> {
   constructor() {
     super()
 
@@ -167,7 +176,7 @@ class Schedule extends React.Component {
     }
   }
 
-  getFieldValue(schedule, items, fieldName, zeroBasedIndex, defaultSelectedIndex) {
+  getFieldValue(schedule: ?ScheduleInfoType, items: DictItem[], fieldName: string, zeroBasedIndex?: boolean, defaultSelectedIndex?: number) {
     if (schedule === null || schedule === undefined) {
       return defaultSelectedIndex !== undefined ? items[defaultSelectedIndex] : items[0]
     }
@@ -191,15 +200,7 @@ class Schedule extends React.Component {
     return items[schedule[fieldName]]
   }
 
-  padNumber(number) {
-    if (number < 10) {
-      return `0${number}`
-    }
-
-    return number
-  }
-
-  handleDeleteClick(selectedSchedule) {
+  handleDeleteClick(selectedSchedule: ScheduleType) {
     this.setState({ showDeleteConfirmation: true, selectedSchedule })
   }
 
@@ -209,10 +210,10 @@ class Schedule extends React.Component {
 
   handleDeleteConfirmation() {
     this.setState({ showDeleteConfirmation: false })
-    this.props.onRemove(this.state.selectedSchedule.id)
+    this.props.onRemove(this.state.selectedSchedule ? this.state.selectedSchedule.id : null)
   }
 
-  handleShowOptions(selectedSchedule) {
+  handleShowOptions(selectedSchedule: $Subtype<ScheduleType>) {
     this.setState({ showOptionsModal: true, executionOptions: selectedSchedule, selectedSchedule })
   }
 
@@ -220,17 +221,17 @@ class Schedule extends React.Component {
     this.setState({ showOptionsModal: false })
   }
 
-  handleOptionsSave(fields) {
+  handleOptionsSave(fields: Field[]) {
     this.setState({ showOptionsModal: false })
-    let options = {}
+    let options: ScheduleType = {}
     fields.forEach(f => {
       options[f.name] = f.value || false
     })
 
-    this.props.onChange(this.state.selectedSchedule.id, options)
+    this.props.onChange(this.state.selectedSchedule ? this.state.selectedSchedule.id : null, options)
   }
 
-  handleExecutionOptionsChange(fieldName, value) {
+  handleExecutionOptionsChange(fieldName: string, value: string) {
     let options = this.state.executionOptions
     if (!options) {
       options = {}
@@ -243,18 +244,18 @@ class Schedule extends React.Component {
     this.setState({ executionOptions: options })
   }
 
-  handleMonthChange(s, item) {
+  handleMonthChange(s: ScheduleType, item: DictItem) {
     let month = item.value || 1
     let maxNumDays = daysInMonths[month - 1]
-    let change = { schedule: { month: item.value } }
+    let change: ScheduleType = { schedule: { month: item.value } }
     if (s.schedule && s.schedule.dom && s.schedule.dom > maxNumDays) {
-      change.schedule.dom = maxNumDays
+      if (change.schedule) change.schedule.dom = maxNumDays
     }
 
     this.props.onChange(s.id, change)
   }
 
-  handleExpirationDateChange(s, date) {
+  handleExpirationDateChange(s: ScheduleType, date: Date) {
     let newDate = moment(date)
     if (newDate.diff(new Date(), 'minutes') < 60) {
       NotificationActions.notify('Please select a further expiration date.', 'error')
@@ -264,7 +265,7 @@ class Schedule extends React.Component {
     this.props.onChange(s.id, { expiration_date: newDate.toDate() })
   }
 
-  handleHourChange(s, hour) {
+  handleHourChange(s: ScheduleType, hour: number) {
     if (this.props.timezone === 'local' && hour !== null && hour !== undefined) {
       hour = DateUtils.getUtcHour(hour)
     }
@@ -280,6 +281,14 @@ class Schedule extends React.Component {
     this.props.onAddScheduleClick({ schedule: { hour, minute: 0 } })
   }
 
+  padNumber(number: number) {
+    if (number < 10) {
+      return `0${number}`
+    }
+
+    return number.toString()
+  }
+
   renderLoading() {
     if (!this.props.loading) {
       return null
@@ -305,11 +314,11 @@ class Schedule extends React.Component {
     )
   }
 
-  renderLabel(value) {
+  renderLabel(value: DictItem) {
     return <Label>{value.label}</Label>
   }
 
-  renderMonthValue(s) {
+  renderMonthValue(s: ScheduleType) {
     let items = [{ label: 'Any', value: null }]
     let months = moment.months()
     months.forEach((label, value) => {
@@ -331,11 +340,11 @@ class Schedule extends React.Component {
     )
   }
 
-  renderDayOfMonthValue(s) {
-    let month = (s.schedule && s.schedule.month) || 1
+  renderDayOfMonthValue(s: ScheduleType) {
+    let month = s.schedule ? s.schedule.month || 1 : 1
     let items = [{ label: 'Any', value: null }]
     for (let i = 1; i <= daysInMonths[month - 1]; i += 1) {
-      items.push({ label: i, value: i })
+      items.push({ label: i.toString(), value: i })
     }
 
     if (s.enabled) {
@@ -353,8 +362,9 @@ class Schedule extends React.Component {
     )
   }
 
-  renderDayOfWeekValue(s) {
+  renderDayOfWeekValue(s: ScheduleType) {
     let items = [{ label: 'Any', value: null }]
+    // $FlowIssue
     let days = moment.weekdays(true)
     days.forEach((label, value) => {
       items.push({ label, value })
@@ -375,7 +385,7 @@ class Schedule extends React.Component {
     )
   }
 
-  renderHourValue(s) {
+  renderHourValue(s: ScheduleType) {
     let items = [{ label: 'Any', value: null }]
     for (let i = 0; i <= 23; i += 1) {
       items.push({ label: this.padNumber(i), value: i })
@@ -396,7 +406,7 @@ class Schedule extends React.Component {
     )
   }
 
-  renderMinuteValue(s) {
+  renderMinuteValue(s: ScheduleType) {
     let items = [{ label: 'Any', value: null }]
     for (let i = 0; i <= 59; i += 1) {
       items.push({ label: this.padNumber(i), value: i })
@@ -417,23 +427,23 @@ class Schedule extends React.Component {
     )
   }
 
-  renderExpirationValue(s) {
-    let date = s.expiration_date && moment(s.expiration_date)
+  renderExpirationValue(s: ScheduleType) {
+    let date = s.expiration_date ? moment(s.expiration_date) : null
     let labelDate = date
     if (this.props.timezone === 'utc' && date) {
       labelDate = DateUtils.getUtcTime(date)
     }
 
     if (s.enabled) {
-      return this.renderLabel({ label: (labelDate && labelDate.format('DD/MM/YYYY hh:mm A')) || '-' })
+      return this.renderLabel({ label: labelDate ? labelDate.format('DD/MM/YYYY hh:mm A') : '-', value: '' })
     }
 
     return (
       <DatetimePicker
-        value={date}
+        value={date ? date.toDate() : null}
         timezone={this.props.timezone}
         onChange={date => { this.handleExpirationDateChange(s, date) }}
-        isValidDate={date => date.isAfter(moment())}
+        isValidDate={date => moment(date).isAfter(moment())}
       />
     )
   }
@@ -539,7 +549,7 @@ class Schedule extends React.Component {
           <DropdownLink
             items={timezoneItems}
             selectedItem={selectedItem}
-            onChange={this.props.onTimezoneChange}
+            onChange={item => { this.props.onTimezoneChange(item.value === 'utc' ? 'utc' : 'local') }}
           />
         </Timezone>
       </Footer>

+ 19 - 13
src/components/organisms/Tasks/Tasks.jsx → src/components/organisms/Tasks/index.jsx

@@ -12,12 +12,14 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { TaskItem } from 'components'
+import TaskItem from '../../molecules/TaskItem'
 
+import type { Task } from '../../../types/Task'
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 
@@ -40,10 +42,14 @@ const HeaderData = styled.div`
 `
 const Body = styled.div``
 
-class Tasks extends React.Component {
-  static propTypes = {
-    items: PropTypes.array,
-  }
+type Props = {
+  items: Task[],
+}
+type State = {
+  openedItems: Task[],
+}
+class Tasks extends React.Component<Props, State> {
+  dragStartPosition: ?{ x: number, y: number }
 
   constructor() {
     super()
@@ -57,7 +63,7 @@ class Tasks extends React.Component {
     this.componentWillReceiveProps(this.props)
   }
 
-  componentWillReceiveProps(props) {
+  componentWillReceiveProps(props: Props) {
     let openedItems = this.state.openedItems
 
     props.items.forEach(item => {
@@ -76,26 +82,26 @@ class Tasks extends React.Component {
     this.setState({ openedItems })
   }
 
-  handleItemMouseDown(e) {
+  handleItemMouseDown(e: MouseEvent) {
     this.dragStartPosition = { x: e.screenX, y: e.screenY }
   }
 
-  handleItemMouseUp(e, item) {
+  handleItemMouseUp(e: MouseEvent, item: Task) {
     this.dragStartPosition = this.dragStartPosition || { x: e.screenX, y: e.screenY }
 
-    if (Math.abs(this.dragStartPosition.x - e.screenX) < 3 && Math.abs(this.dragStartPosition.y - e.screenY) < 3) {
+    if (this.dragStartPosition && Math.abs(this.dragStartPosition.x - e.screenX) < 3 && Math.abs(this.dragStartPosition.y - e.screenY) < 3) {
       this.toggleItem(item)
     }
 
     this.dragStartPosition = null
   }
 
-  handleDependsOnClick(id) {
+  handleDependsOnClick(id: string) {
     let item = this.props.items.find(i => i.id === id)
-    this.toggleItem(item)
+    if (item) this.toggleItem(item)
   }
 
-  toggleItem(item) {
+  toggleItem(item: Task) {
     let openedItems = this.state.openedItems
     if (openedItems.find(i => i.id === item.id)) {
       openedItems = openedItems.filter(i => i.id !== item.id)

+ 24 - 19
src/components/organisms/WizardEndpointList/WizardEndpointList.jsx → src/components/organisms/WizardEndpointList/index.jsx

@@ -12,11 +12,17 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled, { css } from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { EndpointLogos, Dropdown, StatusImage, Button } from 'components'
+import EndpointLogos from '../../atoms/EndpointLogos'
+import Dropdown from '../../molecules/Dropdown'
+import StatusImage from '../../atoms/StatusImage'
+import Button from '../../atoms/Button'
+
+import type { Endpoint } from '../../../types/Endpoint'
 
 const Wrapper = styled.div`
   display: flex;
@@ -55,27 +61,26 @@ const Row = styled.div`
   `}
 `
 
-class WizardEndpointList extends React.Component {
-  static propTypes = {
-    providers: PropTypes.array,
-    endpoints: PropTypes.array,
-    loading: PropTypes.bool,
-    selectedEndpoint: PropTypes.object,
-    otherEndpoint: PropTypes.object,
-    onChange: PropTypes.func,
-    onAddEndpoint: PropTypes.func,
-  }
-
-  handleOnChange(selectedItem) {
-    if (selectedItem.id !== 'addNew') {
+type Props = {
+  providers: string[],
+  endpoints: Endpoint[],
+  loading: boolean,
+  selectedEndpoint: Endpoint,
+  otherEndpoint: Endpoint,
+  onChange: (endpoint: Endpoint) => void,
+  onAddEndpoint: (provider: string) => void,
+}
+class WizardEndpointList extends React.Component<Props> {
+  handleOnChange(selectedItem: ?Endpoint, provider: string) {
+    if (selectedItem && selectedItem.id !== 'addNew') {
       this.props.onChange(selectedItem)
       return
     }
 
-    this.props.onAddEndpoint(selectedItem.provider)
+    this.props.onAddEndpoint(provider)
   }
 
-  renderProvider(provider) {
+  renderProvider(provider: string) {
     let otherEndpoint = this.props.otherEndpoint
     let items = this.props.endpoints.filter(e => e.type === provider && (!otherEndpoint || otherEndpoint.id !== e.id))
     let selectedItem = this.props.selectedEndpoint && this.props.selectedEndpoint.type === provider
@@ -95,7 +100,7 @@ class WizardEndpointList extends React.Component {
           noSelectionMessage="Select"
           centered
           selectedItem={selectedItem}
-          onChange={selectedItem => { this.handleOnChange(selectedItem) }}
+          onChange={selectedItem => { selectedItem.id === 'addNew' ? this.handleOnChange(null, selectedItem.provider) : this.handleOnChange(selectedItem, selectedItem.provider) }}
         />
       )
     } else {
@@ -104,7 +109,7 @@ class WizardEndpointList extends React.Component {
           secondary
           hollow
           hoverPrimary
-          onClick={() => { this.handleOnChange({ id: 'addNew', provider }) }}
+          onClick={() => { this.props.onAddEndpoint(provider) }}
         >Add</Button>
       )
     }

+ 34 - 29
src/components/organisms/WizardInstances/WizardInstances.jsx → src/components/organisms/WizardInstances/index.jsx

@@ -12,22 +12,22 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled, { css } from 'styled-components'
-import PropTypes from 'prop-types'
-
-import {
-  Checkbox,
-  SearchInput,
-  ReloadButton,
-  Arrow,
-  StatusIcon,
-  StatusImage,
-  Button,
-} from 'components'
+
+import Checkbox from '../../atoms/Checkbox'
+import ReloadButton from '../../atoms/ReloadButton'
+import Arrow from '../../atoms/Arrow'
+import StatusIcon from '../../atoms/StatusIcon'
+import StatusImage from '../../atoms/StatusImage'
+import Button from '../../atoms/Button'
+import SearchInput from '../../molecules/SearchInput'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
+import type { Instance as InstanceType } from '../../../types/Instance'
 
 import instanceImage from './images/instance.svg'
 import bigInstanceImage from './images/instance-big.svg'
@@ -178,23 +178,28 @@ const BigInstanceImage = styled.div`
   background: url('${bigInstanceImage}') center no-repeat;
 `
 
-class WizardInstances extends React.Component {
-  static propTypes = {
-    instances: PropTypes.array,
-    selectedInstances: PropTypes.array,
-    currentPage: PropTypes.number,
-    loading: PropTypes.bool,
-    searching: PropTypes.bool,
-    searchNotFound: PropTypes.bool,
-    loadingPage: PropTypes.bool,
-    hasNextPage: PropTypes.bool,
-    reloading: PropTypes.bool,
-    onSearchInputChange: PropTypes.func,
-    onNextPageClick: PropTypes.func,
-    onPreviousPageClick: PropTypes.func,
-    onReloadClick: PropTypes.func,
-    onInstanceClick: PropTypes.func,
-  }
+type Props = {
+  instances: InstanceType[],
+  selectedInstances: InstanceType[],
+  currentPage: number,
+  loading: boolean,
+  searching: boolean,
+  searchNotFound: boolean,
+  loadingPage: boolean,
+  hasNextPage: boolean,
+  reloading: boolean,
+  onSearchInputChange: (value: string) => void,
+  onNextPageClick: (searchText: string) => void,
+  onPreviousPageClick: () => void,
+  onReloadClick: (searchText: string) => void,
+  onInstanceClick: (instance: InstanceType) => void,
+}
+
+type State = {
+  searchText: string,
+}
+class WizardInstances extends React.Component<Props, State> {
+  timeout: TimeoutID
 
   constructor() {
     super()
@@ -204,7 +209,7 @@ class WizardInstances extends React.Component {
     }
   }
 
-  handleSeachInputChange(searchText) {
+  handleSeachInputChange(searchText: string) {
     clearTimeout(this.timeout)
     this.timeout = setTimeout(() => {
       this.setState({ searchText })

+ 16 - 13
src/components/organisms/WizardNetworks/WizardNetworks.jsx → src/components/organisms/WizardNetworks/index.jsx

@@ -12,14 +12,18 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { StatusImage, Dropdown } from 'components'
+import StatusImage from '../../atoms/StatusImage'
+import Dropdown from '../../molecules/Dropdown'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
+import type { Instance, Nic as NicType } from '../../../types/Instance'
+import type { Network } from '../../../types/Network'
 
 import networkImage from './images/network.svg'
 import bigNetworkImage from './images/network-big.svg'
@@ -100,16 +104,15 @@ const NoNicsSubtitle = styled.div`
   text-align: center;
 `
 
-class WizardNetworks extends React.Component {
-  static propTypes = {
-    loading: PropTypes.bool,
-    loadingInstancesDetails: PropTypes.bool,
-    networks: PropTypes.array,
-    instancesDetails: PropTypes.array,
-    selectedNetworks: PropTypes.array,
-    onChange: PropTypes.func,
-  }
-
+type Props = {
+  loading: boolean,
+  loadingInstancesDetails: boolean,
+  networks: Network[],
+  instancesDetails: Instance[],
+  selectedNetworks: Network[],
+  onChange: (nic: NicType, network: Network) => void,
+}
+class WizardNetworks extends React.Component<Props> {
   isLoading() {
     return this.props.loadingInstancesDetails || this.props.loading
   }
@@ -190,7 +193,7 @@ class WizardNetworks extends React.Component {
                 selectedItem={selectedNetworkName}
                 items={this.props.networks}
                 labelField="name"
-                onChange={item => { this.props.onChange(nic, item) }}
+                onChange={(item: Network) => { this.props.onChange(nic, item) }}
               />
             </Nic>
           )

+ 19 - 15
src/components/organisms/WizardOptions/WizardOptions.jsx → src/components/organisms/WizardOptions/index.jsx

@@ -12,11 +12,16 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 
-import { ToggleButtonBar, WizardOptionsField, Tooltip } from 'components'
+import Tooltip from '../../atoms/Tooltip'
+import ToggleButtonBar from '../../atoms/ToggleButtonBar'
+import WizardOptionsField from '../../molecules/WizardOptionsField'
+import type { Field } from '../../../types/Field'
+import type { Instance } from '../../../types/Instance'
 
 import { executionOptions } from '../../../config'
 
@@ -35,18 +40,17 @@ const WizardOptionsFieldStyled = styled(WizardOptionsField) `
   margin-bottom: 39px;
 `
 
-class WizardOptions extends React.Component {
-  static propTypes = {
-    fields: PropTypes.array,
-    selectedInstances: PropTypes.array,
-    data: PropTypes.object,
-    onChange: PropTypes.func,
-    useAdvancedOptions: PropTypes.bool,
-    onAdvancedOptionsToggle: PropTypes.func,
-    wizardType: PropTypes.string,
-  }
-
-  getFieldValue(fieldName, defaultValue) {
+type Props = {
+  fields: Field[],
+  selectedInstances: Instance[],
+  data: { [string]: mixed },
+  onChange: (field: Field, value: any) => void,
+  useAdvancedOptions: boolean,
+  onAdvancedOptionsToggle: (showAdvanced: boolean) => void,
+  wizardType: string,
+}
+class WizardOptions extends React.Component<Props> {
+  getFieldValue(fieldName: string, defaultValue: any) {
     if (!this.props.data || this.props.data[fieldName] === undefined) {
       return defaultValue
     }
@@ -85,7 +89,7 @@ class WizardOptions extends React.Component {
     return fieldsSchema
   }
 
-  renderOptionsField(field) {
+  renderOptionsField(field: Field) {
     if (field.type === 'object' && field.properties) {
       return (
         <WizardOptionsFieldStyled

+ 53 - 49
src/components/organisms/WizardPageContent/WizardPageContent.jsx → src/components/organisms/WizardPageContent/index.jsx

@@ -12,26 +12,27 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
-
-import {
-  WizardType,
-  Button,
-  WizardBreadcrumbs,
-  EndpointLogos,
-  WizardEndpointList,
-  WizardInstances,
-  WizardNetworks,
-  WizardOptions,
-  Schedule,
-  WizardSummary,
-} from 'components'
+
+import EndpointLogos from '../../atoms/EndpointLogos'
+import WizardType from '../../molecules/WizardType'
+import Button from '../../atoms/Button'
+import WizardBreadcrumbs from '../../molecules/WizardBreadcrumbs'
+import WizardEndpointList from '../WizardEndpointList'
+import WizardInstances from '../WizardInstances'
+import WizardNetworks from '../WizardNetworks'
+import WizardOptions from '../WizardOptions'
+import Schedule from '../Schedule'
+import WizardSummary from '../WizardSummary'
 
 import StyleProps from '../../styleUtils/StyleProps'
 import Palette from '../../styleUtils/Palette'
 import { providerTypes, wizardConfig } from '../../../config'
+import type { WizardData } from '../../../types/WizardData'
+import type { Endpoint } from '../../../types/Endpoint'
 
 import migrationArrowImage from './images/migration.js'
 
@@ -81,36 +82,39 @@ const WizardTypeIcon = styled.div`
   justify-content: center;
   align-items: center;
 `
-
-class WizardPageContent extends React.Component {
-  static propTypes = {
-    page: PropTypes.object,
-    type: PropTypes.string,
-    nextButtonDisabled: PropTypes.bool,
-    providerStore: PropTypes.object,
-    instanceStore: PropTypes.object,
-    networkStore: PropTypes.object,
-    wizardData: PropTypes.object,
-    endpoints: PropTypes.array,
-    onTypeChange: PropTypes.func,
-    onBackClick: PropTypes.func,
-    onNextClick: PropTypes.func,
-    onSourceEndpointChange: PropTypes.func,
-    onTargetEndpointChange: PropTypes.func,
-    onAddEndpoint: PropTypes.func,
-    onInstancesSearchInputChange: PropTypes.func,
-    onInstancesNextPageClick: PropTypes.func,
-    onInstancesPreviousPageClick: PropTypes.func,
-    onInstancesReloadClick: PropTypes.func,
-    onInstanceClick: PropTypes.func,
-    onOptionsChange: PropTypes.func,
-    onNetworkChange: PropTypes.func,
-    onAddScheduleClick: PropTypes.func,
-    onScheduleChange: PropTypes.func,
-    onScheduleRemove: PropTypes.func,
-    onContentRef: PropTypes.func,
-  }
-
+type Props = {
+  page: { id: string, title: string },
+  type: 'replica' | 'migration',
+  nextButtonDisabled: boolean,
+  providerStore: any,
+  instanceStore: any,
+  networkStore: any,
+  wizardData: WizardData,
+  endpoints: Endpoint[],
+  onTypeChange: (isReplicaChecked: ?boolean) => void,
+  onBackClick: () => void,
+  onNextClick: () => void,
+  onSourceEndpointChange: (endpoint: Endpoint) => void,
+  onTargetEndpointChange: (endpoint: Endpoint) => void,
+  onAddEndpoint: (provider: string, fromSource: boolean) => void,
+  onInstancesSearchInputChange: () => void,
+  onInstancesNextPageClick: () => void,
+  onInstancesPreviousPageClick: () => void,
+  onInstancesReloadClick: () => void,
+  onInstanceClick: () => void,
+  onOptionsChange: () => void,
+  onNetworkChange: () => void,
+  onAddScheduleClick: () => void,
+  onScheduleChange: () => void,
+  onScheduleRemove: () => void,
+  onContentRef: (ref: any) => void,
+}
+type TimezoneValue = 'local' | 'utc'
+type State = {
+  useAdvancedOptions: boolean,
+  timezone: TimezoneValue,
+}
+class WizardPageContent extends React.Component<Props, State> {
   constructor() {
     super()
 
@@ -128,7 +132,7 @@ class WizardPageContent extends React.Component {
     this.props.onContentRef(null)
   }
 
-  getProvidersType(type) {
+  getProvidersType(type: string) {
     if (this.props.type === 'replica') {
       if (type === 'source') {
         return providerTypes.SOURCE_REPLICA
@@ -142,7 +146,7 @@ class WizardPageContent extends React.Component {
     return providerTypes.TARGET_MIGRATION
   }
 
-  getProviders(type) {
+  getProviders(type: string) {
     let providers = []
     let providerType = this.getProvidersType(type)
 
@@ -215,12 +219,12 @@ class WizardPageContent extends React.Component {
     }
   }
 
-  handleAdvancedOptionsToggle(useAdvancedOptions) {
+  handleAdvancedOptionsToggle(useAdvancedOptions: boolean) {
     this.setState({ useAdvancedOptions })
   }
 
-  handleTimezoneChange(timezone) {
-    this.setState({ timezone: timezone.value })
+  handleTimezoneChange(timezone: TimezoneValue) {
+    this.setState({ timezone })
   }
 
   renderHeader() {

+ 30 - 22
src/components/organisms/WizardSummary/WizardSummary.jsx → src/components/organisms/WizardSummary/index.jsx

@@ -12,17 +12,20 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 import moment from 'moment'
 
-import { StatusPill } from 'components'
+import StatusPill from '../../atoms/StatusPill'
 
 import StyleProps from '../../styleUtils/StyleProps'
 import Palette from '../../styleUtils/Palette'
 import LabelDictionary from '../../../utils/LabelDictionary'
 import DateUtils from '../../../utils/DateUtils'
+import type { Schedule } from '../../../types/Schedule'
+import type { WizardData } from '../../../types/WizardData'
 
 import networkArrowImage from './images/network-arrow.svg'
 
@@ -117,13 +120,12 @@ const OptionLabel = styled.div`
 `
 const OptionValue = styled.div``
 
-class WizardSummary extends React.Component {
-  static propTypes = {
-    data: PropTypes.object,
-    wizardType: PropTypes.string,
-  }
-
-  getDefaultOption(fieldName) {
+type Props = {
+  data: WizardData,
+  wizardType: 'replica' | 'migration',
+}
+class WizardSummary extends React.Component<Props> {
+  getDefaultOption(fieldName: string) {
     if (this.props.data.options && this.props.data.options[fieldName] === false) {
       return false
     }
@@ -131,41 +133,47 @@ class WizardSummary extends React.Component {
     return true
   }
 
-  renderScheduleLabel(schedule) {
+  renderScheduleLabel(schedule: Schedule) {
+    let scheduleInfo = schedule.schedule
     let monthLabel
-    if (schedule.month === null || schedule.month === undefined) {
+    if (!scheduleInfo) {
+      return null
+    }
+
+    if (scheduleInfo.month === undefined || scheduleInfo.month === null) {
       monthLabel = 'Every month'
     } else {
-      monthLabel = `Every ${moment.months()[schedule.month - 1]}`
+      monthLabel = `Every ${moment.months()[scheduleInfo.month ? scheduleInfo.month - 1 : 0]}`
     }
 
     let dayOfMonthLabel
-    if (schedule.dom === null || schedule.dom === undefined) {
+    if (scheduleInfo.dom === null || scheduleInfo.dom === undefined) {
       dayOfMonthLabel = 'every day'
     } else {
-      dayOfMonthLabel = `every ${DateUtils.getOrdinalDay(schedule.dom)}`
+      dayOfMonthLabel = `every ${DateUtils.getOrdinalDay(scheduleInfo.dom)}`
     }
 
     let dayOfWeekLabel
-    if (schedule.dow === null || schedule.dow === undefined) {
+    if (scheduleInfo.dow === null || scheduleInfo.dow === undefined) {
       dayOfWeekLabel = 'every weekday'
     } else {
-      dayOfWeekLabel = `every ${moment.weekdays(true)[schedule.dow]}`
+      // $FlowIssue
+      dayOfWeekLabel = `every ${moment.weekdays(true)[scheduleInfo.dow]}`
     }
 
 
-    let padNumber = number => number < 10 ? `0${number}` : number
+    let padNumber = number => number || 0 < 10 ? `0${number || 0}` : (number || 0).toString()
     let timeLabel
     if (schedule.minute === null || schedule.minute === undefined) {
       if (schedule.hour === null || schedule.hour === undefined) {
         timeLabel = 'every hour, every minute'
       } else {
-        timeLabel = `at ${padNumber(schedule.hour)} o'clock, every minute`
+        timeLabel = `at ${padNumber(scheduleInfo.hour)} o'clock, every minute`
       }
     } else if (schedule.hour === null || schedule.hour === undefined) {
-      timeLabel = `every hour, at minute ${padNumber(schedule.minute)}`
+      timeLabel = `every hour, at minute ${padNumber(scheduleInfo.minute)}`
     } else {
-      timeLabel = `at ${padNumber(schedule.hour)}:${padNumber(schedule.minute)}`
+      timeLabel = `at ${padNumber(scheduleInfo.hour)}:${padNumber(scheduleInfo.minute)}`
     }
 
     return `${monthLabel}, ${dayOfMonthLabel}, ${dayOfWeekLabel}, ${timeLabel}`
@@ -184,7 +192,7 @@ class WizardSummary extends React.Component {
           {schedules.map(schedule => {
             return (
               <Row key={schedule.id} schedule>
-                {this.renderScheduleLabel(schedule.schedule || {})}
+                {this.renderScheduleLabel(schedule)}
               </Row>
             )
           })}
@@ -193,7 +201,7 @@ class WizardSummary extends React.Component {
     )
   }
 
-  renderOptionValue(value) {
+  renderOptionValue(value: any) {
     if (value === true) {
       return 'Yes'
     }

+ 36 - 28
src/components/pages/EndpointDetailsPage/EndpointDetailsPage.jsx → src/components/pages/EndpointDetailsPage/index.jsx

@@ -12,21 +12,20 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 import connectToStores from 'alt-utils/lib/connectToStores'
 
-import {
-  DetailsTemplate,
-  DetailsPageHeader,
-  DetailsContentHeader,
-  EndpointDetailsContent,
-  AlertModal,
-  Modal,
-  EndpointValidation,
-  Endpoint,
-} from 'components'
+import DetailsTemplate from '../../templates/DetailsTemplate'
+import { DetailsPageHeader } from '../../organisms/DetailsPageHeader'
+import DetailsContentHeader from '../../organisms/DetailsContentHeader'
+import EndpointDetailsContent from '../../organisms/EndpointDetailsContent'
+import AlertModal from '../../organisms/AlertModal'
+import Modal from '../../molecules/Modal'
+import EndpointValidation from '../../organisms/EndpointValidation'
+import Endpoint from '../../organisms/Endpoint'
 
 import EndpointStore from '../../../stores/EndpointStore'
 import EndpointActions from '../../../actions/EndpointActions'
@@ -36,20 +35,27 @@ import MigrationActions from '../../../actions/MigrationActions'
 import ReplicaActions from '../../../actions/ReplicaActions'
 import UserStore from '../../../stores/UserStore'
 import UserActions from '../../../actions/UserActions'
+import type { Endpoint as EndpointType } from '../../../types/Endpoint'
 
 import endpointImage from './images/endpoint.svg'
 
 const Wrapper = styled.div``
 
-class EndpointDetailsPage extends React.Component {
-  static propTypes = {
-    match: PropTypes.object,
-    endpointStore: PropTypes.object,
-    userStore: PropTypes.object,
-    migrationStore: PropTypes.object,
-    replicaStore: PropTypes.object,
-  }
-
+type Props = {
+  match: any,
+  endpointStore: any,
+  userStore: any,
+  migrationStore: any,
+  replicaStore: any,
+}
+type State = {
+  showDeleteEndpointConfirmation: boolean,
+  showValidationModal: boolean,
+  showEndpointModal: boolean,
+  showEndpointInUseModal: boolean,
+  showEndpointInUseLoadingModal: boolean,
+}
+class EndpointDetailsPage extends React.Component<Props, State> {
   static getStores() {
     return [EndpointStore, UserStore, MigrationStore, ReplicaStore]
   }
@@ -85,8 +91,8 @@ class EndpointDetailsPage extends React.Component {
     EndpointActions.clearConnectionInfo()
   }
 
-  getEndpoint() {
-    return this.props.endpointStore.endpoints.find(e => e.id === this.props.match.params.id) || {}
+  getEndpoint(): ?EndpointType {
+    return this.props.endpointStore.endpoints.find(e => e.id === this.props.match.params.id) || null
   }
 
   getEndpointUsage() {
@@ -99,7 +105,7 @@ class EndpointDetailsPage extends React.Component {
     return { migrationsCount, replicasCount }
   }
 
-  handleUserItemClick(item) {
+  handleUserItemClick(item: { value: string }) {
     switch (item.value) {
       case 'signout':
         UserActions.logout()
@@ -173,15 +179,16 @@ class EndpointDetailsPage extends React.Component {
     EndpointActions.getEndpoints().promise.then(() => {
       let endpoint = this.getEndpoint()
 
-      if (endpoint.connection_info && endpoint.connection_info.secret_ref) {
+      if (endpoint && endpoint.connection_info && endpoint.connection_info.secret_ref) {
         EndpointActions.getConnectionInfo(endpoint)
-      } else {
+      } else if (endpoint && endpoint.connection_info) {
         EndpointActions.getConnectionInfoSuccess(endpoint.connection_info)
       }
     })
   }
 
   render() {
+    let endpoint = this.getEndpoint()
     return (
       <Wrapper>
         <DetailsTemplate
@@ -190,14 +197,15 @@ class EndpointDetailsPage extends React.Component {
             onUserItemClick={item => { this.handleUserItemClick(item) }}
           />}
           contentHeaderComponent={<DetailsContentHeader
-            item={this.getEndpoint()}
+            item={(endpoint: any)}
             onBackButonClick={() => { this.handleBackButtonClick() }}
+            onCancelClick={() => { }}
             typeImage={endpointImage}
-            description={this.getEndpoint().description}
+            description={endpoint ? endpoint.description : ''}
             noSidemenuSpace
           />}
           contentComponent={<EndpointDetailsContent
-            item={this.getEndpoint()}
+            item={endpoint}
             loading={this.props.endpointStore.connectionInfoLoading || this.props.endpointStore.loading}
             connectionInfo={this.props.endpointStore.connectionInfo}
             onDeleteClick={() => { this.handleDeleteEndpointClick() }}

+ 40 - 27
src/components/pages/EndpointsPage/EndpointsPage.jsx → src/components/pages/EndpointsPage/index.jsx

@@ -12,22 +12,22 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 import connectToStores from 'alt-utils/lib/connectToStores'
 
-import {
-  MainTemplate,
-  Navigation,
-  FilterList,
-  PageHeader,
-  EndpointListItem,
-  AlertModal,
-  Modal,
-  ChooseProvider,
-  Endpoint,
-} from 'components'
+import MainTemplate from '../../templates/MainTemplate'
+import Navigation from '../../organisms/Navigation'
+import FilterList from '../../organisms/FilterList'
+import PageHeader from '../../organisms/PageHeader'
+import EndpointListItem from '../../molecules/EndpointListItem'
+import AlertModal from '../../organisms/AlertModal'
+import Modal from '../../molecules/Modal'
+import ChooseProvider from '../../organisms/ChooseProvider'
+import Endpoint from '../../organisms/Endpoint'
+import type { Endpoint as EndpointType } from '../../../types/Endpoint'
 
 import endpointImage from './images/endpoint-large.svg'
 
@@ -52,16 +52,22 @@ const BulkActions = [
   { label: 'Delete', value: 'delete' },
 ]
 
-class EndpointsPage extends React.Component {
-  static propTypes = {
-    projectStore: PropTypes.object,
-    userStore: PropTypes.object,
-    endpointStore: PropTypes.object,
-    migrationStore: PropTypes.object,
-    replicaStore: PropTypes.object,
-    providerStore: PropTypes.object,
-  }
-
+type Props = {
+  projectStore: any,
+  userStore: any,
+  endpointStore: any,
+  migrationStore: any,
+  replicaStore: any,
+  providerStore: any,
+}
+type State = {
+  showDeleteEndpointsConfirmation: boolean,
+  confirmationItems: ?EndpointType[],
+  showChooseProviderModal: boolean,
+  showEndpointModal: boolean,
+  providerType: ?string,
+}
+class EndpointsPage extends React.Component<Props, State> {
   static getStores() {
     return [UserStore, ProjectStore, EndpointStore, MigrationStore, ReplicaStore, ProviderStore]
   }
@@ -77,6 +83,8 @@ class EndpointsPage extends React.Component {
     }
   }
 
+  pollInterval: IntervalID
+
   constructor() {
     super()
 
@@ -113,7 +121,7 @@ class EndpointsPage extends React.Component {
     return types
   }
 
-  getEndpointUsage(endpoint) {
+  getEndpointUsage(endpoint: Endpoint) {
     let replicasCount = this.props.replicaStore.replicas.filter(
       r => r.origin_endpoint_id === endpoint.id || r.destination_endpoint_id === endpoint.id).length
     let migrationsCount = this.props.migrationStore.migrations.filter(
@@ -148,6 +156,7 @@ class EndpointsPage extends React.Component {
     if (action === 'delete') {
       this.setState({
         showDeleteEndpointsConfirmation: true,
+        // $FlowIssue
         confirmationItems: items,
       })
     }
@@ -161,9 +170,11 @@ class EndpointsPage extends React.Component {
   }
 
   handleDeleteEndpointsConfirmation() {
-    this.state.confirmationItems.forEach(endpoint => {
-      EndpointActions.delete(endpoint)
-    })
+    if (this.state.confirmationItems) {
+      this.state.confirmationItems.forEach(endpoint => {
+        EndpointActions.delete(endpoint)
+      })
+    }
     this.handleCloseDeleteEndpointsConfirmation()
   }
 
@@ -190,7 +201,8 @@ class EndpointsPage extends React.Component {
 
   itemFilterFunction(item, filterItem, filterText) {
     if ((filterItem !== 'all' && (item.type !== filterItem)) ||
-      (item.name.toLowerCase().indexOf(filterText) === -1 &&
+      (item.name.toLowerCase().indexOf(filterText || '') === -1 &&
+      // $FlowIssue
       item.description.toLowerCase().indexOf(filterText) === -1)
     ) {
       return false
@@ -216,6 +228,7 @@ class EndpointsPage extends React.Component {
               onActionChange={(items, action) => { this.handleActionChange(items, action) }}
               itemFilterFunction={(...args) => this.itemFilterFunction(...args)}
               renderItemComponent={options =>
+                // $FlowIssue
                 (<EndpointListItem
                   {...options}
                   getUsage={endpoint => this.getEndpointUsage(endpoint)}

+ 15 - 9
src/components/pages/LoginPage/LoginPage.jsx → src/components/pages/LoginPage/index.jsx

@@ -12,12 +12,16 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 import connectToStores from 'alt-utils/lib/connectToStores'
 
-import { EmptyTemplate, Logo, LoginForm } from 'components'
+import EmptyTemplate from '../../templates/EmptyTemplate'
+import Logo from '../../atoms/Logo'
+import LoginForm from '../../organisms/LoginForm'
+
 import StyleProps from '../../styleUtils/StyleProps'
 import UserActions from '../../../actions/UserActions'
 import UserStore from '../../../stores/UserStore'
@@ -72,13 +76,15 @@ const CbsLogo = styled.a`
   cursor: pointer;
 `
 
-class LoginPage extends React.Component {
-  static propTypes = {
-    loading: PropTypes.bool,
-    loginFailedResponse: PropTypes.object,
-    user: PropTypes.object,
-  }
-
+type Props = {
+  loading: boolean,
+  loginFailedResponse: { status: string },
+  user: { username: string, email: string },
+}
+type State = {
+  loading: boolean,
+}
+class LoginPage extends React.Component<Props, State> {
   static getStores() {
     return [UserStore]
   }

+ 20 - 16
src/components/pages/MigrationDetailsPage/MigrationDetailsPage.jsx → src/components/pages/MigrationDetailsPage/index.jsx

@@ -12,18 +12,17 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
-import PropTypes from 'prop-types'
 import connectToStores from 'alt-utils/lib/connectToStores'
 
-import {
-  DetailsTemplate,
-  DetailsPageHeader,
-  DetailsContentHeader,
-  MigrationDetailsContent,
-  AlertModal,
-} from 'components'
+import DetailsTemplate from '../../templates/DetailsTemplate'
+import { DetailsPageHeader } from '../../organisms/DetailsPageHeader'
+import DetailsContentHeader from '../../organisms/DetailsContentHeader'
+import MigrationDetailsContent from '../../organisms/MigrationDetailsContent'
+import AlertModal from '../../organisms/AlertModal'
 
 import MigrationStore from '../../../stores/MigrationStore'
 import UserStore from '../../../stores/UserStore'
@@ -38,14 +37,17 @@ import migrationImage from './images/migration.svg'
 
 const Wrapper = styled.div``
 
-class MigrationDetailsPage extends React.Component {
-  static propTypes = {
-    match: PropTypes.object,
-    migrationStore: PropTypes.object,
-    endpointStore: PropTypes.object,
-    userStore: PropTypes.object,
-  }
-
+type Props = {
+  match: any,
+  migrationStore: any,
+  endpointStore: any,
+  userStore: any,
+}
+type State = {
+  showDeleteMigrationConfirmation: boolean,
+  showCancelConfirmation: boolean,
+}
+class MigrationDetailsPage extends React.Component<Props, State> {
   static getStores() {
     return [MigrationStore, EndpointStore, UserStore]
   }
@@ -58,6 +60,8 @@ class MigrationDetailsPage extends React.Component {
     }
   }
 
+  pollInterval: IntervalID
+
   constructor() {
     super()
 

+ 29 - 11
src/components/pages/MigrationsPage/MigrationsPage.jsx → src/components/pages/MigrationsPage/index.jsx

@@ -12,12 +12,19 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
-import PropTypes from 'prop-types'
 import styled from 'styled-components'
 import connectToStores from 'alt-utils/lib/connectToStores'
 
-import { MainTemplate, Navigation, FilterList, PageHeader, AlertModal, MainListItem } from 'components'
+import MainTemplate from '../../templates/MainTemplate'
+import Navigation from '../../organisms/Navigation'
+import FilterList from '../../organisms/FilterList'
+import PageHeader from '../../organisms/PageHeader'
+import AlertModal from '../../organisms/AlertModal'
+import MainListItem from '../../molecules/MainListItem'
+import type { MainItem } from '../../../types/MainItem'
 
 import migrationItemImage from './images/migration.svg'
 import migrationLargeImage from './images/migration-large.svg'
@@ -40,14 +47,18 @@ const BulkActions = [
   { label: 'Delete', value: 'delete' },
 ]
 
-class MigrationsPage extends React.Component {
-  static propTypes = {
-    projectStore: PropTypes.object,
-    migrationStore: PropTypes.object,
-    userStore: PropTypes.object,
-    endpointStore: PropTypes.object,
-  }
-
+type Props = {
+  projectStore: any,
+  migrationStore: any,
+  userStore: any,
+  endpointStore: any,
+}
+type State = {
+  showDeleteMigrationConfirmation: boolean,
+  showCancelMigrationConfirmation: boolean,
+  confirmationItems: ?MainItem[],
+}
+class MigrationsPage extends React.Component<Props, State> {
   static getStores() {
     return [UserStore, ProjectStore, MigrationStore, EndpointStore]
   }
@@ -135,6 +146,9 @@ class MigrationsPage extends React.Component {
   }
 
   handleCancelMigrationConfirmation() {
+    if (!this.state.confirmationItems) {
+      return
+    }
     this.state.confirmationItems.forEach(migration => {
       MigrationActions.cancel(migration.id)
     })
@@ -157,6 +171,9 @@ class MigrationsPage extends React.Component {
   }
 
   handleDeleteMigrationConfirmation() {
+    if (!this.state.confirmationItems) {
+      return
+    }
     this.state.confirmationItems.forEach(migration => {
       MigrationActions.delete(migration.id)
     })
@@ -169,12 +186,13 @@ class MigrationsPage extends React.Component {
 
   searchText(item, text) {
     let result = false
-    if (item.instances[0].toLowerCase().indexOf(text) > -1) {
+    if (item.instances[0].toLowerCase().indexOf(text || '') > -1) {
       return true
     }
     if (item.destination_environment) {
       Object.keys(item.destination_environment).forEach(prop => {
         if (item.destination_environment[prop] && item.destination_environment[prop].toLowerCase
+          // $FlowIssue
           && item.destination_environment[prop].toLowerCase().indexOf(text) > -1) {
           result = true
         }

+ 3 - 1
src/components/pages/NotFoundPage/NotFoundPage.jsx → src/components/pages/NotFoundPage/index.jsx

@@ -12,10 +12,12 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import styled from 'styled-components'
 
-import { EmptyTemplate } from 'components'
+import EmptyTemplate from '../../templates/EmptyTemplate'
 
 const Wrapper = styled.div``
 

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů