From 2a473e3378a6920d7df2b0b7d61b378db46cddc9 Mon Sep 17 00:00:00 2001 From: meriadec Date: Mon, 15 Jan 2018 12:05:51 +0100 Subject: [PATCH 1/2] Create the Modal component and associated reducer and make use of it in the SideBar component, to open the send and receive modals. --- package.json | 1 + src/components/ReceiveModal.js | 13 +++++ src/components/SendModal.js | 13 +++++ src/components/SideBar/Item.js | 35 ++++++++++-- src/components/SideBar/index.js | 4 +- src/components/Wrapper.js | 4 ++ src/components/base/Modal.js | 98 +++++++++++++++++++++++++++++++++ src/reducers/index.js | 2 + src/reducers/modals.js | 50 +++++++++++++++++ yarn.lock | 33 ++++++++++- 10 files changed, 244 insertions(+), 9 deletions(-) create mode 100644 src/components/ReceiveModal.js create mode 100644 src/components/SendModal.js create mode 100644 src/components/base/Modal.js create mode 100644 src/reducers/modals.js diff --git a/package.json b/package.json index 462998e5..e420c04c 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "react": "^16.2.0", "react-dom": "^16.2.0", "react-i18next": "^7.3.1", + "react-mortal": "^3.0.1", "react-redux": "^5.0.6", "react-router": "^4.2.0", "react-router-dom": "^4.2.2", diff --git a/src/components/ReceiveModal.js b/src/components/ReceiveModal.js new file mode 100644 index 00000000..65541bf9 --- /dev/null +++ b/src/components/ReceiveModal.js @@ -0,0 +1,13 @@ +// @flow + +import React, { PureComponent } from 'react' + +import Modal from 'components/base/Modal' + +class ReceiveModal extends PureComponent { + render() { + return receive modal + } +} + +export default ReceiveModal diff --git a/src/components/SendModal.js b/src/components/SendModal.js new file mode 100644 index 00000000..5bad380d --- /dev/null +++ b/src/components/SendModal.js @@ -0,0 +1,13 @@ +// @flow + +import React, { PureComponent } from 'react' + +import Modal from 'components/base/Modal' + +class SendModal extends PureComponent { + render() { + return send modal + } +} + +export default SendModal diff --git a/src/components/SideBar/Item.js b/src/components/SideBar/Item.js index 42bd8ad7..364da66b 100644 --- a/src/components/SideBar/Item.js +++ b/src/components/SideBar/Item.js @@ -7,6 +7,8 @@ import { withRouter } from 'react-router' import { push } from 'react-router-redux' import { connect } from 'react-redux' +import { openModal, isModalOpened } from 'reducers/modals' + import type { Element } from 'react' import type { Location } from 'react-router' @@ -16,14 +18,25 @@ import Text from 'components/base/Text' type Props = { children: string, linkTo?: string | null, + modal?: string | null, desc?: string | null, icon?: Element<*> | null, location: Location, + isModalOpened: boolean, push: Function, + openModal: Function, } +const mapStateToProps = (state, { modal }) => ({ + // connect router here only to make components re-render + // see https://github.com/ReactTraining/react-router/issues/4671 + router: state.router, + isModalOpened: modal ? isModalOpened(state, modal) : false, +}) + const mapDispatchToProps = { push, + openModal, } const Container = styled(Box).attrs({ @@ -46,10 +59,23 @@ const IconWrapper = styled(Box)` border: 2px solid rgba(255, 255, 255, 0.1); ` -function Item({ children, desc, icon, linkTo, push, location }: Props) { +function Item({ + children, + desc, + icon, + linkTo, + push, + location, + modal, + openModal, + isModalOpened, +}: Props) { const { pathname } = location return ( - push(linkTo) : void 0} active={pathname === linkTo}> + push(linkTo) : modal ? () => openModal(modal) : void 0} + active={pathname === linkTo || isModalOpened} + > {icon || null}
@@ -69,13 +95,12 @@ Item.defaultProps = { icon: null, desc: null, linkTo: null, + modal: null, } export default compose( withRouter, - // connect router here only to make components re-render - // see https://github.com/ReactTraining/react-router/issues/4671 - connect(({ router }) => ({ router }), mapDispatchToProps, null, { + connect(mapStateToProps, mapDispatchToProps, null, { pure: false, }), )(Item) diff --git a/src/components/SideBar/index.js b/src/components/SideBar/index.js index c9f66310..e06d9083 100644 --- a/src/components/SideBar/index.js +++ b/src/components/SideBar/index.js @@ -24,8 +24,8 @@ class SideBar extends PureComponent<{}> { {'Menu'}
{'Dashboard'} - {'Send'} - {'Receive'} + {'Send'} + {'Receive'} {'Settings'}
diff --git a/src/components/Wrapper.js b/src/components/Wrapper.js index 774d936f..533fd292 100644 --- a/src/components/Wrapper.js +++ b/src/components/Wrapper.js @@ -9,6 +9,8 @@ import Box from 'components/base/Box' import DashboardPage from 'components/DashboardPage' import SettingsPage from 'components/SettingsPage' import AccountPage from 'components/AccountPage' +import SendModal from 'components/SendModal' +import ReceiveModal from 'components/ReceiveModal' import SideBar from 'components/SideBar' import TopBar from 'components/TopBar' @@ -22,6 +24,8 @@ const Wrapper = () => ( + + ) diff --git a/src/components/base/Modal.js b/src/components/base/Modal.js new file mode 100644 index 00000000..b71fc377 --- /dev/null +++ b/src/components/base/Modal.js @@ -0,0 +1,98 @@ +// @flow +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ + +import React, { PureComponent } from 'react' +import { connect } from 'react-redux' +import Mortal from 'react-mortal' +import styled from 'styled-components' + +import { closeModal, isModalOpened } from 'reducers/modals' + +type Props = { + isOpened: boolean, + onClose: Function, + children: any, +} + +const springConfig = { + stiffness: 350, +} + +const mapStateToProps = (state, { name, isOpened }) => ({ + isOpened: isOpened || (name && isModalOpened(state, name)), +}) + +const mapDispatchToProps = (dispatch, { name }) => ({ + onClose: name ? () => dispatch(closeModal(name)) : undefined, +}) + +const Container = styled.div.attrs({ + style: p => ({ + pointerEvents: p.isVisible ? 'auto' : 'none', + }), +})` + position: fixed; + z-index: 1; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + display: flex; + align-items: flex-start; + justify-content: center; +` + +const Backdrop = styled.div.attrs({ + style: p => ({ + opacity: p.op, + }), +})` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.4); +` + +const Body = styled.div.attrs({ + style: p => ({ + opacity: p.op, + transform: `translate3d(0, ${p.offset}px, 0)`, + }), +})` + padding: 20px; + margin-top: 100px; + background: white; + width: 400px; + z-index: 2; +` + +class Modal extends PureComponent { + render() { + const { isOpened, onClose, children } = this.props + return ( + ({ + opacity: spring(isVisible ? 1 : 0, springConfig), + y: spring(isVisible ? 0 : 20, springConfig), + })} + > + {(m, isVisible) => ( + + + + {children} + + + )} + + ) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(Modal) diff --git a/src/reducers/index.js b/src/reducers/index.js index c3063211..a7586fe4 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -4,8 +4,10 @@ import { combineReducers } from 'redux' import { routerReducer as router } from 'react-router-redux' import devices from './devices' +import modals from './modals' export default combineReducers({ router, devices, + modals, }) diff --git a/src/reducers/modals.js b/src/reducers/modals.js new file mode 100644 index 00000000..bb55336a --- /dev/null +++ b/src/reducers/modals.js @@ -0,0 +1,50 @@ +// @flow + +import { handleActions, createAction } from 'redux-actions' + +const state = {} + +type OpenPayload = { + name: string, + data?: Object | null, +} + +type ClosePayload = { + name: string, +} + +const handlers = { + MODAL_OPEN: (state, { payload }: { payload: OpenPayload }) => { + const { name, data } = payload + return { + ...state, + [name]: { + isOpened: true, + data, + }, + } + }, + MODAL_CLOSE: (state, { payload }: { payload: ClosePayload }) => { + const { name } = payload + return { + ...state, + [name]: { + isOpened: false, + data: null, + }, + } + }, +} + +// Actions + +export const openModal = createAction('MODAL_OPEN', (name, data = {}) => ({ name, data })) +export const closeModal = createAction('MODAL_CLOSE', name => ({ name })) + +// Selectors + +export const isModalOpened = (state, name) => state.modals[name] && state.modals[name].isOpened + +// Exporting reducer + +export default handleActions(handlers, state) diff --git a/yarn.lock b/yarn.lock index c3b378f9..e3eb85a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5901,7 +5901,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.6.0: +prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0: version "15.6.0" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" dependencies: @@ -5993,6 +5993,12 @@ querystringify@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" +raf@^3.1.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" + dependencies: + performance-now "^2.1.0" + randomatic@^1.1.3: version "1.1.7" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" @@ -6063,6 +6069,29 @@ react-i18next@^7.3.1: html-parse-stringify2 "2.0.1" prop-types "^15.6.0" +react-mortal@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/react-mortal/-/react-mortal-3.0.1.tgz#be8477513deb08ceb22ba8ae4b52220dc5c741ff" + dependencies: + prop-types "^15.6.0" + react "^16.1.1" + react-motion "^0.5.0" + react-portal "^4.0.0" + +react-motion@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316" + dependencies: + performance-now "^0.2.0" + prop-types "^15.5.8" + raf "^3.1.0" + +react-portal@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-4.1.2.tgz#7f28f3c8c2ed5c541907c0ed0f24e8996acf627f" + dependencies: + prop-types "^15.5.8" + react-redux@^5.0.6: version "5.0.6" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946" @@ -6111,7 +6140,7 @@ react-stand-in@^4.0.0-beta.14: dependencies: shallowequal "^1.0.2" -react@^16.2.0: +react@^16.1.1, react@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba" dependencies: From eadb3dc3043dac1efee54ee56096e66c59dfae33 Mon Sep 17 00:00:00 2001 From: meriadec Date: Mon, 15 Jan 2018 12:11:41 +0100 Subject: [PATCH 2/2] Fix lint, flow, and comm instanciation --- src/components/ReceiveModal.js | 4 +++- src/components/SendModal.js | 4 +++- src/components/SideBar/Item.js | 2 +- src/components/base/Modal.js | 6 +++++- src/internals/usb/wallet.js | 2 +- src/reducers/modals.js | 3 ++- 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/components/ReceiveModal.js b/src/components/ReceiveModal.js index 65541bf9..18de7d98 100644 --- a/src/components/ReceiveModal.js +++ b/src/components/ReceiveModal.js @@ -4,7 +4,9 @@ import React, { PureComponent } from 'react' import Modal from 'components/base/Modal' -class ReceiveModal extends PureComponent { +type Props = {} + +class ReceiveModal extends PureComponent { render() { return receive modal } diff --git a/src/components/SendModal.js b/src/components/SendModal.js index 5bad380d..b267ec16 100644 --- a/src/components/SendModal.js +++ b/src/components/SendModal.js @@ -4,7 +4,9 @@ import React, { PureComponent } from 'react' import Modal from 'components/base/Modal' -class SendModal extends PureComponent { +type Props = {} + +class SendModal extends PureComponent { render() { return send modal } diff --git a/src/components/SideBar/Item.js b/src/components/SideBar/Item.js index 364da66b..c3d667d2 100644 --- a/src/components/SideBar/Item.js +++ b/src/components/SideBar/Item.js @@ -27,7 +27,7 @@ type Props = { openModal: Function, } -const mapStateToProps = (state, { modal }) => ({ +const mapStateToProps = (state, { modal }: any) => ({ // connect router here only to make components re-render // see https://github.com/ReactTraining/react-router/issues/4671 router: state.router, diff --git a/src/components/base/Modal.js b/src/components/base/Modal.js index b71fc377..58495853 100644 --- a/src/components/base/Modal.js +++ b/src/components/base/Modal.js @@ -10,7 +10,7 @@ import styled from 'styled-components' import { closeModal, isModalOpened } from 'reducers/modals' type Props = { - isOpened: boolean, + isOpened?: boolean, onClose: Function, children: any, } @@ -71,6 +71,10 @@ const Body = styled.div.attrs({ ` class Modal extends PureComponent { + static defaultProps = { + isOpened: false, + } + render() { const { isOpened, onClose, children } = this.props return ( diff --git a/src/internals/usb/wallet.js b/src/internals/usb/wallet.js index 3118bdcf..1f6eee8c 100644 --- a/src/internals/usb/wallet.js +++ b/src/internals/usb/wallet.js @@ -5,7 +5,7 @@ import Btc from '@ledgerhq/hw-app-btc' async function getWalletInfos(path, wallet) { if (wallet === 'btc') { - const comm = new CommNodeHid(path, true, 0, false) + const comm = await CommNodeHid.open(path) const btc = new Btc(comm) const walletInfos = await btc.getWalletPublicKey(`44'/0'/0'/0`) return walletInfos diff --git a/src/reducers/modals.js b/src/reducers/modals.js index bb55336a..a41e460f 100644 --- a/src/reducers/modals.js +++ b/src/reducers/modals.js @@ -43,7 +43,8 @@ export const closeModal = createAction('MODAL_CLOSE', name => ({ name })) // Selectors -export const isModalOpened = (state, name) => state.modals[name] && state.modals[name].isOpened +export const isModalOpened = (state: Object, name: string) => + state.modals[name] && state.modals[name].isOpened // Exporting reducer