Changeset View
Changeset View
Standalone View
Standalone View
web/cashtab/src/components/Send/SendToken.js
Show All 18 Lines | |||||
import makeBlockie from 'ethereum-blockies-base64'; | import makeBlockie from 'ethereum-blockies-base64'; | ||||
import BigNumber from 'bignumber.js'; | import BigNumber from 'bignumber.js'; | ||||
import { | import { | ||||
currency, | currency, | ||||
parseAddress, | parseAddress, | ||||
isValidTokenPrefix, | isValidTokenPrefix, | ||||
} from '@components/Common/Ticker.js'; | } from '@components/Common/Ticker.js'; | ||||
import { Event } from '@utils/GoogleAnalytics'; | import { Event } from '@utils/GoogleAnalytics'; | ||||
import { formatBalance } from '@utils/cashMethods'; | import { formatBalance, isValidStoredWallet } from '@utils/cashMethods'; | ||||
const SendToken = ({ tokenId }) => { | const SendToken = ({ tokenId, jestBCH }) => { | ||||
const { wallet, tokens, slpBalancesAndUtxos, apiError } = React.useContext( | const { wallet, tokens, slpBalancesAndUtxos, apiError } = React.useContext( | ||||
WalletContext, | WalletContext, | ||||
); | ); | ||||
const token = tokens.find(token => token.tokenId === tokenId); | // If this wallet has migrated to latest storage structure, get token info from there | ||||
// If not, use the tokens object (unless it's undefined, in which case use an empty array) | |||||
const liveTokenState = | |||||
isValidStoredWallet(wallet) && wallet.state.tokens | |||||
? wallet.state.tokens | |||||
: tokens | |||||
? tokens | |||||
: []; | |||||
const token = liveTokenState.find(token => token.tokenId === tokenId); | |||||
const [queryStringText, setQueryStringText] = useState(null); | const [queryStringText, setQueryStringText] = useState(null); | ||||
const [sendTokenAddressError, setSendTokenAddressError] = useState(false); | const [sendTokenAddressError, setSendTokenAddressError] = useState(false); | ||||
const [sendTokenAmountError, setSendTokenAmountError] = useState(false); | const [sendTokenAmountError, setSendTokenAmountError] = useState(false); | ||||
// Get device window width | // Get device window width | ||||
// If this is less than 769, the page will open with QR scanner open | // If this is less than 769, the page will open with QR scanner open | ||||
const { width } = useWindowDimensions(); | const { width } = useWindowDimensions(); | ||||
// Load with QR code open if device is mobile and NOT iOS + anything but safari | // Load with QR code open if device is mobile and NOT iOS + anything but safari | ||||
const scannerSupported = width < 769 && isMobile && !(isIOS && !isSafari); | const scannerSupported = width < 769 && isMobile && !(isIOS && !isSafari); | ||||
const [formData, setFormData] = useState({ | const [formData, setFormData] = useState({ | ||||
dirty: true, | dirty: true, | ||||
value: '', | value: '', | ||||
address: '', | address: '', | ||||
}); | }); | ||||
const [loading, setLoading] = useState(false); | const [loading, setLoading] = useState(false); | ||||
const { getBCH, getRestUrl, sendToken } = useBCH(); | const { getBCH, getRestUrl, sendToken } = useBCH(); | ||||
const BCH = getBCH(); | // jestBCH is only ever specified for unit tests, otherwise app will use getBCH(); | ||||
const BCH = jestBCH ? jestBCH : getBCH(); | |||||
// Keep this function around for re-enabling later | // Keep this function around for re-enabling later | ||||
// eslint-disable-next-line no-unused-vars | // eslint-disable-next-line no-unused-vars | ||||
async function submit() { | async function submit() { | ||||
setFormData({ | setFormData({ | ||||
...formData, | ...formData, | ||||
dirty: false, | dirty: false, | ||||
}); | }); | ||||
▲ Show 20 Lines • Show All 82 Lines • ▼ Show 20 Lines | const handleSlpAmountChange = e => { | ||||
} else if (token && token.balance && isGreaterThanBalance === 1) { | } else if (token && token.balance && isGreaterThanBalance === 1) { | ||||
error = `Amount cannot exceed your ${token.info.tokenTicker} balance of ${token.balance}`; | error = `Amount cannot exceed your ${token.info.tokenTicker} balance of ${token.balance}`; | ||||
} else if (!isNaN(value) && value.toString().includes('.')) { | } else if (!isNaN(value) && value.toString().includes('.')) { | ||||
if (value.toString().split('.')[1].length > token.info.decimals) { | if (value.toString().split('.')[1].length > token.info.decimals) { | ||||
error = `This token only supports ${token.info.decimals} decimal places`; | error = `This token only supports ${token.info.decimals} decimal places`; | ||||
} | } | ||||
} | } | ||||
setSendTokenAmountError(error); | setSendTokenAmountError(error); | ||||
setFormData(p => ({ ...p, [name]: value })); | setFormData(p => ({ | ||||
...p, | |||||
[name]: value, | |||||
})); | |||||
}; | }; | ||||
const handleTokenAddressChange = e => { | const handleTokenAddressChange = e => { | ||||
const { value, name } = e.target; | const { value, name } = e.target; | ||||
// validate for token address | // validate for token address | ||||
// validate for parameters | // validate for parameters | ||||
// show warning that query strings are not supported | // show warning that query strings are not supported | ||||
▲ Show 20 Lines • Show All 72 Lines • ▼ Show 20 Lines | return ( | ||||
{formatBalance(token.balance)}{' '} | {formatBalance(token.balance)}{' '} | ||||
{token.info.tokenTicker} | {token.info.tokenTicker} | ||||
</h3> | </h3> | ||||
</BalanceHeader> | </BalanceHeader> | ||||
<Row type="flex"> | <Row type="flex"> | ||||
<Col span={24}> | <Col span={24}> | ||||
<Spin | <Spin | ||||
style={{ color: 'red' }} | style={{ | ||||
color: 'red', | |||||
}} | |||||
spinning={loading} | spinning={loading} | ||||
indicator={CashLoadingIcon} | indicator={CashLoadingIcon} | ||||
> | > | ||||
<Form style={{ width: 'auto' }}> | <Form | ||||
style={{ | |||||
width: 'auto', | |||||
}} | |||||
> | |||||
<FormItemWithQRCodeAddon | <FormItemWithQRCodeAddon | ||||
loadWithCameraOpen={scannerSupported} | loadWithCameraOpen={scannerSupported} | ||||
validateStatus={ | validateStatus={ | ||||
sendTokenAddressError ? 'error' : '' | sendTokenAddressError ? 'error' : '' | ||||
} | } | ||||
help={ | help={ | ||||
sendTokenAddressError | sendTokenAddressError | ||||
? sendTokenAddressError | ? sendTokenAddressError | ||||
▲ Show 20 Lines • Show All 69 Lines • ▼ Show 20 Lines | return ( | ||||
), | ), | ||||
suffix: token.info.tokenTicker, | suffix: token.info.tokenTicker, | ||||
onChange: e => | onChange: e => | ||||
handleSlpAmountChange(e), | handleSlpAmountChange(e), | ||||
required: true, | required: true, | ||||
value: formData.value, | value: formData.value, | ||||
}} | }} | ||||
/> | /> | ||||
<div style={{ paddingTop: '12px' }}> | <div | ||||
style={{ | |||||
paddingTop: '12px', | |||||
}} | |||||
> | |||||
{apiError || | {apiError || | ||||
sendTokenAmountError || | sendTokenAmountError || | ||||
sendTokenAddressError ? ( | sendTokenAddressError ? ( | ||||
<> | <> | ||||
<SecondaryButton> | <SecondaryButton> | ||||
Send {token.info.tokenName} | Send {token.info.tokenName} | ||||
</SecondaryButton> | </SecondaryButton> | ||||
{apiError && <CashLoader />} | {apiError && <CashLoader />} | ||||
Show All 9 Lines | return ( | ||||
{queryStringText && ( | {queryStringText && ( | ||||
<Alert | <Alert | ||||
message={`You are sending a transaction to an address including query parameters "${queryStringText}." Token transactions do not support query parameters and they will be ignored.`} | message={`You are sending a transaction to an address including query parameters "${queryStringText}." Token transactions do not support query parameters and they will be ignored.`} | ||||
type="warning" | type="warning" | ||||
/> | /> | ||||
)} | )} | ||||
{apiError && ( | {apiError && ( | ||||
<p style={{ color: 'red' }}> | <p | ||||
style={{ | |||||
color: 'red', | |||||
}} | |||||
> | |||||
<b> | <b> | ||||
An error occured on our end. | An error occured on our end. | ||||
Reconnecting... | Reconnecting... | ||||
</b> | </b> | ||||
</p> | </p> | ||||
)} | )} | ||||
</Form> | </Form> | ||||
</Spin> | </Spin> | ||||
Show All 9 Lines |