diff --git a/web/cashtab/src/components/Common/Atoms.js b/web/cashtab/src/components/Common/Atoms.js
index 6a25ba1ed..3de4810ea 100644
--- a/web/cashtab/src/components/Common/Atoms.js
+++ b/web/cashtab/src/components/Common/Atoms.js
@@ -1,52 +1,63 @@
import styled from 'styled-components';
export const LoadingCtn = styled.div`
width: 100%;
display: flex;
align-items: center;
justify-content: center;
height: 400px;
flex-direction: column;
svg {
width: 50px;
height: 50px;
fill: ${props => props.theme.primary};
}
`;
export const BalanceHeader = styled.div`
color: ${props => props.theme.wallet.text.primary};
width: 100%;
font-size: 30px;
font-weight: bold;
@media (max-width: 768px) {
font-size: 23px;
}
`;
export const BalanceHeaderFiat = styled.div`
color: ${props => props.theme.wallet.text.secondary};
width: 100%;
font-size: 18px;
margin-bottom: 20px;
font-weight: bold;
@media (max-width: 768px) {
font-size: 16px;
}
`;
export const ZeroBalanceHeader = styled.div`
color: ${props => props.theme.wallet.text.primary};
width: 100%;
font-size: 14px;
margin-bottom: 5px;
`;
export const TokenParamLabel = styled.span`
font-weight: bold;
`;
export const AlertMsg = styled.p`
color: ${props => props.theme.forms.error};
`;
+
+export const ConvertAmount = styled.div`
+ color: ${props => props.theme.wallet.text.secondary};
+ width: 100%;
+ font-size: 14px;
+ margin-bottom: 10px;
+ font-weight: bold;
+ @media (max-width: 768px) {
+ font-size: 12px;
+ }
+`;
diff --git a/web/cashtab/src/components/Send/Send.js b/web/cashtab/src/components/Send/Send.js
index 3a8d4fdba..0ec5f9105 100644
--- a/web/cashtab/src/components/Send/Send.js
+++ b/web/cashtab/src/components/Send/Send.js
@@ -1,573 +1,528 @@
import React, { useState, useEffect } from 'react';
-import styled from 'styled-components';
import { WalletContext } from '@utils/context';
import { Form, notification, message, Spin, Modal, Alert } from 'antd';
import { CashLoader, CashLoadingIcon } from '@components/Common/CustomIcons';
import { Row, Col } from 'antd';
import Paragraph from 'antd/lib/typography/Paragraph';
import PrimaryButton, {
SecondaryButton,
} from '@components/Common/PrimaryButton';
import {
SendBchInput,
FormItemWithQRCodeAddon,
} from '@components/Common/EnhancedInputs';
import useBCH from '@hooks/useBCH';
import useWindowDimensions from '@hooks/useWindowDimensions';
import { isMobile, isIOS, isSafari } from 'react-device-detect';
import {
currency,
isValidTokenPrefix,
parseAddress,
toLegacy,
} from '@components/Common/Ticker.js';
import { Event } from '@utils/GoogleAnalytics';
import { fiatToCrypto, shouldRejectAmountInput } from '@utils/validation';
import { formatBalance } from '@utils/cashMethods';
-
-export const BalanceHeader = styled.div`
- p {
- color: ${props => props.theme.wallet.text.secondary}
- width: 100%;
- font-size: 14px;
- margin-bottom: 0px;
- }
-
- h3 {
- color: ${props => props.theme.wallet.text.primary};
- width: 100%;
- font-size: 26px;
- font-weight: bold;
- margin-bottom: 0px;
- }
-`;
-
-export const BalanceHeaderFiat = styled.div`
- color: ${props => props.theme.wallet.text.secondary};
- width: 100%;
- font-size: 18px;
- margin-bottom: 20px;
- font-weight: bold;
- @media (max-width: 768px) {
- font-size: 16px;
- }
-`;
-
-export const ZeroBalanceHeader = styled.div`
- color: ${props => props.theme.wallet.text.secondary};
- width: 100%;
- font-size: 14px;
- margin-bottom: 20px;
-`;
-
-const ConvertAmount = styled.div`
- color: ${props => props.theme.wallet.text.secondary};
- width: 100%;
- font-size: 14px;
- margin-bottom: 10px;
- font-weight: bold;
- @media (max-width: 768px) {
- font-size: 12px;
- }
-`;
+import {
+ BalanceHeader,
+ BalanceHeaderFiat,
+ ZeroBalanceHeader,
+ ConvertAmount,
+} from '@components/Common/Atoms';
// Note jestBCH is only used for unit tests; BCHJS must be mocked for jest
const SendBCH = ({ jestBCH, filledAddress, callbackTxId }) => {
// use balance parameters from wallet.state object and not legacy balances parameter from walletState, if user has migrated wallet
// this handles edge case of user with old wallet who has not opened latest Cashtab version yet
// If the wallet object from ContextValue has a `state key`, then check which keys are in the wallet object
// Else set it as blank
const ContextValue = React.useContext(WalletContext);
const { wallet, fiatPrice, slpBalancesAndUtxos, apiError } = ContextValue;
let balances;
const paramsInWalletState = wallet.state ? Object.keys(wallet.state) : [];
// If wallet.state includes balances and parsedTxHistory params, use these
// These are saved in indexedDb in the latest version of the app, hence accessible more quickly
if (paramsInWalletState.includes('balances')) {
balances = wallet.state.balances;
} else {
// If balances and parsedTxHistory are not in the wallet.state object, load them from Context
// This is how the app used to work
balances = ContextValue.balances;
}
// Get device window width
// If this is less than 769, the page will open with QR scanner open
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 [formData, setFormData] = useState({
dirty: true,
value: '',
address: filledAddress || '',
});
const [loading, setLoading] = useState(false);
const [queryStringText, setQueryStringText] = useState(null);
const [sendBchAddressError, setSendBchAddressError] = useState(false);
const [sendBchAmountError, setSendBchAmountError] = useState(false);
const [selectedCurrency, setSelectedCurrency] = useState(currency.ticker);
// Support cashtab button from web pages
const [txInfoFromUrl, setTxInfoFromUrl] = useState(false);
// Show a confirmation modal on transactions created by populating form from web page button
const [isModalVisible, setIsModalVisible] = useState(false);
const showModal = () => {
setIsModalVisible(true);
};
const handleOk = () => {
setIsModalVisible(false);
submit();
};
const handleCancel = () => {
setIsModalVisible(false);
};
const { getBCH, getRestUrl, sendBch, calcFee } = useBCH();
// jestBCH is only ever specified for unit tests, otherwise app will use getBCH();
const BCH = jestBCH ? jestBCH : getBCH();
// 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
useEffect(() => {
setLoading(false);
}, [balances.totalBalance]);
useEffect(() => {
// Manually parse for txInfo object on page load when Send.js is loaded with a query string
// Do not set txInfo in state if query strings are not present
if (
!window.location ||
!window.location.hash ||
window.location.hash === '#/send'
) {
return;
}
const txInfoArr = window.location.hash.split('?')[1].split('&');
// Iterate over this to create object
const txInfo = {};
for (let i = 0; i < txInfoArr.length; i += 1) {
let txInfoKeyValue = txInfoArr[i].split('=');
let key = txInfoKeyValue[0];
let value = txInfoKeyValue[1];
txInfo[key] = value;
}
console.log(`txInfo from page params`, txInfo);
setTxInfoFromUrl(txInfo);
populateFormsFromUrl(txInfo);
}, []);
function populateFormsFromUrl(txInfo) {
if (txInfo && txInfo.address && txInfo.value) {
setFormData({
address: txInfo.address,
value: txInfo.value,
});
}
}
async function submit() {
setFormData({
...formData,
dirty: false,
});
if (
!formData.address ||
!formData.value ||
Number(formData.value) <= 0
) {
return;
}
// Event("Category", "Action", "Label")
// Track number of BCHA send transactions and whether users
// are sending BCHA or USD
Event('Send.js', 'Send', selectedCurrency);
setLoading(true);
const { address, value } = formData;
// Get the param-free address
let cleanAddress = address.split('?')[0];
// Ensure address has bitcoincash: prefix and checksum
cleanAddress = toLegacy(cleanAddress);
let hasValidCashPrefix;
try {
hasValidCashPrefix = cleanAddress.startsWith(
currency.legacyPrefix + ':',
);
} catch (err) {
hasValidCashPrefix = false;
console.log(`toLegacy() returned an error:`, cleanAddress);
}
if (!hasValidCashPrefix) {
// set loading to false and set address validation to false
// Now that the no-prefix case is handled, this happens when user tries to send
// BCHA to an SLPA address
setLoading(false);
setSendBchAddressError(
`Destination is not a valid ${currency.ticker} address`,
);
return;
}
// Calculate the amount in BCH
let bchValue = value;
if (selectedCurrency === 'USD') {
bchValue = fiatToCrypto(value, fiatPrice);
}
try {
const link = await sendBch(
BCH,
wallet,
slpBalancesAndUtxos.nonSlpUtxos,
filledAddress || cleanAddress,
bchValue,
currency.defaultFee,
callbackTxId,
);
notification.success({
message: 'Success',
description: (
Are you sure you want to send {formData.value}{' '}
{currency.ticker} to {formData.address}?
Available balance Available balance
Deposit some funds to use this feature
- {formatBalance(balances.totalBalance)}{' '}
- {currency.ticker}
-
+ {formatBalance(balances.totalBalance)} {currency.ticker}
- {formatBalance(token.balance)}{' '}
- {token.info.tokenTicker}
-
+ {formatBalance(token.balance)} {token.info.tokenTicker}
- Available balance -
-- Available balance -
-- Available balance -
-- Available balance -
-- Available balance -
-- Available balance -
-- Available balance -
-
Cashtab is an
open source,
non-custodial web wallet for
Bitcoin ABC
.
Want to learn more?
Check out the Cashtab documentation.