diff --git a/web/cashtab/src/components/Common/Ticker.js b/web/cashtab/src/components/Common/Ticker.js --- a/web/cashtab/src/components/Common/Ticker.js +++ b/web/cashtab/src/components/Common/Ticker.js @@ -24,7 +24,7 @@ tokenIconsUrl: 'https://etoken-icons.s3.us-west-2.amazonaws.com', txHistoryCount: 10, xecApiBatchSize: 20, - defaultSettings: { fiatCurrency: 'usd' }, + defaultSettings: { fiatCurrency: 'usd', sendModal: false }, notificationDurationShort: 3, notificationDurationLong: 5, newTokenDefaultUrl: 'https://cashtab.com/', @@ -68,6 +68,7 @@ 'nzd', 'chf', ], + sendModal: [true, false], }, fiatCurrencies: { usd: { name: 'US Dollar', symbol: '$', slug: 'usd' }, diff --git a/web/cashtab/src/components/Configure/Configure.js b/web/cashtab/src/components/Configure/Configure.js --- a/web/cashtab/src/components/Configure/Configure.js +++ b/web/cashtab/src/components/Configure/Configure.js @@ -496,6 +496,10 @@ } }; + const handleSendModalToggle = checkedState => { + changeCashtabSettings('sendModal', checkedState); + }; + return ( @@ -770,6 +774,20 @@ )} + +
+ Send Confirmations +
+ } + unCheckedChildren={} + checked={ + cashtabSettings ? cashtabSettings.sendModal : false + } + onChange={handleSendModalToggle} + /> +
[ +
+
+ + + + Send Confirmations +
+ +
@@ -770,6 +833,69 @@
+
+
+ + + + Send Confirmations +
+ +
diff --git a/web/cashtab/src/components/Send/Send.js b/web/cashtab/src/components/Send/Send.js --- a/web/cashtab/src/components/Send/Send.js +++ b/web/cashtab/src/components/Send/Send.js @@ -139,8 +139,15 @@ setOpReturnMsg(''); // OP_RETURN message has its own state field }; - const showModal = () => { - setIsModalVisible(true); + const checkForConfirmationBeforeSendXec = () => { + if (txInfoFromUrl) { + setIsModalVisible(true); + } else if (cashtabSettings.sendModal) { + setIsModalVisible(cashtabSettings.sendModal); + } else { + // if the user does not have the send confirmation enabled in settings then send directly + send(); + } }; const handleOk = () => { @@ -626,8 +633,11 @@ onCancel={handleCancel} >

- Are you sure you want to send {formData.value}{' '} - {currency.ticker} to {formData.address}? + {isOneToManyXECSend + ? `are you sure you want to send the following One to Many transaction? + ${formData.address}` + : `Are you sure you want to send ${formData.value}${' '} + ${currency.ticker} to ${formData.address}?`}

@@ -798,14 +808,16 @@ <> {txInfoFromUrl ? ( showModal()} + onClick={() => + checkForConfirmationBeforeSendXec() + } > Send ) : ( { - send(); + checkForConfirmationBeforeSendXec(); }} > Send diff --git a/web/cashtab/src/components/Send/SendToken.js b/web/cashtab/src/components/Send/SendToken.js --- a/web/cashtab/src/components/Send/SendToken.js +++ b/web/cashtab/src/components/Send/SendToken.js @@ -1,7 +1,16 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { WalletContext } from '@utils/context'; -import { Form, message, Row, Col, Alert, Descriptions } from 'antd'; +import { + Form, + message, + Row, + Col, + Alert, + Descriptions, + Popover, + Modal, +} from 'antd'; import PrimaryButton, { SecondaryButton, } from '@components/Common/PrimaryButton'; @@ -42,7 +51,8 @@ `; const SendToken = ({ tokenId, jestBCH, passLoadingStatus }) => { - const { wallet, apiError } = React.useContext(WalletContext); + const { wallet, apiError, cashtabSettings } = + React.useContext(WalletContext); const walletState = getWalletState(wallet); const { tokens, slpBalancesAndUtxos } = walletState; const token = tokens.find(token => token.tokenId === tokenId); @@ -56,6 +66,7 @@ const { width } = useWindowDimensions(); // Load with QR code open if device is mobile and NOT iOS + anything but safari const scannerSupported = width < 769 && isMobile && !(isIOS && !isSafari); + const [isModalVisible, setIsModalVisible] = useState(false); const [formData, setFormData] = useState({ value: '', @@ -236,6 +247,24 @@ } }; + const checkForConfirmationBeforeSendEtoken = () => { + if (cashtabSettings.sendModal) { + setIsModalVisible(cashtabSettings.sendModal); + } else { + // if the user does not have the send confirmation enabled in settings then send directly + submit(); + } + }; + + const handleOk = () => { + setIsModalVisible(false); + submit(); + }; + + const handleCancel = () => { + setIsModalVisible(false); + }; + useEffect(() => { // If the balance has changed, unlock the UI // This is redundant, if backend has refreshed in 1.75s timeout below, UI will already be unlocked @@ -245,6 +274,19 @@ return ( <> + +

+ {token && token.info && formData + ? `Are you sure you want to send ${formData.value}${' '} + ${token.info.tokenTicker} to ${formData.address}?` + : ''} +

+
{!token && } {token && ( @@ -354,7 +396,11 @@ ) : ( - submit()}> + + checkForConfirmationBeforeSendEtoken() + } + > Send {token.info.tokenName} )} diff --git a/web/cashtab/src/hooks/useWallet.js b/web/cashtab/src/hooks/useWallet.js --- a/web/cashtab/src/hooks/useWallet.js +++ b/web/cashtab/src/hooks/useWallet.js @@ -960,14 +960,18 @@ setLoading(false); return; } + // Make sure function was called with valid params - if ( - Object.keys(currentSettings).includes(key) && - currency.settingsValidation[key].includes(newValue) - ) { + if (currency.settingsValidation[key].includes(newValue)) { // Update settings newSettings = currentSettings; newSettings[key] = newValue; + } else { + // Set fiat price to null, which disables fiat sends throughout the app + setFiatPrice(null); + // Unlock the UI + setLoading(false); + return; } // Set new settings in state so they are available in context throughout the app setCashtabSettings(newSettings); diff --git a/web/cashtab/src/utils/__tests__/validation.test.js b/web/cashtab/src/utils/__tests__/validation.test.js --- a/web/cashtab/src/utils/__tests__/validation.test.js +++ b/web/cashtab/src/utils/__tests__/validation.test.js @@ -247,15 +247,33 @@ expect(isValidTokenStats(noCovidStatsInvalid)).toBe(false); }); it(`Recognizes a valid cashtab settings object`, () => { - expect(isValidCashtabSettings({ fiatCurrency: 'usd' })).toBe(true); + expect( + isValidCashtabSettings({ fiatCurrency: 'usd', sendModal: false }), + ).toBe(true); }); it(`Rejects a cashtab settings object for an unsupported currency`, () => { - expect(isValidCashtabSettings({ fiatCurrency: 'xau' })).toBe(false); + expect( + isValidCashtabSettings({ fiatCurrency: 'xau', sendModal: false }), + ).toBe(false); }); it(`Rejects a corrupted cashtab settings object for an unsupported currency`, () => { - expect(isValidCashtabSettings({ fiatCurrencyWrongLabel: 'usd' })).toBe( - false, - ); + expect( + isValidCashtabSettings({ + fiatCurrencyWrongLabel: 'usd', + sendModal: false, + }), + ).toBe(false); + }); + it(`Rejects a valid fiatCurrency setting but undefined sendModal setting`, () => { + expect(isValidCashtabSettings({ fiatCurrency: 'usd' })).toBe(false); + }); + it(`Rejects a valid fiatCurrency setting but invalid sendModal setting`, () => { + expect( + isValidCashtabSettings({ + fiatCurrency: 'usd', + sendModal: 'NOTVALID', + }), + ).toBe(false); }); it(`isValidXecAddress correctly validates a valid XEC address with ecash: prefix`, () => { const addr = 'ecash:qz2708636snqhsxu8wnlka78h6fdp77ar59jrf5035'; diff --git a/web/cashtab/src/utils/validation.js b/web/cashtab/src/utils/validation.js --- a/web/cashtab/src/utils/validation.js +++ b/web/cashtab/src/utils/validation.js @@ -122,12 +122,18 @@ export const isValidCashtabSettings = settings => { try { - const isValid = - typeof settings === 'object' && - Object.prototype.hasOwnProperty.call(settings, 'fiatCurrency') && - currency.settingsValidation.fiatCurrency.includes( - settings.fiatCurrency, - ); + let isValidSettingParams = true; + for (let param in currency.defaultSettings) { + if ( + !Object.prototype.hasOwnProperty.call(settings, param) || + !currency.settingsValidation[param].includes(settings[param]) + ) { + isValidSettingParams = false; + break; + } + } + const isValid = typeof settings === 'object' && isValidSettingParams; + return isValid; } catch (err) { return false;