diff --git a/front/package.json b/front/package.json index 8358fdf3..91488025 100644 --- a/front/package.json +++ b/front/package.json @@ -10,15 +10,21 @@ "dependencies": { "@zeit/next-typescript": "^1.1.1", "args-parser": "^1.1.0", + "axios": "^0.18.0", "cookie-parser": "^1.4.3", "express": "^4.16.4", + "final-form": "^4.11.1", + "http-status-codes": "^1.3.0", + "js-cookie": "^2.2.0", "lodash": "^4.17.11", "next": "^7.0.2", "next-compose-plugins": "^2.1.1", "next-css-unpluggable": "^2.1.0", + "next-redirect": "^1.0.1", "next-routes": "^1.4.2", "react": "^16.8.1", "react-dom": "^16.8.1", + "react-final-form": "^4.0.2", "react-redux": "^6.0.0", "redux": "^4.0.1", "redux-clear": "^1.0.3", @@ -27,6 +33,7 @@ "redux-thunk": "^2.3.0" }, "devDependencies": { + "@types/js-cookie": "^2.2.0", "@types/lodash": "^4.14.120", "@types/next": "^7.0.7", "@types/react": "^16.8.2", diff --git a/front/pages/_app.tsx b/front/pages/_app.tsx index e2bb1d5e..f69c861b 100644 --- a/front/pages/_app.tsx +++ b/front/pages/_app.tsx @@ -1,3 +1,4 @@ +import redirect from 'next-redirect' import App, { Container, NextAppContext } from 'next/app' import Head from 'next/head' import React from 'react' @@ -9,20 +10,33 @@ import { AppContext } from '@front/domain/AppContext' import { WithReduxProps } from '@front/domain/store/WithReduxProps' import { withReduxStore } from '@front/domain/store/withReduxStore' import { actions } from '@front/domain/user/reducer/data' +import { getToken } from '@front/domain/user/selectors/getToken' +import { pushRoute } from '@front/pushRoute' class CheckmoneyWeb extends App { - public static getInitialProps(appContext: NextAppContext) { + public static async getInitialProps(appContext: NextAppContext) { const ctx: AppContext = appContext.ctx as any + // TODO: set cookie! const token = Option.of(ctx) - .map(context => context.req) - .map(request => request.cookies) - .map(cookies => cookies.token) + .flatMap(context => Option.of(context.req)) + .flatMap(request => Option.of(request.cookies)) + .flatMap(cookies => Option.of(cookies.token)) if (token.nonEmpty()) { ctx.reduxStore.dispatch(actions.setToken(token.get())) } + const isSecure = !!(appContext.Component as any).isSecure + const loggedIn = getToken(ctx.reduxStore.getState()).nonEmpty() + if (isSecure && !loggedIn) { + if (!!ctx.res) { + await redirect(ctx, '/forbidden') + } else { + await pushRoute('/forbidden') + } + } + return App.getInitialProps(appContext) } @@ -44,4 +58,4 @@ class CheckmoneyWeb extends App { } } -export default withReduxStore(CheckmoneyWeb) +export default withReduxStore(CheckmoneyWeb as any) diff --git a/front/pages/forbidden.tsx b/front/pages/forbidden.tsx new file mode 100644 index 00000000..04bd6d2e --- /dev/null +++ b/front/pages/forbidden.tsx @@ -0,0 +1,17 @@ +import { FORBIDDEN } from 'http-status-codes' +import * as React from 'react' + +import { AppContext } from '@front/domain/AppContext' + +export default class ForbiddenPage extends React.Component { + public static getInitialProps({ res }: AppContext) { + // prettier-ignore + ;(res as any).statusCode = FORBIDDEN + + return {} + } + + public render() { + return

Forbidden

+ } +} diff --git a/front/pages/index.css b/front/pages/index.css deleted file mode 100644 index 76f3805e..00000000 --- a/front/pages/index.css +++ /dev/null @@ -1,5 +0,0 @@ -.title { - display: flex; - font-size: 21px; - color: var(--color-primary); -} diff --git a/front/pages/index.tsx b/front/pages/index.tsx index c142ccb6..0c3976b4 100644 --- a/front/pages/index.tsx +++ b/front/pages/index.tsx @@ -1,11 +1,9 @@ import * as React from 'react' -import log from '@front/log' -import con from '@shared/module' +import { Landing } from '@front/features/landing' -import s from './index.css' - -export default () => { - con() - return

{log()}

+export default class IndexPage extends React.Component { + public render() { + return + } } diff --git a/front/pages/internal/app.tsx b/front/pages/internal/app.tsx new file mode 100644 index 00000000..36cb4be0 --- /dev/null +++ b/front/pages/internal/app.tsx @@ -0,0 +1,9 @@ +import * as React from 'react' + +export default class AppPage extends React.Component { + public static isSecure = true + + public render() { + return

app

+ } +} diff --git a/front/pages/internal/hello.tsx b/front/pages/internal/hello.tsx new file mode 100644 index 00000000..62b80104 --- /dev/null +++ b/front/pages/internal/hello.tsx @@ -0,0 +1,9 @@ +import * as React from 'react' + +export default class HelloPage extends React.Component { + public static isSecure = true + + public render() { + return

hello

+ } +} diff --git a/front/routes.js b/front/routes.js index 60086461..1fac0754 100644 --- a/front/routes.js +++ b/front/routes.js @@ -1,3 +1,7 @@ const nextRoutes = require('next-routes') -module.exports = nextRoutes().add('index') +module.exports = nextRoutes() + .add({ pattern: '/', page: 'index' }) + .add({ pattern: '/forbidden', page: 'forbidden' }) + .add({ pattern: '/hello', page: 'internal/hello' }) + .add({ pattern: '/app', page: 'internal/app' }) diff --git a/front/src/domain/api/Api.ts b/front/src/domain/api/Api.ts new file mode 100644 index 00000000..8855d181 --- /dev/null +++ b/front/src/domain/api/Api.ts @@ -0,0 +1,21 @@ +import axios, { AxiosInstance } from 'axios' +import { Option } from 'tsoption' + +export class Api { + public get client() { + return this.axios + } + + private readonly axios: AxiosInstance + + public constructor(token: Option) { + const authHeaders = token.nonEmpty() + ? { Authorization: `Bearer ${token.get()}` } + : {} + + this.axios = axios.create({ + baseURL: 'http://localhost:3000/', + headers: authHeaders, + }) + } +} diff --git a/front/src/domain/api/createApi.ts b/front/src/domain/api/createApi.ts new file mode 100644 index 00000000..070f23d0 --- /dev/null +++ b/front/src/domain/api/createApi.ts @@ -0,0 +1,5 @@ +import { Option } from 'tsoption' + +import { Api } from './Api' + +export const createApi = (token: Option) => new Api(token) diff --git a/front/src/domain/store/fetchingRedux/fetchOrFail.ts b/front/src/domain/store/fetchingRedux/fetchOrFail.ts index 23d2104a..2c3345d8 100644 --- a/front/src/domain/store/fetchingRedux/fetchOrFail.ts +++ b/front/src/domain/store/fetchingRedux/fetchOrFail.ts @@ -1,4 +1,8 @@ import { AnyAction, Dispatch } from 'redux' +import { Option } from 'tsoption' + +import { Api } from '@front/domain/api/Api' +import { getToken } from '@front/domain/user/selectors/getToken' import { State } from '../State' @@ -10,19 +14,24 @@ interface FetchActions { type Execute = ( dispatch: Dispatch, + getApi: () => Api, getState: () => State, ) => Promise export const fetchOrFail = ( fetchActions: FetchActions, execute: Execute, -) => async (dispatch: Dispatch, getState: () => State) => { +) => async ( + dispatch: Dispatch, + getState: () => State, + createApi: (token: Option) => Api, +) => { const { request, success, failure } = fetchActions try { dispatch(request()) - await execute(dispatch, getState) + await execute(dispatch, () => createApi(getToken(getState())), getState) dispatch(success()) } catch (e) { diff --git a/front/src/domain/store/initializeStore.ts b/front/src/domain/store/initializeStore.ts index ba035cd1..198b00bd 100644 --- a/front/src/domain/store/initializeStore.ts +++ b/front/src/domain/store/initializeStore.ts @@ -2,6 +2,8 @@ import { applyMiddleware, createStore } from 'redux' import { composeWithDevTools } from 'redux-devtools-extension' import thunk from 'redux-thunk' +import { createApi } from '@front/domain/api/createApi' + import { reducer } from './reducer' import { State } from './State' @@ -9,5 +11,5 @@ export const initializeStore = (initialState?: State) => createStore( reducer, initialState, - composeWithDevTools(applyMiddleware(thunk)), + composeWithDevTools(applyMiddleware(thunk.withExtraArgument(createApi))), ) diff --git a/front/src/domain/store/withReduxStore.tsx b/front/src/domain/store/withReduxStore.tsx index 8218a4f0..7cdde47a 100644 --- a/front/src/domain/store/withReduxStore.tsx +++ b/front/src/domain/store/withReduxStore.tsx @@ -1,5 +1,10 @@ -import App, { AppProps, DefaultAppIProps, NextAppContext } from 'next/app' -import React, { ComponentType } from 'react' +import { + AppComponentType, + AppProps, + DefaultAppIProps, + NextAppContext, +} from 'next/app' +import React from 'react' import { AppContext } from '../AppContext' import { getOrCreateStore } from './getOrCreateStore' @@ -9,7 +14,7 @@ import { WithReduxProps } from './WithReduxProps' type NextProps = AppProps & DefaultAppIProps export const withReduxStore = ( - Application: ComponentType, + Application: AppComponentType, ) => { return class AppWithRedux extends React.Component { public static async getInitialProps(appContext: NextAppContext) { @@ -19,8 +24,8 @@ export const withReduxStore = ( context.reduxStore = reduxStore let appProps = {} - if (typeof App.getInitialProps === 'function') { - appProps = await App.getInitialProps(appContext) + if (typeof Application.getInitialProps === 'function') { + appProps = await Application.getInitialProps(appContext) } return { diff --git a/front/src/domain/user/actions/signIn.ts b/front/src/domain/user/actions/signIn.ts index bc050383..92b73b06 100644 --- a/front/src/domain/user/actions/signIn.ts +++ b/front/src/domain/user/actions/signIn.ts @@ -1,13 +1,17 @@ import { fetchOrFail } from '@front/domain/store/fetchingRedux/fetchOrFail' +import { signIn as signInRequest } from '../api/signIn' import { actions as dataActions } from '../reducer/data' import { actions as signInActions } from '../reducer/signIn' +import { setCookie } from '../setCookie' const { setToken } = dataActions export const signIn = (login: string, password: string) => - fetchOrFail(signInActions, async dispatch => { - const { token } = { token: `${login}+${password}` } + fetchOrFail(signInActions, async (dispatch, getApi) => { + const { token } = await signInRequest(getApi())(login, password) + + setCookie(token) dispatch(setToken(token)) }) diff --git a/front/src/domain/user/actions/signUp.ts b/front/src/domain/user/actions/signUp.ts new file mode 100644 index 00000000..f3de77c9 --- /dev/null +++ b/front/src/domain/user/actions/signUp.ts @@ -0,0 +1,17 @@ +import { fetchOrFail } from '@front/domain/store/fetchingRedux/fetchOrFail' + +import { signUp as signUpRequest } from '../api/signUp' +import { actions as dataActions } from '../reducer/data' +import { actions as signUpActions } from '../reducer/signUp' +import { setCookie } from '../setCookie' + +const { setToken } = dataActions + +export const signUp = (login: string, password: string) => + fetchOrFail(signUpActions, async (dispatch, getApi) => { + const { token } = await signUpRequest(getApi())(login, password) + + setCookie(token) + + dispatch(setToken(token)) + }) diff --git a/front/src/domain/user/api/signIn.ts b/front/src/domain/user/api/signIn.ts new file mode 100644 index 00000000..88a7eb0e --- /dev/null +++ b/front/src/domain/user/api/signIn.ts @@ -0,0 +1,14 @@ +import { TokenModel } from '@shared/models/user/TokenModel' + +import { Api } from '@front/domain/api/Api' + +export const signIn = (api: Api) => ( + email: string, + password: string, +): Promise => + api.client + .post('user/auth/sign-in', { + email, + password, + }) + .then(response => response.data) diff --git a/front/src/domain/user/api/signUp.ts b/front/src/domain/user/api/signUp.ts new file mode 100644 index 00000000..46a265a7 --- /dev/null +++ b/front/src/domain/user/api/signUp.ts @@ -0,0 +1,14 @@ +import { TokenModel } from '@shared/models/user/TokenModel' + +import { Api } from '@front/domain/api/Api' + +export const signUp = (api: Api) => ( + email: string, + password: string, +): Promise => + api.client + .post('user/auth/sign-up', { + email, + password, + }) + .then(response => response.data) diff --git a/front/src/domain/user/hooks/useSignIn.ts b/front/src/domain/user/hooks/useSignIn.ts new file mode 100644 index 00000000..1ea5cde9 --- /dev/null +++ b/front/src/domain/user/hooks/useSignIn.ts @@ -0,0 +1,14 @@ +import { useCallback } from 'react' +import { useDispatch } from 'redux-react-hook' + +import { signIn } from '@front/domain/user/actions/signIn' + +export const useSignIn = () => { + const dispatch = useDispatch() + + return useCallback( + (login: string, password: string): Promise => + dispatch(signIn(login, password) as any), + [], + ) +} diff --git a/front/src/domain/user/hooks/useSignUp.ts b/front/src/domain/user/hooks/useSignUp.ts new file mode 100644 index 00000000..78118933 --- /dev/null +++ b/front/src/domain/user/hooks/useSignUp.ts @@ -0,0 +1,14 @@ +import { useCallback } from 'react' +import { useDispatch } from 'redux-react-hook' + +import { signUp } from '@front/domain/user/actions/signUp' + +export const useSignUp = () => { + const dispatch = useDispatch() + + return useCallback( + (login: string, password: string): Promise => + dispatch(signUp(login, password) as any), + [], + ) +} diff --git a/front/src/domain/user/reducer/index.ts b/front/src/domain/user/reducer/index.ts index eb29d80b..acd2e28b 100644 --- a/front/src/domain/user/reducer/index.ts +++ b/front/src/domain/user/reducer/index.ts @@ -2,14 +2,17 @@ import { combineReducers } from 'redux' import { reducer as dataReducer, State as DataState } from './data' import { reducer as signInReducer, State as SignInState } from './signIn' +import { reducer as signUpReducer, State as SignUpState } from './signUp' interface State { signIn: SignInState + signUp: SignUpState data: DataState } const reducer = combineReducers({ signIn: signInReducer, + signUp: signUpReducer, data: dataReducer, }) diff --git a/front/src/domain/user/reducer/signUp.ts b/front/src/domain/user/reducer/signUp.ts new file mode 100644 index 00000000..02e744af --- /dev/null +++ b/front/src/domain/user/reducer/signUp.ts @@ -0,0 +1,8 @@ +import { createFetchingStore } from '@front/domain/store/fetchingRedux/createFetchingRedux' +import { FetchingState } from '@front/domain/store/fetchingRedux/FetchingState' + +type State = FetchingState + +const { reducer, actions } = createFetchingStore('user/sign-up') + +export { reducer, actions, State } diff --git a/front/src/domain/user/selectors/getToken.ts b/front/src/domain/user/selectors/getToken.ts new file mode 100644 index 00000000..af6db8e6 --- /dev/null +++ b/front/src/domain/user/selectors/getToken.ts @@ -0,0 +1,3 @@ +import { State } from '@front/domain/store/State' + +export const getToken = (state: State) => state.user.data.token diff --git a/front/src/domain/user/setCookie.ts b/front/src/domain/user/setCookie.ts new file mode 100644 index 00000000..7a874d1a --- /dev/null +++ b/front/src/domain/user/setCookie.ts @@ -0,0 +1,3 @@ +import Cookies from 'js-cookie' + +export const setCookie = (token: string) => Cookies.set('token', token) diff --git a/front/src/features/landing/Landing.tsx b/front/src/features/landing/Landing.tsx new file mode 100644 index 00000000..595ef993 --- /dev/null +++ b/front/src/features/landing/Landing.tsx @@ -0,0 +1,9 @@ +import { SignIn } from './features/sign-in' +import { SignUp } from './features/sign-up' + +export const Landing = () => ( + <> + + + +) diff --git a/front/src/features/landing/features/sign-in/SignIn.tsx b/front/src/features/landing/features/sign-in/SignIn.tsx new file mode 100644 index 00000000..8c293193 --- /dev/null +++ b/front/src/features/landing/features/sign-in/SignIn.tsx @@ -0,0 +1,41 @@ +import { useCallback } from 'react' +import { Field, Form } from 'react-final-form' + +import { useSignIn } from '@front/domain/user/hooks/useSignIn' +import { pushRoute } from '@front/pushRoute' + +export const SignIn = () => { + const signIn = useSignIn() + + const onSubmit = useCallback(async ({ email, password }) => { + await signIn(email, password) + await pushRoute('/app') + }, []) + + return ( +
+ {({ handleSubmit }) => ( + +

Sign-in

+ +
+ + +
+ +
+ + +
+ + +
+ )} + + ) +} diff --git a/front/src/features/landing/features/sign-in/index.ts b/front/src/features/landing/features/sign-in/index.ts new file mode 100644 index 00000000..a34f58cb --- /dev/null +++ b/front/src/features/landing/features/sign-in/index.ts @@ -0,0 +1 @@ +export { SignIn } from './SignIn' diff --git a/front/src/features/landing/features/sign-up/SignUp.tsx b/front/src/features/landing/features/sign-up/SignUp.tsx new file mode 100644 index 00000000..12971e95 --- /dev/null +++ b/front/src/features/landing/features/sign-up/SignUp.tsx @@ -0,0 +1,46 @@ +import { useCallback } from 'react' +import { Field, Form } from 'react-final-form' + +import { useSignUp } from '@front/domain/user/hooks/useSignUp' +import { pushRoute } from '@front/pushRoute' + +export const SignUp = () => { + const signUp = useSignUp() + + const onSubmit = useCallback(async ({ email, password }) => { + await signUp(email, password) + await pushRoute('/hello') + }, []) + + return ( +
+ {({ handleSubmit }) => ( + +

Sign-up

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ )} + + ) +} diff --git a/front/src/features/landing/features/sign-up/index.ts b/front/src/features/landing/features/sign-up/index.ts new file mode 100644 index 00000000..a9e29fc1 --- /dev/null +++ b/front/src/features/landing/features/sign-up/index.ts @@ -0,0 +1 @@ +export { SignUp } from './SignUp' diff --git a/front/src/features/landing/index.ts b/front/src/features/landing/index.ts new file mode 100644 index 00000000..13474e36 --- /dev/null +++ b/front/src/features/landing/index.ts @@ -0,0 +1 @@ +export { Landing } from './Landing' diff --git a/front/src/log.ts b/front/src/log.ts deleted file mode 100644 index 44b32a76..00000000 --- a/front/src/log.ts +++ /dev/null @@ -1 +0,0 @@ -export default () => 'Hello!' diff --git a/front/src/pushRoute.ts b/front/src/pushRoute.ts new file mode 100644 index 00000000..1c78060b --- /dev/null +++ b/front/src/pushRoute.ts @@ -0,0 +1,4 @@ +import NextRoutes from '../routes' + +export const pushRoute = async (route: string): Promise => + NextRoutes.Router.pushRoute(route) diff --git a/front/types.d.ts b/front/types.d.ts index b27d05c4..87f31d5e 100644 --- a/front/types.d.ts +++ b/front/types.d.ts @@ -2,3 +2,9 @@ declare module '*.css' { const classes: { [key: string]: string } export default classes } + +declare module 'next-redirect' { + type Redirect = (ctx: any, route: string) => Promise + const redirect: redirect + export default redirect +} diff --git a/yarn.lock b/yarn.lock index def8ff1b..c22de9d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -709,7 +709,7 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@^7.2.0": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1": version "7.3.1" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.1.tgz#574b03e8e8a9898eaf4a872a92ea20b7846f6f2a" integrity sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA== @@ -1132,6 +1132,11 @@ "@types/express-serve-static-core" "*" "@types/serve-static" "*" +"@types/js-cookie@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.0.tgz#ae2eabaa4bc892ba553c664cca21e6ee5224e5b5" + integrity sha512-k0umv2jvUvl1/OdOgFwEIPF6qKllf2jEsrd5ka9aVQo2ilxaBb2vsh1AmAAZ3eqt54vNrdbGW5QPOKJBmCNS7g== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -1790,7 +1795,7 @@ autoprefixer@^9.0.0, autoprefixer@^9.4.2: postcss "^7.0.13" postcss-value-parser "^3.3.1" -axios@0.18.0: +axios@0.18.0, axios@^0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102" integrity sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI= @@ -3995,6 +4000,13 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" +final-form@^4.11.1: + version "4.11.1" + resolved "https://registry.yarnpkg.com/final-form/-/final-form-4.11.1.tgz#cf84a995a75140727c3676647d7cdb858869cb22" + integrity sha512-GuRAcbUE+vs/IFcvsRxMAaz0MQp5QaJKXU8FnNf4qlsVDuKoAyvDJGvvDsSTSgQDixavoxPmqwzJOVXrf0Hw1w== + dependencies: + "@babel/runtime" "^7.3.1" + finalhandler@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" @@ -4633,6 +4645,11 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" +http-status-codes@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.3.0.tgz#9cd0e71391773d0671b489d41cbc5094aa4163b6" + integrity sha1-nNDnE5F3PQZxtInUHLxQlKpBY7Y= + http-status@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/http-status/-/http-status-1.0.1.tgz#dc43001a8bfc50ac87d485a892f7578964bc94a2" @@ -5241,6 +5258,11 @@ jest-validate@^23.5.0: leven "^2.1.0" pretty-format "^23.6.0" +js-cookie@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.0.tgz#1b2c279a6eece380a12168b92485265b35b1effb" + integrity sha1-Gywnmm7s44ChIWi5JIUmWzWx7/s= + js-git@^0.7.8: version "0.7.8" resolved "https://registry.yarnpkg.com/js-git/-/js-git-0.7.8.tgz#52fa655ab61877d6f1079efc6534b554f31e5444" @@ -6366,6 +6388,11 @@ next-css-unpluggable@^2.1.0: ignore-loader "0.1.2" postcss-loader "3.0.0" +next-redirect@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/next-redirect/-/next-redirect-1.0.1.tgz#eb84ba12f5e1d7ee37fed7088d56ea747b4cedf2" + integrity sha1-64S6EvXh1+43/tcIjVbqdHtM7fI= + next-routes@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/next-routes/-/next-routes-1.4.2.tgz#736a382579a792ea69f35ae70b449acdfefa7944" @@ -8025,6 +8052,13 @@ react-error-overlay@4.0.0: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.0.tgz#d198408a85b4070937a98667f500c832f86bd5d4" integrity sha512-FlsPxavEyMuR6TjVbSSywovXSEyOg6ZDj5+Z8nbsRl9EkOzAhEIcS+GLoQDC5fz/t9suhUXWmUrOBrgeUvrMxw== +react-final-form@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/react-final-form/-/react-final-form-4.0.2.tgz#fa829d7fb60dcc168804bf2edda58f3d62c4607b" + integrity sha512-EdFWrT8nMWu5sHViuZ/VmlaYT+mLu/q5TMDWZZj5gnssUAWfgcila0QpptFNDg6+qNtepGgFh5ZPASlivoEXUA== + dependencies: + "@babel/runtime" "^7.1.2" + react-is@^16.6.3, react-is@^16.7.0: version "16.7.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.7.0.tgz#c1bd21c64f1f1364c6f70695ec02d69392f41bfa"