diff --git a/package.json b/package.json
index ea6c337..3d9296b 100644
--- a/package.json
+++ b/package.json
@@ -8,22 +8,24 @@
"url": "git@github.com:Indoqa/indoqa-react-redux.git"
},
"scripts": {
- "start": "indoqa-dev-server ./indoqa-webpack-options.js",
+ "analyze": "source-map-explorer ./target/assets/app-*",
"build": "indoqa-webpack ./indoqa-webpack-options.js",
"docs": "indoqa-webpack ./indoqa-webpack-docs.js",
"flow": "flow",
"lint": "indoqa-eslint ./src/main",
"package": "yarn run lint && yarn run flow && yarn run test && indoqa-webpack ./indoqa-webpack-options.js",
+ "postinstall": "yarn run package",
+ "start": "indoqa-dev-server ./indoqa-webpack-options.js",
"test": "indoqa-jest",
"test:watch": "yarn run test --watch",
- "test:coverage": "yarn run test --coverage",
- "postinstall": "yarn run package"
+ "test:coverage": "yarn run test --coverage"
},
"dependencies": {
"fela": "6.1.7",
"fela-monolithic": "5.0.21",
"fela-plugin-named-media-query": "5.0.13",
"fela-preset-web": "8.0.5",
+ "formik": "0.11.11",
"indoqa-react-app": "2.6.0",
"indoqa-react-fela": "0.6.0",
"ramda": "0.25.0",
@@ -37,11 +39,14 @@
"redux-logger": "3.0.6",
"redux-observable": "0.18.0",
"reselect": "3.0.1",
- "rxjs": "5.5.10"
+ "rxjs": "5.5.10",
+ "shortid": "2.2.8",
+ "yup": "0.24.1"
},
"devDependencies": {
"flow-bin": "0.70.0",
- "indoqa-webpack": "1.2.0"
+ "indoqa-webpack": "1.2.0",
+ "source-map-explorer": "1.5.0"
},
"proxy": {
"/geonames": {
diff --git a/src/main/app/rootEpic.js b/src/main/app/rootEpic.js
index 1e00360..6a5ee1c 100644
--- a/src/main/app/rootEpic.js
+++ b/src/main/app/rootEpic.js
@@ -1,10 +1,13 @@
// @flow
import {ajax} from 'rxjs/observable/dom/ajax'
import {combineEpics} from 'redux-observable'
+
+import formsEpics from '../forms/store/forms.epics'
import timeEpics from '../time/store/time.epics.js'
import wordsEpics from '../words/store/words.epics'
const combinedEpics = combineEpics(
+ ...formsEpics,
...timeEpics,
...wordsEpics
)
diff --git a/src/main/app/rootReducer.js b/src/main/app/rootReducer.js
index ad2cef6..fbf2778 100644
--- a/src/main/app/rootReducer.js
+++ b/src/main/app/rootReducer.js
@@ -1,11 +1,13 @@
// @flow
import {combineReducersWithRouter} from 'indoqa-react-app'
-import time from '../time/store/time.reducer.js'
-import todos from '../todos/store/todos.reducer.js'
+import forms from '../forms/store/forms.reducer'
+import time from '../time/store/time.reducer'
+import todos from '../todos/store/todos.reducer'
import words from '../words/store/words.reducer'
const reducers = {
+ forms,
time,
todos,
words,
diff --git a/src/main/app/routes.react.js b/src/main/app/routes.react.js
index 2eb7fcf..b579d89 100644
--- a/src/main/app/routes.react.js
+++ b/src/main/app/routes.react.js
@@ -3,15 +3,20 @@ import React from 'react'
import {IndexRoute, Route} from 'react-router'
import App from './App.react.js'
+import FormsPage from '../forms/components/FormsPage.redux.js'
import TimePage from '../time/components/TimePage.react.js'
import TodosPage from '../todos/components/TodosPage.react.js'
-import WordsPage from '../words/components/WordsPage.react'
+import UserPage from '../forms/components/UserPage.redux.js'
+import WordsPage from '../words/components/WordsPage.react.js'
const routes = (
+
+
+
-
+
)
diff --git a/src/main/app/selectors.js b/src/main/app/selectors.js
index 622db73..5320145 100644
--- a/src/main/app/selectors.js
+++ b/src/main/app/selectors.js
@@ -1,14 +1,17 @@
// @flow
+import type {FormsState} from '../forms/types/FormsState'
import type {TimeState} from '../time/types/TimeState'
import type {TodoState} from '../todos/types/TodoState'
import type {WordsState} from '../words/types/WordsState'
type State = {
+ forms: FormsState,
time: TimeState,
todos: TodoState,
words: WordsState,
}
+export const selectFormsState = (state: State) => state.forms
export const selectTimeState = (state: State) => state.time
export const selectTodoState = (state: State) => state.todos
export const selectWordsState = (state: State) => state.words
diff --git a/src/main/app/theme.js b/src/main/app/theme.js
index ea9ae54..cca0b67 100644
--- a/src/main/app/theme.js
+++ b/src/main/app/theme.js
@@ -3,7 +3,7 @@ export default {
colors: {
text: '#030303',
disabled: '#727272',
- bgLight: '#e5e5e5',
+ bgLight: '#d5d5d5',
},
fonts: {
text: 'sans-serif',
diff --git a/src/main/commons/components/atoms/ButtonLink.react.js b/src/main/commons/components/atoms/ButtonLink.react.js
new file mode 100644
index 0000000..0d70921
--- /dev/null
+++ b/src/main/commons/components/atoms/ButtonLink.react.js
@@ -0,0 +1,13 @@
+// @flow
+import {createComponentWithProxy} from 'react-fela'
+
+const ButtonLink = ({theme}) => ({
+ '& > a': {
+ color: theme.colors.text,
+ display: 'block',
+ height: '100%',
+ textDecoration: 'none',
+ },
+})
+
+export default createComponentWithProxy(ButtonLink, 'button')
diff --git a/src/main/commons/components/molecules/Logo.react.js b/src/main/commons/components/molecules/Logo.react.js
index 3615189..9776bfd 100644
--- a/src/main/commons/components/molecules/Logo.react.js
+++ b/src/main/commons/components/molecules/Logo.react.js
@@ -1,14 +1,19 @@
// @flow
import {createComponentWithProxy} from 'react-fela'
+import {Box} from 'indoqa-react-fela'
const Logo = ({theme}) => {
return ({
- height: '50px',
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
- padding: theme.spacing.space1,
+ height: 50,
+ fontWeight: 'bold',
+ '> a': {
+ textDecoration: 'none',
+ color: theme.colors.text,
+ },
})
}
-export default createComponentWithProxy(Logo, 'div')
+export default createComponentWithProxy(Logo, Box)
diff --git a/src/main/commons/components/organisms/MainMenu.react.js b/src/main/commons/components/organisms/MainMenu.react.js
index 513afd0..3cf9c63 100644
--- a/src/main/commons/components/organisms/MainMenu.react.js
+++ b/src/main/commons/components/organisms/MainMenu.react.js
@@ -9,10 +9,10 @@ import MenuLink from '../molecules/MenuLink.react.js'
const MainMenu = () => (
)
diff --git a/src/main/commons/components/templates/MainMenuTemplate.react.js b/src/main/commons/components/templates/MainMenuTemplate.react.js
index 6ebd13e..9920b69 100644
--- a/src/main/commons/components/templates/MainMenuTemplate.react.js
+++ b/src/main/commons/components/templates/MainMenuTemplate.react.js
@@ -16,7 +16,7 @@ const MainMenuTemplate = ({title, header, children}: Props) => (
-
+
{title}
{header}
diff --git a/src/main/forms/components/AddressesForm.react.js b/src/main/forms/components/AddressesForm.react.js
new file mode 100644
index 0000000..ef27a69
--- /dev/null
+++ b/src/main/forms/components/AddressesForm.react.js
@@ -0,0 +1,83 @@
+// @flow
+import * as React from 'react'
+import {FieldArray} from 'formik'
+import {Box, Flex, Text} from 'indoqa-react-fela'
+
+import {createNewAddress} from '../store/forms.factory'
+import FormRow from './FormRow.react'
+
+type Props = {
+ values: any,
+ errors: any,
+ touched: any,
+}
+
+const renderAddressHeader = (arrayHelpers, count, index) => {
+ return (
+
+ Address {index + 1}
+
+ {index < count - 1 ?
+
+ : null
+ }
+ {index > 0 ?
+
+ : null
+ }
+
+ )
+}
+
+const renderAddressForm = (arrayHelpers, values, errors, touched, address, index) => {
+ return (
+
+
+ {renderAddressHeader(arrayHelpers, values.length, index)}
+
+
+
+
+
+
+
+ )
+}
+
+const renderAddressesHeader = (arrayHelpers) => {
+ return (
+
+
+ Addresses
+
+
+
+ )
+}
+
+const renderForms = (arrayHelpers, values, errors, touched) => {
+ const {addresses} = values
+
+ if (!(addresses && addresses.length > 0)) {
+ return null
+ }
+ return addresses.map((address, index) => (
+ renderAddressForm(arrayHelpers, values, errors, touched, address, index)
+ ))
+}
+
+const AddressesForm = ({values, errors, touched}:Props) => {
+ return (
+ (
+
+ {renderAddressesHeader(arrayHelpers)}
+ {renderForms(arrayHelpers, values, errors, touched)}
+
+ )}
+ />
+ )
+}
+
+export default AddressesForm
diff --git a/src/main/forms/components/FormRow.react.js b/src/main/forms/components/FormRow.react.js
new file mode 100644
index 0000000..465f5a2
--- /dev/null
+++ b/src/main/forms/components/FormRow.react.js
@@ -0,0 +1,81 @@
+// @flow
+import * as React from 'react'
+import {createComponent, createComponentWithProxy} from 'react-fela'
+import {Field, getIn} from 'formik'
+import {Text} from 'indoqa-react-fela'
+
+type Props = {
+ name: string,
+ label: string,
+ errors: any,
+ touched: any,
+}
+
+const RowContainer = createComponent(() => ({
+ marginTop: 3,
+ marginBottom: 3,
+}))
+
+const Label = createComponent(() => ({
+ display: 'inline-block',
+ width: '100',
+}), 'label')
+
+const InputField = createComponentWithProxy(({hasError}) => ({
+ borderStyle: 'solid',
+ borderWidth: 1,
+ padding: 4,
+ borderColor: hasError ? 'red' : 'grey',
+ outline: 'none',
+ boxShadow: 'none',
+ transition: 'all 0.30s ease-in-out',
+ ':focus': {
+ boxShadow: '0 0 5px rgba(81, 203, 238, 1)',
+ borderWidth: 1,
+ borderStyle: 'solid',
+ borderColor: 'rgba(81, 203, 238, 1)',
+ },
+}), 'input')
+
+const ErrorMessage = createComponent(() => ({
+ color: 'red',
+}), Text)
+
+const renderLabel = (label) => {
+ return
+}
+
+const renderField = (name, hasError) => {
+ return (
+ (
+
+ )}
+ />
+ )
+}
+
+const renderError = (name, hasError, errors) => {
+ if (!hasError) {
+ return null
+ }
+ return (
+
+ {getIn(errors, name)}
+
+ )
+}
+
+const FormRow = ({name, label, errors, touched}:Props) => {
+ const hasError = getIn(touched, name) && getIn(errors, name)
+ return (
+
+ {renderLabel(label)}
+ {renderField(name, hasError)}
+ {renderError(name, hasError, errors)}
+
+ )
+}
+
+export default FormRow
diff --git a/src/main/forms/components/FormsPage.react.js b/src/main/forms/components/FormsPage.react.js
new file mode 100644
index 0000000..782965c
--- /dev/null
+++ b/src/main/forms/components/FormsPage.react.js
@@ -0,0 +1,57 @@
+// @flow
+import React from 'react'
+import {Box} from 'indoqa-react-fela'
+import {Link} from 'react-router'
+import {createComponent} from 'react-fela'
+
+import MainMenuTemplate from '../../commons/components/templates/MainMenuTemplate.react'
+import ButtonLink from '../../commons/components/atoms/ButtonLink.react'
+
+import type {User} from '../types/User'
+
+type Props = {
+ users: { [string]:User },
+}
+
+const TableData = createComponent(({theme}) => ({
+ padding: theme.spacing.space1,
+}), 'td')
+
+const renderUserRow = (user: User) => {
+ return (
+
+ {user.name}
+ {user.email}
+
+
+ Edit
+
+
+
+ )
+}
+
+class FormsPage extends React.Component {
+
+ render() {
+ const {users} = this.props
+ return (
+
+
+
+
+ {Object.keys(users).map((k) => renderUserRow(users[k]))}
+
+
+
+
+ Add user
+
+
+
+
+ )
+ }
+}
+
+export default FormsPage
diff --git a/src/main/forms/components/FormsPage.redux.js b/src/main/forms/components/FormsPage.redux.js
new file mode 100644
index 0000000..9ba9e71
--- /dev/null
+++ b/src/main/forms/components/FormsPage.redux.js
@@ -0,0 +1,12 @@
+// @flow
+import {connect} from 'react-redux'
+import {selectUsers} from '../store/forms.selectors'
+
+import type {FormsState} from '../types/FormsState'
+import FormsPage from './FormsPage.react'
+
+const mapStateToProps = (state: FormsState) => ({
+ users: selectUsers(state),
+})
+
+export default connect(mapStateToProps)(FormsPage)
diff --git a/src/main/forms/components/UserForm.react.js b/src/main/forms/components/UserForm.react.js
new file mode 100644
index 0000000..f299326
--- /dev/null
+++ b/src/main/forms/components/UserForm.react.js
@@ -0,0 +1,58 @@
+// @flow
+import * as React from 'react'
+import {Form, Formik} from 'formik'
+import {Box} from 'indoqa-react-fela'
+import yup from 'yup'
+import {Link} from 'react-router'
+
+import FormRow from './FormRow.react'
+import AddressesForm from './AddressesForm.react'
+import ButtonLink from '../../commons/components/atoms/ButtonLink.react'
+
+import type {User} from '../types/User'
+
+type Props = {
+ user: User,
+ onSubmit: Function,
+}
+
+const validationSchema = () => {
+ return yup.object().shape({
+ name: yup.string().required(),
+ email: yup.string().required('E-mail is required'),
+ addresses: yup.array().of(yup.object().shape({
+ country: yup.string().required('Country is required'),
+ zipCode: yup.string().required('ZIP code is required'),
+ })),
+ })
+}
+
+const UserForm = ({user, onSubmit}:Props) => {
+ return (
+ onSubmit(values, setErrors)}
+ initialValues={user}
+ validateOnChange={false}
+ validationSchema={validationSchema}
+ render={({values, errors, touched}) => {
+ return (
+
+ )
+ }}
+ />
+ )
+}
+
+export default UserForm
diff --git a/src/main/forms/components/UserPage.react.js b/src/main/forms/components/UserPage.react.js
new file mode 100644
index 0000000..1776dbe
--- /dev/null
+++ b/src/main/forms/components/UserPage.react.js
@@ -0,0 +1,56 @@
+// @flow
+import React from 'react'
+import {Box} from 'indoqa-react-fela'
+import {withRouter, type Router} from 'react-router'
+
+import MainMenuTemplate from '../../commons/components/templates/MainMenuTemplate.react'
+import UserForm from './UserForm.react'
+
+import type {User} from '../types/User'
+
+type Props = {
+ router: Router,
+ currentUser: User,
+ loadUser: Function,
+ postUser: Function,
+}
+
+class UserPage extends React.Component {
+
+ constructor(props: Props) {
+ super(props)
+ this.postUser = this.postUser.bind(this)
+ }
+
+ componentWillMount() {
+ const {router, loadUser} = this.props
+ const {id} = router.params
+ loadUser(id)
+ }
+
+ postUser: (User) => void
+
+ postUser(user: User, setErrors) {
+ const {postUser} = this.props
+ postUser(user, setErrors)
+ }
+
+ render() {
+ const {currentUser} = this.props
+
+ if (currentUser === null) {
+ return null
+ }
+
+ const operation = currentUser.id === '' ? 'Add' : 'Edit'
+ return (
+
+
+
+
+
+ )
+ }
+}
+
+export default withRouter(UserPage)
diff --git a/src/main/forms/components/UserPage.redux.js b/src/main/forms/components/UserPage.redux.js
new file mode 100644
index 0000000..3c1e8f7
--- /dev/null
+++ b/src/main/forms/components/UserPage.redux.js
@@ -0,0 +1,14 @@
+// @flow
+import {connect} from 'react-redux'
+import {selectCurrentUser} from '../store/forms.selectors'
+
+import {loadUser, postUser} from '../store/forms.actions'
+import UserPage from './UserPage.react'
+
+import type {FormsState} from '../types/FormsState'
+
+const mapStateToProps = (state: FormsState) => ({
+ currentUser: selectCurrentUser(state),
+})
+
+export default connect(mapStateToProps, {loadUser, postUser})(UserPage)
diff --git a/src/main/forms/store/forms.actions.js b/src/main/forms/store/forms.actions.js
new file mode 100644
index 0000000..740e56a
--- /dev/null
+++ b/src/main/forms/store/forms.actions.js
@@ -0,0 +1,26 @@
+// @flow
+import type {Action} from '../types/FormsActions'
+
+import type {User} from '../types/User'
+
+export const loadUser = (id: string): Action => ({
+ type: 'FORMS_LOAD_USER',
+ id,
+})
+
+export const saveUser = (user: User): Action => ({
+ type: 'FORMS_SAVE_USER',
+ user,
+})
+
+export const postUser = (user: User, setErrors: Function): Action => ({
+ type: 'FORMS_POST_USER',
+ user,
+ setErrors,
+})
+
+
+export const setCurrentUser = (currentUser: User): Action => ({
+ type: 'FORMS_SET_CURRENT_USER',
+ currentUser,
+})
diff --git a/src/main/forms/store/forms.epics.js b/src/main/forms/store/forms.epics.js
new file mode 100644
index 0000000..5a1da9e
--- /dev/null
+++ b/src/main/forms/store/forms.epics.js
@@ -0,0 +1,52 @@
+import {Observable} from 'rxjs/Observable'
+import {push} from 'react-router-redux'
+import shortid from 'shortid'
+
+import {selectUsers} from './forms.selectors'
+import {setCurrentUser, saveUser} from './forms.actions'
+import {createNewUser} from './forms.factory'
+
+const selectUser = (id, state) => {
+ if (id === undefined) {
+ return createNewUser()
+ }
+
+ const user = selectUsers(state)[id]
+ if (user === undefined) {
+ throw Error(`User with id '${id}' does not exist.`)
+ }
+ return user
+}
+
+const postUser = (user) => {
+ if (user.id === '') {
+ user.id = shortid.generate()
+ }
+ return saveUser(user)
+}
+
+const loadCurrentUserEpic$ = (action$, store) =>
+ action$
+ .ofType('FORMS_LOAD_USER')
+ .map((action) => setCurrentUser(selectUser(action.id, store.getState())))
+
+const postUserEpic$ = (action$) =>
+ action$
+ .ofType('FORMS_POST_USER')
+ .mergeMap((action) => {
+ // Formik does not allow setting initial errors, hence this work-around is necessary to pass the setErrors method of the form
+ // see https://github.com/jaredpalmer/formik/issues/288
+ const {user, setErrors} = action
+ if (user.name.startsWith('G')) {
+ setErrors({name: 'Names starting with \'G\' are not allowed.'})
+ // do not emit an action observable in the case of an error
+ return Observable.of().ignoreElements()
+ }
+
+ return Observable.merge(
+ Observable.of(postUser(action.user)),
+ Observable.of(push('/forms'))
+ )
+ })
+
+export default [loadCurrentUserEpic$, postUserEpic$]
diff --git a/src/main/forms/store/forms.factory.js b/src/main/forms/store/forms.factory.js
new file mode 100644
index 0000000..0b253e1
--- /dev/null
+++ b/src/main/forms/store/forms.factory.js
@@ -0,0 +1,25 @@
+// @flow
+import type {User} from '../types/User'
+import type {Address} from '../types/Address'
+
+const createNewUser = ():User => {
+ return {
+ id: '',
+ name: '',
+ email: '',
+ addresses: [],
+ lastModified: new Date(),
+ }
+}
+
+const createNewAddress = ():Address => ({
+ street: '',
+ city: '',
+ zipCode: '',
+ country: '',
+})
+
+export {
+ createNewUser,
+ createNewAddress,
+}
diff --git a/src/main/forms/store/forms.reducer.js b/src/main/forms/store/forms.reducer.js
new file mode 100644
index 0000000..a698c68
--- /dev/null
+++ b/src/main/forms/store/forms.reducer.js
@@ -0,0 +1,75 @@
+// @flow
+import type {FormsState} from '../types/FormsState'
+import type {User} from '../types/User'
+import type {Action} from '../types/FormsActions'
+
+const user1: User = {
+ id: 'HyJifGwFG',
+ name: 'Maier',
+ email: 'w.maier@example.com',
+ addresses: [
+ {
+ street: 'Schottenring 3',
+ city: 'Vienna',
+ zipCode: '1010',
+ country: 'Austria',
+ },
+ {
+ street: 'Heinrichstrasse 7',
+ city: 'Graz',
+ zipCode: '8010',
+ country: 'Austria',
+ }
+ ],
+ lastModified: new Date(),
+}
+
+const user2: User = {
+ id: 'r1rozfvFf',
+ name: 'Gruber',
+ email: 'f.gruber@example.com',
+ addresses: [],
+ lastModified: new Date(),
+}
+
+const initialState: FormsState = {
+ users: {
+ [user1.id]: user1,
+ [user2.id]: user2,
+ },
+ currentUser: null,
+}
+
+export default (state: FormsState = initialState, action: Action) => {
+ switch (action.type) {
+ case 'FORMS_SAVE_USER':
+ return {
+ ...state,
+ users: {
+ ...state.users,
+ [action.user.id]: {
+ ...action.user,
+ addresses: [
+ ...action.user.addresses,
+ ],
+ lastModified: new Date(),
+ },
+ },
+ }
+
+ case 'FORMS_LOAD_USER':
+ return {
+ ...state,
+ currentUser: null,
+ }
+
+ case 'FORMS_SET_CURRENT_USER':
+ return {
+ ...state,
+ currentUser: action.currentUser,
+ }
+
+ default:
+ return state
+ }
+}
diff --git a/src/main/forms/store/forms.selectors.js b/src/main/forms/store/forms.selectors.js
new file mode 100644
index 0000000..17b75e9
--- /dev/null
+++ b/src/main/forms/store/forms.selectors.js
@@ -0,0 +1,8 @@
+// @flow
+import {createSelector} from 'reselect'
+
+import type {FormsState} from '../types/FormsState'
+import {selectFormsState} from '../../app/selectors.js'
+
+export const selectUsers = createSelector(selectFormsState, (state: FormsState) => state.users)
+export const selectCurrentUser = createSelector(selectFormsState, (state: FormsState) => state.currentUser)
diff --git a/src/main/forms/types/Address.js b/src/main/forms/types/Address.js
new file mode 100644
index 0000000..24c3d8b
--- /dev/null
+++ b/src/main/forms/types/Address.js
@@ -0,0 +1,7 @@
+// @flow
+export type Address = {
+ street: string,
+ city: string,
+ zipCode: string,
+ country: string,
+}
diff --git a/src/main/forms/types/FormsActions.js b/src/main/forms/types/FormsActions.js
new file mode 100644
index 0000000..03334db
--- /dev/null
+++ b/src/main/forms/types/FormsActions.js
@@ -0,0 +1,28 @@
+// @flow
+import type {User} from './User'
+
+type SaveUserAction = {
+ type: 'FORMS_SAVE_USER',
+ user: User,
+}
+
+type PostUserAction = {
+ type: 'FORMS_POST_USER',
+ user: User,
+}
+
+type LoadUserAction = {
+ type: 'FORMS_LOAD_USER',
+ id: string,
+}
+
+type SetCurrentUserAction = {
+ type: 'FORMS_SET_CURRENT_USER',
+ currentUser: User,
+}
+
+export type Action =
+ | SaveUserAction
+ | PostUserAction
+ | LoadUserAction
+ | SetCurrentUserAction
diff --git a/src/main/forms/types/FormsState.js b/src/main/forms/types/FormsState.js
new file mode 100644
index 0000000..8526086
--- /dev/null
+++ b/src/main/forms/types/FormsState.js
@@ -0,0 +1,7 @@
+// @flow
+import type {User} from './User'
+
+export type FormsState = {
+ +users: { [string]: User },
+ +currentUser: ?User,
+}
diff --git a/src/main/forms/types/User.js b/src/main/forms/types/User.js
new file mode 100644
index 0000000..5ffd46b
--- /dev/null
+++ b/src/main/forms/types/User.js
@@ -0,0 +1,10 @@
+// @flow
+import type {Address} from './Address'
+
+export type User = {
+ id: string,
+ name: string,
+ email: string,
+ addresses: Array,
+ lastModified: Date,
+}
diff --git a/src/main/time/components/TimePage.react.js b/src/main/time/components/TimePage.react.js
index 501a057..1e07774 100644
--- a/src/main/time/components/TimePage.react.js
+++ b/src/main/time/components/TimePage.react.js
@@ -13,7 +13,7 @@ class TimePage extends React.Component {
render() {
return (
-
+
diff --git a/src/main/todos/components/AddTodo.react.js b/src/main/todos/components/AddTodo.react.js
index 9c19b37..3ef3ab1 100644
--- a/src/main/todos/components/AddTodo.react.js
+++ b/src/main/todos/components/AddTodo.react.js
@@ -1,5 +1,6 @@
// @flow
import React from 'react'
+import {Box} from 'indoqa-react-fela'
type Props = {
addTodo: Function,
@@ -9,7 +10,7 @@ const AddTodo = ({addTodo}: Props) => {
let input
return (
-
+
-
+
)
}
diff --git a/src/main/todos/components/Footer.react.js b/src/main/todos/components/Header.react.js
similarity index 88%
rename from src/main/todos/components/Footer.react.js
rename to src/main/todos/components/Header.react.js
index e6abe59..a6e6ba3 100644
--- a/src/main/todos/components/Footer.react.js
+++ b/src/main/todos/components/Header.react.js
@@ -2,7 +2,7 @@
import React from 'react'
import FilterLink from './FilterLink.redux'
-const Footer = () => (
+const Header = () => (
Show:
{' '}
@@ -20,4 +20,4 @@ const Footer = () => (
)
-export default Footer
+export default Header
diff --git a/src/main/todos/components/TodosPage.react.js b/src/main/todos/components/TodosPage.react.js
index 5bba35d..c56cd8d 100644
--- a/src/main/todos/components/TodosPage.react.js
+++ b/src/main/todos/components/TodosPage.react.js
@@ -4,7 +4,7 @@ import {Box} from 'indoqa-react-fela'
import MainMenuTemplate from '../../commons/components/templates/MainMenuTemplate.react'
import AddTodo from '../components/AddTodo.redux'
-import Footer from '../components/Footer.react'
+import Header from './Header.react'
import TodoList from '../components/TodoList.redux'
class TodosPage extends React.Component<{}> {
@@ -12,10 +12,10 @@ class TodosPage extends React.Component<{}> {
render() {
return (
-
+
+
-
)
diff --git a/src/main/words/components/WordsPage.react.js b/src/main/words/components/WordsPage.react.js
index 4cd8949..1cc5bf0 100644
--- a/src/main/words/components/WordsPage.react.js
+++ b/src/main/words/components/WordsPage.react.js
@@ -11,7 +11,7 @@ class WordsPage extends Component<{}> {
render() {
return (
-
+
diff --git a/src/main/words/components/WordsSearch.react.js b/src/main/words/components/WordsSearch.react.js
index 3012221..48698ca 100644
--- a/src/main/words/components/WordsSearch.react.js
+++ b/src/main/words/components/WordsSearch.react.js
@@ -1,6 +1,7 @@
// @flow
import * as React from 'react'
import {Box, Text} from 'indoqa-react-fela'
+import {createComponentWithProxy} from 'react-fela'
type Props = {
fetchWords: Function,
@@ -10,11 +11,18 @@ type Props = {
isLoadingFlag: boolean,
}
+const InputField = createComponentWithProxy(({theme}) => ({
+ width: 300,
+ padding: 4,
+ marginRight: theme.spacing.space2,
+}), 'input')
+
+
const WordsSearch = ({fetchWords, cancelFetchWords, prefix, isLoadingFlag, error}: Props) => {
return (
- fetchWords(e.currentTarget.value)}
diff --git a/yarn.lock b/yarn.lock
index 87b46af..60079e9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1451,6 +1451,10 @@ bser@^2.0.0:
dependencies:
node-int64 "^0.4.0"
+btoa@^1.1.2:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73"
+
buffer-from@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531"
@@ -1576,6 +1580,10 @@ caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805:
version "1.0.30000830"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000830.tgz#cb96b8a2dd3cbfe04acea2af3c4e894249095328"
+case@^1.2.1:
+ version "1.5.4"
+ resolved "https://registry.yarnpkg.com/case/-/case-1.5.4.tgz#b201642aae9e374feb5750d1181a76850153830c"
+
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@@ -1845,7 +1853,7 @@ content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
-convert-source-map@^1.4.0, convert-source-map@^1.5.0:
+convert-source-map@^1.1.1, convert-source-map@^1.4.0, convert-source-map@^1.5.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
@@ -2293,6 +2301,10 @@ dns-txt@^2.0.2:
dependencies:
buffer-indexof "^1.0.0"
+docopt@^0.6.2:
+ version "0.6.2"
+ resolved "https://registry.yarnpkg.com/docopt/-/docopt-0.6.2.tgz#b28e9e2220da5ec49f7ea5bb24a47787405eeb11"
+
doctrine@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
@@ -3190,6 +3202,10 @@ flush-write-stream@^1.0.0:
inherits "^2.0.1"
readable-stream "^2.0.4"
+fn-name@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-1.0.1.tgz#de8d8a15388b33cbf2145782171f73770c6030f0"
+
for-in@^1.0.1, for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -3224,6 +3240,16 @@ form-data@~2.3.1:
combined-stream "1.0.6"
mime-types "^2.1.12"
+formik@0.11.11:
+ version "0.11.11"
+ resolved "https://registry.yarnpkg.com/formik/-/formik-0.11.11.tgz#4b02838133c0196b1ef443aa973766cd097ec4a5"
+ dependencies:
+ lodash.clonedeep "^4.5.0"
+ lodash.isequal "4.5.0"
+ lodash.topath "4.5.2"
+ prop-types "^15.5.10"
+ warning "^3.0.0"
+
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@@ -4767,15 +4793,23 @@ lodash.clonedeep@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
+lodash.isequal@4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
+
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+lodash.topath@4.5.2:
+ version "4.5.2"
+ resolved "https://registry.yarnpkg.com/lodash.topath/-/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009"
+
lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
-lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1:
+lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1:
version "4.17.5"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
@@ -5301,6 +5335,10 @@ onetime@^2.0.0:
dependencies:
mimic-fn "^1.0.0"
+open@0.0.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/open/-/open-0.0.5.tgz#42c3e18ec95466b6bf0dc42f3a2945c3f0cad8fc"
+
opn@5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-5.1.0.tgz#72ce2306a17dbea58ff1041853352b4a8fc77519"
@@ -5516,8 +5554,8 @@ path-type@^2.0.0:
pify "^2.0.0"
pbkdf2@^3.0.3:
- version "3.0.14"
- resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade"
+ version "3.0.16"
+ resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c"
dependencies:
create-hash "^1.1.2"
create-hmac "^1.1.4"
@@ -5927,6 +5965,10 @@ prop-types@15.6.1, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6,
loose-envify "^1.3.1"
object-assign "^4.1.1"
+property-expr@^1.2.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.4.0.tgz#e28cfe4e7a5a231fb14c8ad687a93a5342e05a8c"
+
proxy-addr@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
@@ -6562,6 +6604,10 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.
dependencies:
glob "^7.0.5"
+rimraf@~2.2.6:
+ version "2.2.8"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582"
+
ripemd160@^2.0.0, ripemd160@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
@@ -6775,6 +6821,10 @@ shellwords@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
+shortid@2.2.8:
+ version "2.2.8"
+ resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.8.tgz#033b117d6a2e975804f6f0969dbe7d3d0b355131"
+
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
@@ -6856,6 +6906,19 @@ source-list-map@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
+source-map-explorer@1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/source-map-explorer/-/source-map-explorer-1.5.0.tgz#654e2ba0db158fecfc99b9cefdf891b755b767d1"
+ dependencies:
+ btoa "^1.1.2"
+ convert-source-map "^1.1.1"
+ docopt "^0.6.2"
+ glob "^7.1.2"
+ open "0.0.5"
+ source-map "^0.5.1"
+ temp "^0.8.3"
+ underscore "^1.8.3"
+
source-map-resolve@^0.5.0:
version "0.5.1"
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.1.tgz#7ad0f593f2281598e854df80f19aae4b92d7a11a"
@@ -6886,7 +6949,7 @@ source-map@0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
-source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1:
+source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
@@ -7180,6 +7243,10 @@ symbol-tree@^3.2.1:
version "3.2.2"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
+synchronous-promise@^1.0.18:
+ version "1.0.18"
+ resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-1.0.18.tgz#936e8763e6554088cdcf78dc64f7473b972fcefc"
+
table@4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36"
@@ -7216,6 +7283,13 @@ tar@^2.2.1:
fstream "^1.0.2"
inherits "2"
+temp@^0.8.3:
+ version "0.8.3"
+ resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59"
+ dependencies:
+ os-tmpdir "^1.0.0"
+ rimraf "~2.2.6"
+
test-exclude@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.1.tgz#dfa222f03480bca69207ca728b37d74b45f724fa"
@@ -7303,6 +7377,10 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2"
safe-regex "^1.1.0"
+toposort@^0.2.10:
+ version "0.2.12"
+ resolved "https://registry.yarnpkg.com/toposort/-/toposort-0.2.12.tgz#c7d2984f3d48c217315cc32d770888b779491e81"
+
toposort@^1.0.0:
version "1.0.6"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.6.tgz#c31748e55d210effc00fdcdc7d6e68d7d7bb9cec"
@@ -7352,6 +7430,10 @@ type-is@~1.6.15, type-is@~1.6.16:
media-typer "0.3.0"
mime-types "~2.1.18"
+type-name@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/type-name/-/type-name-2.0.2.tgz#efe7d4123d8ac52afff7f40c7e4dec5266008fb4"
+
typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
@@ -7412,6 +7494,10 @@ uid-number@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
+underscore@^1.8.3:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.0.tgz#31dbb314cfcc88f169cd3692d9149d81a00a73e4"
+
union-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
@@ -7884,3 +7970,15 @@ yargs@~3.10.0:
cliui "^2.1.0"
decamelize "^1.0.0"
window-size "0.1.0"
+
+yup@0.24.1:
+ version "0.24.1"
+ resolved "https://registry.yarnpkg.com/yup/-/yup-0.24.1.tgz#2c8a81b5f929ef29aaf77a8b7c9acfa52ab6a7d1"
+ dependencies:
+ case "^1.2.1"
+ fn-name "~1.0.1"
+ lodash "^4.17.0"
+ property-expr "^1.2.0"
+ synchronous-promise "^1.0.18"
+ toposort "^0.2.10"
+ type-name "^2.0.1"