From 11bbd58e33ce6bdff2ac52b538855d68b8f6753e Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Sat, 11 Sep 2021 23:02:34 +0800 Subject: [PATCH] General-purpose "Confirm" modal Added a re-usable "yes/no" confirmation modal where the client just sets the question string and gets a callback "OK" or "Cancel" is clicked. It doesn't make sense to create one modal for each confirmation, especially when the modal is only used in one place. Replaced one of the existing modal as an example. --- ui/component/walletSwap/index.js | 3 +- ui/component/walletSwap/view.jsx | 16 ++++-- ui/constants/modal_types.js | 2 +- ui/modal/modalConfirm/index.js | 10 ++++ ui/modal/modalConfirm/view.jsx | 56 +++++++++++++++++++++ ui/modal/modalRemoveBtcSwapAddress/index.js | 13 ----- ui/modal/modalRemoveBtcSwapAddress/view.jsx | 43 ---------------- ui/modal/modalRouter/view.jsx | 8 ++- 8 files changed, 84 insertions(+), 67 deletions(-) create mode 100644 ui/modal/modalConfirm/index.js create mode 100644 ui/modal/modalConfirm/view.jsx delete mode 100644 ui/modal/modalRemoveBtcSwapAddress/index.js delete mode 100644 ui/modal/modalRemoveBtcSwapAddress/view.jsx diff --git a/ui/component/walletSwap/index.js b/ui/component/walletSwap/index.js index 5fbb8cc15..13c91cb43 100644 --- a/ui/component/walletSwap/index.js +++ b/ui/component/walletSwap/index.js @@ -2,7 +2,7 @@ import { connect } from 'react-redux'; import { withRouter } from 'react-router'; import WalletSwap from './view'; import { doOpenModal } from 'redux/actions/app'; -import { doAddCoinSwap, doQueryCoinSwapStatus } from 'redux/actions/coinSwap'; +import { doAddCoinSwap, doRemoveCoinSwap, doQueryCoinSwapStatus } from 'redux/actions/coinSwap'; import { doToast } from 'redux/actions/notifications'; import { selectCoinSwaps } from 'redux/selectors/coinSwap'; import { selectUserVerifiedEmail } from 'redux/selectors/user'; @@ -19,6 +19,7 @@ const perform = (dispatch) => ({ openModal: (modal, props) => dispatch(doOpenModal(modal, props)), doToast: (options) => dispatch(doToast(options)), addCoinSwap: (coinSwap) => dispatch(doAddCoinSwap(coinSwap)), + removeCoinSwap: (chargeCode) => dispatch(doRemoveCoinSwap(chargeCode)), getNewAddress: () => dispatch(doGetNewAddress()), checkAddressIsMine: (address) => dispatch(doCheckAddressIsMine(address)), queryCoinSwapStatus: (sendAddress) => dispatch(doQueryCoinSwapStatus(sendAddress)), diff --git a/ui/component/walletSwap/view.jsx b/ui/component/walletSwap/view.jsx index 9a003d4c9..936bd3a73 100644 --- a/ui/component/walletSwap/view.jsx +++ b/ui/component/walletSwap/view.jsx @@ -59,6 +59,7 @@ type Props = { isAuthenticated: boolean, doToast: ({ message: string }) => void, addCoinSwap: (CoinSwapInfo) => void, + removeCoinSwap: (string) => void, getNewAddress: () => void, checkAddressIsMine: (string) => void, openModal: (string, {}) => void, @@ -72,6 +73,7 @@ function WalletSwap(props: Props) { coinSwaps, isAuthenticated, addCoinSwap, + removeCoinSwap, getNewAddress, checkAddressIsMine, openModal, @@ -103,9 +105,15 @@ function WalletSwap(props: Props) { setSwap(null); } - function removeCoinSwap(chargeCode) { - openModal(MODALS.CONFIRM_REMOVE_BTC_SWAP_ADDRESS, { - chargeCode: chargeCode, + function handleRemoveSwap(chargeCode) { + openModal(MODALS.CONFIRM, { + title: __('Remove Swap'), + subtitle: {`${chargeCode}`} }}>Remove %address%?, + body:

{__('This process cannot be reversed.')}

, + onConfirm: (closeModal) => { + removeCoinSwap(chargeCode); + closeModal(); + }, }); } @@ -657,7 +665,7 @@ function WalletSwap(props: Props) { button="link" icon={ICONS.REMOVE} title={__('Remove swap')} - onClick={() => removeCoinSwap(x.chargeCode)} + onClick={() => handleRemoveSwap(x.chargeCode)} /> diff --git a/ui/constants/modal_types.js b/ui/constants/modal_types.js index 2355f3087..7ea2090b4 100644 --- a/ui/constants/modal_types.js +++ b/ui/constants/modal_types.js @@ -1,3 +1,4 @@ +export const CONFIRM = 'confirm'; export const CONFIRM_FILE_REMOVE = 'confirm_file_remove'; export const CONFIRM_EXTERNAL_RESOURCE = 'confirm_external_resource'; export const COMMENT_ACKNOWEDGEMENT = 'comment_acknowlegement'; @@ -42,7 +43,6 @@ export const SYNC_ENABLE = 'SYNC_ENABLE'; export const IMAGE_UPLOAD = 'image_upload'; export const MOBILE_SEARCH = 'mobile_search'; export const VIEW_IMAGE = 'view_image'; -export const CONFIRM_REMOVE_BTC_SWAP_ADDRESS = 'confirm_remove_btc_swap_address'; export const BLOCK_CHANNEL = 'block_channel'; export const COLLECTION_ADD = 'collection_add'; export const COLLECTION_DELETE = 'collection_delete'; diff --git a/ui/modal/modalConfirm/index.js b/ui/modal/modalConfirm/index.js new file mode 100644 index 000000000..07570d2c7 --- /dev/null +++ b/ui/modal/modalConfirm/index.js @@ -0,0 +1,10 @@ +import { connect } from 'react-redux'; +import { doHideModal } from 'redux/actions/app'; + +import ModalConfirm from './view'; + +const perform = (dispatch) => ({ + doHideModal: () => dispatch(doHideModal()), +}); + +export default connect(null, perform)(ModalConfirm); diff --git a/ui/modal/modalConfirm/view.jsx b/ui/modal/modalConfirm/view.jsx new file mode 100644 index 000000000..5f2bb3d36 --- /dev/null +++ b/ui/modal/modalConfirm/view.jsx @@ -0,0 +1,56 @@ +// @flow +import React from 'react'; +import type { Node } from 'react'; +import Button from 'component/button'; +import Card from 'component/common/card'; +import Spinner from 'component/spinner'; +import { Modal } from 'modal/modal'; + +type Props = { + title: string, + subtitle?: string | Node, + body?: string | Node, + labelOk?: string, + labelCancel?: string, + onConfirm: (closeModal: () => void, setIsBusy: (boolean) => void) => void, + hideCancel?: boolean, + // --- perform --- + doHideModal: () => void, +}; + +export default function ModalConfirm(props: Props) { + const { title, subtitle, body, labelOk, labelCancel, onConfirm, hideCancel, doHideModal } = props; + const [isBusy, setIsBusy] = React.useState(false); + + function handleOnClick() { + if (onConfirm) { + onConfirm(doHideModal, setIsBusy); + } + } + + function getOkLabel() { + return isBusy ? : labelOk || __('OK'); + } + + function getCancelLabel() { + return labelCancel || __('Cancel'); + } + + return ( + + +
+
+ + } + /> +
+ ); +} diff --git a/ui/modal/modalRemoveBtcSwapAddress/index.js b/ui/modal/modalRemoveBtcSwapAddress/index.js deleted file mode 100644 index 465b4cc19..000000000 --- a/ui/modal/modalRemoveBtcSwapAddress/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux'; -import { doHideModal } from 'redux/actions/app'; -import ModalRemoveBtcSwapAddress from './view'; -import { doRemoveCoinSwap } from 'redux/actions/coinSwap'; - -const select = (state, props) => ({}); - -const perform = (dispatch) => ({ - removeCoinSwap: (chargeCode) => dispatch(doRemoveCoinSwap(chargeCode)), - closeModal: () => dispatch(doHideModal()), -}); - -export default connect(select, perform)(ModalRemoveBtcSwapAddress); diff --git a/ui/modal/modalRemoveBtcSwapAddress/view.jsx b/ui/modal/modalRemoveBtcSwapAddress/view.jsx deleted file mode 100644 index 6d10d43cb..000000000 --- a/ui/modal/modalRemoveBtcSwapAddress/view.jsx +++ /dev/null @@ -1,43 +0,0 @@ -// @flow -import React from 'react'; -import { Modal } from 'modal/modal'; -import Button from 'component/button'; -import Card from 'component/common/card'; -import I18nMessage from 'component/i18nMessage'; - -type Props = { - chargeCode: string, - removeCoinSwap: (string) => void, - closeModal: () => void, -}; - -function ModalRemoveBtcSwapAddress(props: Props) { - const { chargeCode, removeCoinSwap, closeModal } = props; - - return ( - - {`${chargeCode}`} }}>Remove %address%?} - body={

{__('This process cannot be reversed.')}

} - actions={ - <> -
-
- - } - /> -
- ); -} - -export default ModalRemoveBtcSwapAddress; diff --git a/ui/modal/modalRouter/view.jsx b/ui/modal/modalRouter/view.jsx index 856824f28..9a3ed0a04 100644 --- a/ui/modal/modalRouter/view.jsx +++ b/ui/modal/modalRouter/view.jsx @@ -23,6 +23,7 @@ const ModalClaimCollectionAdd = lazyImport(() => const ModalCommentAcknowledgement = lazyImport(() => import('modal/modalCommentAcknowledgement' /* webpackChunkName: "modalCommentAcknowledgement" */) ); +const ModalConfirm = lazyImport(() => import('modal/modalConfirm' /* webpackChunkName: "modalConfirm" */)); const ModalConfirmAge = lazyImport(() => import('modal/modalConfirmAge' /* webpackChunkName: "modalConfirmAge" */)); const ModalConfirmThumbnailUpload = lazyImport(() => import('modal/modalConfirmThumbnailUpload' /* webpackChunkName: "modalConfirmThumbnailUpload" */) @@ -63,9 +64,6 @@ const ModalPublish = lazyImport(() => import('modal/modalPublish' /* webpackChun const ModalPublishPreview = lazyImport(() => import('modal/modalPublishPreview' /* webpackChunkName: "modalPublishPreview" */) ); -const ModalRemoveBtcSwapAddress = lazyImport(() => - import('modal/modalRemoveBtcSwapAddress' /* webpackChunkName: "modalRemoveBtcSwapAddress" */) -); const ModalRemoveCard = lazyImport(() => import('modal/modalRemoveCard' /* webpackChunkName: "modalRemoveCard" */)); const ModalRemoveComment = lazyImport(() => import('modal/modalRemoveComment' /* webpackChunkName: "modalRemoveComment" */) @@ -124,6 +122,8 @@ function ModalRouter(props: Props) { function getModal(id) { switch (id) { + case MODALS.CONFIRM: + return ModalConfirm; case MODALS.UPGRADE: return ModalUpgrade; case MODALS.DOWNLOADING: @@ -198,8 +198,6 @@ function ModalRouter(props: Props) { return ModalViewImage; case MODALS.MASS_TIP_UNLOCK: return ModalMassTipsUnlock; - case MODALS.CONFIRM_REMOVE_BTC_SWAP_ADDRESS: - return ModalRemoveBtcSwapAddress; case MODALS.BLOCK_CHANNEL: return ModalBlockChannel; case MODALS.COLLECTION_ADD: