From c000ad1bc8723a37dae5ac2d557267af21451a43 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Sun, 3 Dec 2017 22:27:55 -0500 Subject: [PATCH] use flow --- src/renderer/.flowconfig | 3 + src/renderer/flow-typed/bluebird.js | 3 + src/renderer/flow-typed/classnames.js | 3 + src/renderer/flow-typed/formik.js | 3 + src/renderer/flow-typed/i18n.js | 1 + src/renderer/flow-typed/qrcode.react.js | 3 + src/renderer/flow-typed/shapeshift.io.js | 3 + .../shapeShift/internal/active-shift.jsx | 46 +++- .../js/component/shapeShift/internal/form.jsx | 59 ++-- src/renderer/js/component/shapeShift/view.jsx | 39 ++- src/renderer/js/redux/actions/shape_shift.js | 67 ++++- src/renderer/js/redux/reducers/shape_shift.js | 256 ++++++++++++++---- src/renderer/js/types/common.js | 3 + src/renderer/js/util/redux-utils.js | 1 + 14 files changed, 381 insertions(+), 109 deletions(-) create mode 100644 src/renderer/flow-typed/bluebird.js create mode 100644 src/renderer/flow-typed/classnames.js create mode 100644 src/renderer/flow-typed/formik.js create mode 100644 src/renderer/flow-typed/i18n.js create mode 100644 src/renderer/flow-typed/qrcode.react.js create mode 100644 src/renderer/flow-typed/shapeshift.io.js create mode 100644 src/renderer/js/types/common.js diff --git a/src/renderer/.flowconfig b/src/renderer/.flowconfig index 18e154135..21d1a300a 100644 --- a/src/renderer/.flowconfig +++ b/src/renderer/.flowconfig @@ -12,6 +12,9 @@ flow-typed suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe suppress_comment=\\(.\\|\n\\)*\\$FlowIssue module.name_mapper='^constants\(.*\)$' -> '/js/constants\1' +module.name_mapper='^util\(.*\)$' -> '/js/util\1' module.name_mapper='^redux\(.*\)$' -> '/js/redux\1' +module.name_mapper='^types\(.*\)$' -> '/js/types\1' +module.name_mapper='^component\(.*\)$' -> '/js/component\1' [strict] diff --git a/src/renderer/flow-typed/bluebird.js b/src/renderer/flow-typed/bluebird.js new file mode 100644 index 000000000..b6ea52b19 --- /dev/null +++ b/src/renderer/flow-typed/bluebird.js @@ -0,0 +1,3 @@ +declare module 'bluebird' { + declare module.exports: any; +} diff --git a/src/renderer/flow-typed/classnames.js b/src/renderer/flow-typed/classnames.js new file mode 100644 index 000000000..8ed60a607 --- /dev/null +++ b/src/renderer/flow-typed/classnames.js @@ -0,0 +1,3 @@ +declare module 'classnames' { + declare module.exports: any; +} diff --git a/src/renderer/flow-typed/formik.js b/src/renderer/flow-typed/formik.js new file mode 100644 index 000000000..020efafd4 --- /dev/null +++ b/src/renderer/flow-typed/formik.js @@ -0,0 +1,3 @@ +declare module 'formik' { + declare module.exports: any; +} diff --git a/src/renderer/flow-typed/i18n.js b/src/renderer/flow-typed/i18n.js new file mode 100644 index 000000000..cbceb4b59 --- /dev/null +++ b/src/renderer/flow-typed/i18n.js @@ -0,0 +1 @@ +declare function __(a: string): string; diff --git a/src/renderer/flow-typed/qrcode.react.js b/src/renderer/flow-typed/qrcode.react.js new file mode 100644 index 000000000..43a589258 --- /dev/null +++ b/src/renderer/flow-typed/qrcode.react.js @@ -0,0 +1,3 @@ +declare module 'qrcode.react' { + declare module.exports: any; +} diff --git a/src/renderer/flow-typed/shapeshift.io.js b/src/renderer/flow-typed/shapeshift.io.js new file mode 100644 index 000000000..2d32593ee --- /dev/null +++ b/src/renderer/flow-typed/shapeshift.io.js @@ -0,0 +1,3 @@ +declare module 'shapeshift.io' { + declare module.exports: any; +} diff --git a/src/renderer/js/component/shapeShift/internal/active-shift.jsx b/src/renderer/js/component/shapeShift/internal/active-shift.jsx index 38c18c4d5..cd8577261 100644 --- a/src/renderer/js/component/shapeShift/internal/active-shift.jsx +++ b/src/renderer/js/component/shapeShift/internal/active-shift.jsx @@ -1,12 +1,27 @@ -import React, { PureComponent } from "react"; +// @flow +import * as React from "react"; import QRCode from "qrcode.react"; import * as statuses from "constants/shape_shift"; import Address from "component/address"; import Link from "component/link"; +import type { Dispatch } from "redux/actions/shape_shift"; -export default class ActiveShapeShift extends PureComponent { +type Props = { + shiftState: ?string, + shiftCoinType: ?string, + shiftDepositAddress: ?string, + shiftReturnAddress: ?string, + shiftOrderId: ?string, + originCoinDepositMax: ?number, + clearShapeShift: Dispatch, + doShowSnackBar: Dispatch, + getActiveShift: Dispatch, +}; + +class ActiveShapeShift extends React.PureComponent { constructor() { super(); + // $FlowFixMe this.continousFetch = undefined; } @@ -14,12 +29,13 @@ export default class ActiveShapeShift extends PureComponent { const { getActiveShift, shiftDepositAddress } = this.props; getActiveShift(shiftDepositAddress); + // $FlowFixMe this.continousFetch = setInterval(() => { getActiveShift(shiftDepositAddress); }, 10000); } - componentWillUpdate(nextProps) { + componentWillUpdate(nextProps: Props) { const { shiftState } = nextProps; if (shiftState === statuses.COMPLETE) { this.clearContinuousFetch(); @@ -31,7 +47,9 @@ export default class ActiveShapeShift extends PureComponent { } clearContinuousFetch() { + /// $FlowFixMe clearInterval(this.continousFetch); + // $FlowFixMe this.continousFetch = null; } @@ -42,7 +60,7 @@ export default class ActiveShapeShift extends PureComponent { shiftReturnAddress, shiftOrderId, shiftState, - shiftDepositLimit, + originCoinDepositMax, clearShapeShift, doShowSnackBar, } = this.props; @@ -54,7 +72,7 @@ export default class ActiveShapeShift extends PureComponent {

Send up to{" "} - {shiftDepositLimit}{" "} + {originCoinDepositMax}{" "} {shiftCoinType} {" "} to the address below. @@ -102,13 +120,15 @@ export default class ActiveShapeShift extends PureComponent { : __("Cancel") } /> - - - + {shiftOrderId && ( + + + + )} {shiftState === statuses.NO_DEPOSITS && shiftReturnAddress && (

@@ -123,3 +143,5 @@ export default class ActiveShapeShift extends PureComponent { ); } } + +export default ActiveShapeShift; diff --git a/src/renderer/js/component/shapeShift/internal/form.jsx b/src/renderer/js/component/shapeShift/internal/form.jsx index bd1925738..58d17b10c 100644 --- a/src/renderer/js/component/shapeShift/internal/form.jsx +++ b/src/renderer/js/component/shapeShift/internal/form.jsx @@ -2,25 +2,48 @@ import React from "react"; import Link from "component/link"; import { getExampleAddress } from "util/shape_shift"; import { Submit, FormRow } from "component/form"; +import type { ShapeShiftFormValues, Dispatch } from "redux/actions/shape_shift"; -export default ({ - values, - errors, - touched, - handleChange, - handleBlur, - handleSubmit, - resetForm, - isSubmitting, - shiftSupportedCoins, - originCoin, - updating, - getCoinStats, - receiveAddress, - originCoinDepositMax, - originCoinDepositMin, - originCoinDepositFee, -}) => { +type ShapeShiftFormErrors = { + returnAddress?: string, +}; + +type Props = { + values: ShapeShiftFormValues, + errors: ShapeShiftFormErrors, + touched: boolean, + handleChange: Event => any, + handleBlur: Event => any, + handleSubmit: Event => any, + isSubmitting: boolean, + shiftSupportedCoins: Array, + originCoin: string, + updating: boolean, + getCoinStats: Dispatch, + receiveAddress: string, + originCoinDepositFee: number, + originCoinDepositMin: string, + originCoinDepositMax: number, +}; + +export default (props: Props) => { + const { + values, + errors, + touched, + handleChange, + handleBlur, + handleSubmit, + isSubmitting, + shiftSupportedCoins, + originCoin, + updating, + getCoinStats, + receiveAddress, + originCoinDepositMax, + originCoinDepositMin, + originCoinDepositFee, + } = props; return (
diff --git a/src/renderer/js/component/shapeShift/view.jsx b/src/renderer/js/component/shapeShift/view.jsx index f2991863f..8ca97e9c3 100644 --- a/src/renderer/js/component/shapeShift/view.jsx +++ b/src/renderer/js/component/shapeShift/view.jsx @@ -1,4 +1,5 @@ -import React from "react"; +// @flow +import * as React from "react"; import { shell } from "electron"; import { Formik } from "formik"; import classnames from "classnames"; @@ -8,9 +9,23 @@ import Link from "component/link"; import Spinner from "component/common/spinner"; import { BusyMessage } from "component/common"; import ShapeShiftForm from "./internal/form"; -import ActiveShift from "./internal/active-shift"; +import ActiveShapeShift from "./internal/active-shift"; -class ShapeShift extends React.PureComponent { +import type { ShapeShiftState } from "redux/reducers/shape_shift"; +import type { Dispatch, ShapeShiftFormValues } from "redux/actions/shape_shift"; + +type Props = { + shapeShift: ShapeShiftState, + getCoinStats: Dispatch, + createShapeShift: Dispatch, + clearShapeShift: Dispatch, + getActiveShift: Dispatch, + doShowSnackBar: Dispatch, + shapeShiftInit: Dispatch, + receiveAddress: string, +}; + +class ShapeShift extends React.PureComponent { componentDidMount() { const { shapeShiftInit, @@ -49,11 +64,15 @@ class ShapeShift extends React.PureComponent { shiftReturnAddress, shiftCoinType, shiftOrderId, - cancelShapeShift, shiftState, - origin, } = shapeShift; + const initialFormValues: ShapeShiftFormValues = { + receiveAddress, + originCoin: "BTC", + returnAddress: "", + }; + return ( // add the "shapeshift__intital-wrapper class so we can avoid content jumping once everything loads" // it just gives the section a min-height equal to the height of the content when the form is rendered @@ -85,11 +104,7 @@ class ShapeShift extends React.PureComponent { ( )} {hasActiveShift && ( - dispatch => { +// All ShapeShift actions +// Action types defined in the reducer will contain some payload +export type Action = + | { type: types.GET_SUPPORTED_COINS_START } + | { type: types.GET_SUPPORTED_COINS_FAIL } + | GetSupportedCoinsSuccess + | GetCoinStatsStart + | { type: types.GET_COIN_STATS_START } + | GetCoinStatsFail + | GetCoinStatsSuccess + | { type: types.PREPARE_SHAPE_SHIFT_START } + | PrepareShapeShiftFail + | PrepareShapeShiftSuccess + | { type: types.GET_ACTIVE_SHIFT_START } + | GetActiveShiftFail + | GetActiveShiftSuccess; + +// Basic thunk types +// It would be nice to import these from types/common +// Not sure how that would work since they rely on the Action type +type PromiseAction = Promise; +type ThunkAction = (dispatch: Dispatch) => any; +export type Dispatch = ( + action: Action | ThunkAction | PromiseAction | Array +) => any; + +// ShapeShift form values +export type ShapeShiftFormValues = { + originCoin: string, + returnAddress: ?string, + receiveAddress: string, +}; + +export const shapeShiftInit = () => (dispatch: Dispatch): ThunkAction => { dispatch({ type: types.GET_SUPPORTED_COINS_START }); return shapeShift @@ -34,8 +79,9 @@ export const shapeShiftInit = () => dispatch => { ); }; -export const getCoinStats = coin => dispatch => { - // TODO: get ShapeShift fee +export const getCoinStats = (coin: string) => ( + dispatch: Dispatch +): ThunkAction => { const pair = `${coin.toLowerCase()}_lbc`; dispatch({ type: types.GET_COIN_STATS_START, data: coin }); @@ -48,7 +94,10 @@ export const getCoinStats = coin => dispatch => { .catch(err => dispatch({ type: types.GET_COIN_STATS_FAIL, data: err })); }; -export const createShapeShift = (values, actions) => dispatch => { +export const createShapeShift = ( + values: ShapeShiftFormValues, + actions: FormikActions +) => (dispatch: Dispatch): ThunkAction => { const { originCoin, returnAddress, @@ -68,12 +117,14 @@ export const createShapeShift = (values, actions) => dispatch => { ) .catch(err => { dispatch({ type: types.PREPARE_SHAPE_SHIFT_FAIL, data: err }); - // for formik + // for formik to stop the submit actions.setSubmitting(false); }); }; -export const getActiveShift = depositAddress => dispatch => { +export const getActiveShift = (depositAddress: string) => ( + dispatch: Dispatch +): ThunkAction => { dispatch({ type: types.GET_ACTIVE_SHIFT_START }); return shapeShift @@ -82,5 +133,5 @@ export const getActiveShift = depositAddress => dispatch => { .catch(err => dispatch({ type: types.GET_ACTIVE_SHIFT_FAIL, data: err })); }; -export const clearShapeShift = () => dispatch => +export const clearShapeShift = () => (dispatch: Dispatch): Action => dispatch({ type: types.CLEAR_SHAPE_SHIFT }); diff --git a/src/renderer/js/redux/reducers/shape_shift.js b/src/renderer/js/redux/reducers/shape_shift.js index dd44378f0..78542f48e 100644 --- a/src/renderer/js/redux/reducers/shape_shift.js +++ b/src/renderer/js/redux/reducers/shape_shift.js @@ -1,14 +1,88 @@ +// @flow import { handleActions } from "util/redux-utils"; -import * as types from "constants/action_types"; +import * as actions from "constants/action_types"; import * as statuses from "constants/shape_shift"; -const defaultState = { +export type ShapeShiftState = { + loading: boolean, + updating: boolean, + shiftSupportedCoins: Array, + hasActiveShift: boolean, + originCoin: ?string, + error: ?string, + shiftDepositAddress: ?string, + shiftReturnAddress: ?string, + shiftCoinType: ?string, + shiftOrderId: ?string, + shiftState: ?string, + originCoinDepositMax: ?number, + // originCoinDepositMin is a string because we need to convert it from scientifc notation + // it will usually be something like 0.00000001 coins + // using Number(x) or parseInt(x) will either change it back to scientific notation or round to zero + originCoinDepositMin: ?string, + originCoinDepositFee: ?number, +}; + +// All ShapeShift actions that will have some payload +export type GetSupportedCoinsSuccess = { + type: actions.GET_SUPPORTED_COINS_SUCCESS, + data: Array, +}; +export type GetCoinStatsStart = { + type: actions.GET_SUPPORTED_COINS_SUCCESS, + data: string, +}; +export type GetCoinStatsSuccess = { + type: actions.GET_COIN_STATS_SUCCESS, + data: ShapeShiftMarketInfo, +}; +export type GetCoinStatsFail = { + type: actions.GET_COIN_STATS_FAIL, + data: string, +}; +export type PrepareShapeShiftSuccess = { + type: actions.PREPARE_SHAPE_SHIFT_SUCCESS, + data: ActiveShiftInfo, +}; +export type PrepareShapeShiftFail = { + type: actions.PREPARE_SHAPE_SHIFT_FAIL, + data: ShapeShiftErrorResponse, +}; +export type GetActiveShiftSuccess = { + type: actions.GET_ACTIVE_SHIFT_SUCCESS, + data: string, +}; +export type GetActiveShiftFail = { + type: actions.GET_ACTIVE_SHIFT_FAIL, + data: ShapeShiftErrorResponse, +}; + +// ShapeShift sub-types +// Defined for actions that contain an object in the payload +type ShapeShiftMarketInfo = { + limit: number, + minimum: number, + minerFee: number, +}; + +type ActiveShiftInfo = { + deposit: string, + depositType: string, + returnAddress: string, + orderId: string, +}; + +type ShapeShiftErrorResponse = { + message: string, +}; + +const defaultState: ShapeShiftState = { loading: true, updating: false, - error: undefined, shiftSupportedCoins: [], - originCoin: undefined, hasActiveShift: false, + originCoin: undefined, + error: undefined, shiftDepositAddress: undefined, // shapeshift address to send your coins to shiftReturnAddress: undefined, shiftCoinType: undefined, @@ -21,61 +95,108 @@ const defaultState = { export default handleActions( { - [types.GET_SUPPORTED_COINS_START]: () => ({ + [actions.GET_SUPPORTED_COINS_START]: ( + state: ShapeShiftState + ): ShapeShiftState => ({ + ...state, loading: true, error: undefined, }), - [types.GET_SUPPORTED_COINS_SUCCESS]: ( - state, - { data: shiftSupportedCoins } - ) => ({ - error: undefined, - shiftSupportedCoins, - }), - [types.GET_SUPPORTED_COINS_FAIL]: () => ({ + [actions.GET_SUPPORTED_COINS_SUCCESS]: ( + state: ShapeShiftState, + action: GetSupportedCoinsSuccess + ): ShapeShiftState => { + const shiftSupportedCoins = action.data; + return { + ...state, + error: undefined, + shiftSupportedCoins, + }; + }, + [actions.GET_SUPPORTED_COINS_FAIL]: ( + state: ShapeShiftState + ): ShapeShiftState => ({ + ...state, loading: false, - error: true, + error: "Error getting available coins", }), - [types.GET_COIN_STATS_START]: (state, { data: coin }) => ({ - updating: true, - originCoin: coin, - }), - [types.GET_COIN_STATS_SUCCESS]: (state, { data: marketInfo }) => ({ - loading: false, - updating: false, - originCoinDepositMax: marketInfo.limit, - // this will come in scientific notation - // toFixed shows the real number, then regex to remove trailing zeros - originCoinDepositMin: marketInfo.minimum - .toFixed(10) - .replace(/\.?0+$/, ""), - originCoinDepositFee: marketInfo.minerFee, - }), - [types.GET_COIN_STATS_FAIL]: (state, { data: error }) => ({ - loading: false, - error, - }), + [actions.GET_COIN_STATS_START]: ( + state: ShapeShiftState, + action: GetCoinStatsStart + ): ShapeShiftState => { + const coin = action.data; + return { + ...state, + updating: true, + originCoin: coin, + }; + }, + [actions.GET_COIN_STATS_SUCCESS]: ( + state: ShapeShiftState, + action: GetCoinStatsSuccess + ): ShapeShiftState => { + const marketInfo: ShapeShiftMarketInfo = action.data; - [types.PREPARE_SHAPE_SHIFT_START]: () => ({ + return { + ...state, + loading: false, + updating: false, + originCoinDepositMax: marketInfo.limit, + // this will come in scientific notation + // toFixed shows the real number, then regex to remove trailing zeros + originCoinDepositMin: marketInfo.minimum + .toFixed(10) + .replace(/\.?0+$/, ""), + originCoinDepositFee: marketInfo.minerFee, + }; + }, + [actions.GET_COIN_STATS_FAIL]: ( + state: ShapeShiftState, + action: GetCoinStatsFail + ): ShapeShiftState => { + const error = action.data; + return { + ...state, + loading: false, + error, + }; + }, + + [actions.PREPARE_SHAPE_SHIFT_START]: ( + state: ShapeShiftState + ): ShapeShiftState => ({ + ...state, error: undefined, }), - [types.PREPARE_SHAPE_SHIFT_SUCCESS]: ( - state, - { data: { deposit, depositType, returnAddress, orderId } } - ) => ({ - hasActiveShift: true, - shiftDepositAddress: deposit, - shiftCoinType: depositType, - shiftReturnAddress: returnAddress, - shiftOrderId: orderId, - shiftState: statuses.NO_DEPOSITS, - }), - [types.PREPARE_SHAPE_SHIFT_FAIL]: (state, { data: error }) => ({ - error: error.message, - }), + [actions.PREPARE_SHAPE_SHIFT_SUCCESS]: ( + state: ShapeShiftState, + action: PrepareShapeShiftSuccess + ) => { + const activeShiftInfo: ActiveShiftInfo = action.data; + return { + ...state, + hasActiveShift: true, + shiftDepositAddress: activeShiftInfo.deposit, + shiftCoinType: activeShiftInfo.depositType, + shiftReturnAddress: activeShiftInfo.returnAddress, + shiftOrderId: activeShiftInfo.orderId, + shiftState: statuses.NO_DEPOSITS, + }; + }, + [actions.PREPARE_SHAPE_SHIFT_FAIL]: ( + state: ShapeShiftState, + action: PrepareShapeShiftFail + ) => { + const error: ShapeShiftErrorResponse = action.data; + return { + ...state, + error: error.message, + }; + }, - [types.CLEAR_SHAPE_SHIFT]: () => ({ + [actions.CLEAR_SHAPE_SHIFT]: (state: ShapeShiftState): ShapeShiftState => ({ + ...state, loading: false, updating: false, hasActiveShift: false, @@ -83,21 +204,38 @@ export default handleActions( shiftReturnAddress: undefined, shiftCoinType: undefined, shiftOrderId: undefined, - originCoin: "BTC", + originCoin: state.shiftSupportedCoins[0], }), - [types.GET_ACTIVE_SHIFT_START]: () => ({ + [actions.GET_ACTIVE_SHIFT_START]: ( + state: ShapeShiftState + ): ShapeShiftState => ({ + ...state, error: undefined, updating: true, }), - [types.GET_ACTIVE_SHIFT_SUCCESS]: (state, { data: status }) => ({ - updating: false, - shiftState: status, - }), - [types.GET_ACTIVE_SHIFT_FAIL]: (state, { data: error }) => ({ - updating: false, - error: error.message, - }), + [actions.GET_ACTIVE_SHIFT_SUCCESS]: ( + state: ShapeShiftState, + action: GetActiveShiftSuccess + ): ShapeShiftState => { + const status = action.data; + return { + ...state, + updating: false, + shiftState: status, + }; + }, + [actions.GET_ACTIVE_SHIFT_FAIL]: ( + state: ShapeShiftState, + action: GetActiveShiftFail + ): ShapeShiftState => { + const error: ShapeShiftErrorResponse = action.data; + return { + ...state, + updating: false, + error: error.message, + }; + }, }, defaultState ); diff --git a/src/renderer/js/types/common.js b/src/renderer/js/types/common.js new file mode 100644 index 000000000..ea8c0d815 --- /dev/null +++ b/src/renderer/js/types/common.js @@ -0,0 +1,3 @@ +export type FormikActions = { + setSubmitting: boolean => mixed, +}; diff --git a/src/renderer/js/util/redux-utils.js b/src/renderer/js/util/redux-utils.js index 6875ec550..17aa4fdaf 100644 --- a/src/renderer/js/util/redux-utils.js +++ b/src/renderer/js/util/redux-utils.js @@ -1,6 +1,7 @@ // util for creating reducers // based off of redux-actions // https://redux-actions.js.org/docs/api/handleAction.html#handleactions + export const handleActions = (actionMap, defaultState) => { return (state = defaultState, action) => { const handler = actionMap[action.type];