Changeset View
Changeset View
Standalone View
Standalone View
web/cashtab/src/components/Home/Tx.js
import React from 'react'; | import React, { useState, useEffect } from 'react'; | ||||
import { Link } from 'react-router-dom'; | import { Link } from 'react-router-dom'; | ||||
import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||
import styled from 'styled-components'; | import styled from 'styled-components'; | ||||
import { | import { | ||||
SendIcon, | SendIcon, | ||||
ReceiveIcon, | ReceiveIcon, | ||||
GenesisIcon, | GenesisIcon, | ||||
UnparsedIcon, | UnparsedIcon, | ||||
ThemedContactsOutlined, | ThemedContactsOutlined, | ||||
AirdropIcon, | |||||
} from 'components/Common/CustomIcons'; | } from 'components/Common/CustomIcons'; | ||||
import { currency } from 'components/Common/Ticker'; | import { currency } from 'components/Common/Ticker'; | ||||
import { fromLegacyDecimals } from 'utils/cashMethods'; | import { fromLegacyDecimals } from 'utils/cashMethods'; | ||||
import { formatBalance, formatDate } from 'utils/formatting'; | import { formatBalance, formatDate } from 'utils/formatting'; | ||||
import TokenIcon from 'components/Tokens/TokenIcon'; | import TokenIcon from 'components/Tokens/TokenIcon'; | ||||
import { Collapse } from 'antd'; | import { Collapse } from 'antd'; | ||||
import CopyToClipboard from 'components/Common/CopyToClipboard'; | import CopyToClipboard from 'components/Common/CopyToClipboard'; | ||||
import { | import { | ||||
ThemedCopySolid, | ThemedCopySolid, | ||||
ThemedLinkSolid, | ThemedLinkSolid, | ||||
ThemedPdfSolid, | ThemedPdfSolid, | ||||
} from 'components/Common/CustomIcons'; | } from 'components/Common/CustomIcons'; | ||||
import useBCH from 'hooks/useBCH'; | |||||
const TxIcon = styled.div` | const TxIcon = styled.div` | ||||
svg { | svg { | ||||
width: 20px; | width: 20px; | ||||
height: 20px; | height: 20px; | ||||
} | } | ||||
height: 40px; | height: 40px; | ||||
width: 40px; | width: 40px; | ||||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Lines | h3 { | ||||
margin: 0; | margin: 0; | ||||
} | } | ||||
.genesis { | .genesis { | ||||
color: ${props => props.theme.genesisGreen}; | color: ${props => props.theme.genesisGreen}; | ||||
} | } | ||||
.received { | .received { | ||||
color: ${props => props.theme.eCashBlue}; | color: ${props => props.theme.eCashBlue}; | ||||
} | } | ||||
.airdrop { | |||||
margin-right: 12px; | |||||
} | |||||
h4 { | h4 { | ||||
font-size: 12px; | font-size: 12px; | ||||
color: ${props => props.theme.lightWhite}; | color: ${props => props.theme.lightWhite}; | ||||
margin: 0; | margin: 0; | ||||
} | } | ||||
`; | `; | ||||
const RightTextCtn = styled.div` | const RightTextCtn = styled.div` | ||||
▲ Show 20 Lines • Show All 247 Lines • ▼ Show 20 Lines | |||||
const Tx = ({ | const Tx = ({ | ||||
data, | data, | ||||
fiatPrice, | fiatPrice, | ||||
fiatCurrency, | fiatCurrency, | ||||
addressesInContactList, | addressesInContactList, | ||||
contactList, | contactList, | ||||
}) => { | }) => { | ||||
const [tokenStats, setTokenStats] = useState(null); | |||||
const txDate = | const txDate = | ||||
typeof data.blocktime === 'undefined' | typeof data.blocktime === 'undefined' | ||||
? formatDate() | ? formatDate() | ||||
: formatDate(data.blocktime, navigator.language); | : formatDate(data.blocktime, navigator.language); | ||||
// if data only includes height and txid, then the tx could not be parsed by cashtab | // if data only includes height and txid, then the tx could not be parsed by cashtab | ||||
// render as such but keep link to block explorer | // render as such but keep link to block explorer | ||||
let unparsedTx = false; | let unparsedTx = false; | ||||
if (!Object.keys(data).includes('outgoingTx')) { | if (!Object.keys(data).includes('outgoingTx')) { | ||||
unparsedTx = true; | unparsedTx = true; | ||||
} | } | ||||
const { getBCH, getTokenStats } = useBCH(); | |||||
useEffect(() => { | |||||
bytesofman: While this approach "works" -- it's not the best way forward. This will require multiple API… | |||||
const fetchTokenStats = async () => { | |||||
if (data.airdropFlag && data.airdropTokenId) { | |||||
const BCH = getBCH(); | |||||
const result = await getTokenStats(BCH, data.airdropTokenId); | |||||
setTokenStats(result); | |||||
} | |||||
}; | |||||
fetchTokenStats(); | |||||
}, []); | |||||
return ( | return ( | ||||
<> | <> | ||||
{unparsedTx ? ( | {unparsedTx ? ( | ||||
<TxWrapper> | <TxWrapper> | ||||
<UnparsedTx> | <UnparsedTx> | ||||
<UnparsedIcon /> | <UnparsedIcon /> | ||||
</UnparsedTx> | </UnparsedTx> | ||||
Show All 18 Lines | return ( | ||||
data.tokenInfo | data.tokenInfo | ||||
.transactionType === | .transactionType === | ||||
'GENESIS' ? ( | 'GENESIS' ? ( | ||||
<GenesisTx> | <GenesisTx> | ||||
<GenesisIcon /> | <GenesisIcon /> | ||||
</GenesisTx> | </GenesisTx> | ||||
) : ( | ) : ( | ||||
<SentTx> | <SentTx> | ||||
{data.airdropFlag ? ( | |||||
<AirdropIcon /> | |||||
) : ( | |||||
<SendIcon /> | <SendIcon /> | ||||
)} | |||||
</SentTx> | </SentTx> | ||||
)} | )} | ||||
</> | </> | ||||
) : ( | ) : ( | ||||
<ReceivedTx> | <ReceivedTx> | ||||
{data.airdropFlag ? ( | |||||
<AirdropIcon /> | |||||
) : ( | |||||
<ReceiveIcon /> | <ReceiveIcon /> | ||||
)} | |||||
</ReceivedTx> | </ReceivedTx> | ||||
)} | )} | ||||
<LeftTextCtn> | <LeftTextCtn> | ||||
{data.outgoingTx ? ( | {data.outgoingTx ? ( | ||||
<> | <> | ||||
{data.tokenTx && | {data.tokenTx && | ||||
data.tokenInfo | data.tokenInfo | ||||
.transactionType === | .transactionType === | ||||
'GENESIS' ? ( | 'GENESIS' ? ( | ||||
<h3 className="genesis"> | <h3 className="genesis"> | ||||
Genesis | Genesis | ||||
</h3> | </h3> | ||||
) : ( | ) : ( | ||||
<> | |||||
{data.airdropFlag ? ( | |||||
<h3 className="airdrop sent"> | |||||
bytesofmanUnsubmitted Not Done Inline Actionsshould not be using className anywhere in the app except where it is unavoidable. Is there an open task to remove className from this component? If not, please create. bytesofman: should not be using className anywhere in the app except where it is unavoidable. Is there an… | |||||
Airdrop | |||||
</h3> | |||||
) : ( | |||||
<h3 className="sent"> | <h3 className="sent"> | ||||
Sent | Sent | ||||
</h3> | </h3> | ||||
)} | )} | ||||
</> | </> | ||||
)} | |||||
</> | |||||
) : ( | ) : ( | ||||
<ReceivedFromCtn> | <ReceivedFromCtn> | ||||
<> | |||||
{data.airdropFlag ? ( | |||||
<h3 className="airdrop received"> | |||||
Airdrop | |||||
</h3> | |||||
) : ( | |||||
<h3 className="received"> | <h3 className="received"> | ||||
Received | Received | ||||
</h3> | </h3> | ||||
)} | |||||
</> | |||||
{addressesInContactList.includes( | {addressesInContactList.includes( | ||||
data.replyAddress, | data.replyAddress, | ||||
) && ( | ) && ( | ||||
<> | <> | ||||
<h4>from</h4> | <h4>from</h4> | ||||
{contactList.map( | {contactList.map( | ||||
( | ( | ||||
contact, | contact, | ||||
Show All 26 Lines | return ( | ||||
}, | }, | ||||
)} | )} | ||||
</> | </> | ||||
)} | )} | ||||
</ReceivedFromCtn> | </ReceivedFromCtn> | ||||
)} | )} | ||||
<h4>{txDate}</h4> | <h4>{txDate}</h4> | ||||
</LeftTextCtn> | </LeftTextCtn> | ||||
<> | |||||
{data.airdropFlag ? ( | |||||
<> | |||||
<TokenInfo> | |||||
<TxTokenIcon> | |||||
<TokenIcon | |||||
size={32} | |||||
tokenId={ | |||||
data.airdropTokenId | |||||
} | |||||
/> | |||||
</TxTokenIcon> | |||||
<RightTextCtn> | |||||
{!data.outgoingTx ? ( | |||||
<TokenTxAmtReceived> | |||||
+{' '} | |||||
{formatBalance( | |||||
fromLegacyDecimals( | |||||
data.amountReceived, | |||||
), | |||||
)} | |||||
| |||||
{ | |||||
currency.ticker | |||||
} | |||||
{tokenStats && ( | |||||
<TokenName> | |||||
{ | |||||
tokenStats.name | |||||
} | |||||
</TokenName> | |||||
)} | |||||
</TokenTxAmtReceived> | |||||
) : ( | |||||
<TokenTxAmt> | |||||
-{' '} | |||||
{formatBalance( | |||||
fromLegacyDecimals( | |||||
data.amountSent, | |||||
), | |||||
)} | |||||
| |||||
{ | |||||
currency.ticker | |||||
} | |||||
{tokenStats && ( | |||||
<TokenName> | |||||
{ | |||||
tokenStats.name | |||||
} | |||||
</TokenName> | |||||
)} | |||||
</TokenTxAmt> | |||||
)} | |||||
</RightTextCtn> | |||||
</TokenInfo> | |||||
</> | |||||
) : ( | |||||
<> | |||||
{' '} | |||||
{data.tokenTx ? ( | {data.tokenTx ? ( | ||||
<TokenInfo | <TokenInfo | ||||
outgoing={data.outgoingTx} | outgoing={ | ||||
data.outgoingTx | |||||
} | |||||
> | > | ||||
{data.tokenTx && | {data.tokenTx && | ||||
data.tokenInfo ? ( | data.tokenInfo ? ( | ||||
<> | <> | ||||
<TxTokenIcon> | <TxTokenIcon> | ||||
<TokenIcon | <TokenIcon | ||||
size={32} | size={ | ||||
32 | |||||
} | |||||
tokenId={ | tokenId={ | ||||
data | data | ||||
.tokenInfo | .tokenInfo | ||||
.tokenId | .tokenId | ||||
} | } | ||||
/> | /> | ||||
</TxTokenIcon> | </TxTokenIcon> | ||||
{data.outgoingTx ? ( | {data.outgoingTx ? ( | ||||
<RightTextCtn> | <RightTextCtn> | ||||
{data.tokenInfo | {data | ||||
.tokenInfo | |||||
.transactionType === | .transactionType === | ||||
'GENESIS' ? ( | 'GENESIS' ? ( | ||||
<> | <> | ||||
<TokenTxAmtGenesis> | <TokenTxAmtGenesis> | ||||
+{' '} | +{' '} | ||||
{data.tokenInfo.qtyReceived.toString()} | {data.tokenInfo.qtyReceived.toString()} | ||||
| | ||||
{ | { | ||||
data | data | ||||
.tokenInfo | .tokenInfo | ||||
.tokenTicker | .tokenTicker | ||||
} | } | ||||
</TokenTxAmtGenesis> | </TokenTxAmtGenesis> | ||||
<TokenName> | <TokenName> | ||||
{ | { | ||||
data | data | ||||
.tokenInfo | .tokenInfo | ||||
.tokenName | .tokenName | ||||
} | } | ||||
</TokenName> | </TokenName> | ||||
</> | </> | ||||
) : ( | ) : ( | ||||
<> | <> | ||||
<TokenTxAmt> | <TokenTxAmt> | ||||
-{' '} | -{' '} | ||||
{data.tokenInfo.qtySent.toString()} | {data.tokenInfo.qtySent.toString()} | ||||
| | ||||
{ | { | ||||
data | data | ||||
.tokenInfo | .tokenInfo | ||||
.tokenTicker | .tokenTicker | ||||
} | } | ||||
</TokenTxAmt> | </TokenTxAmt> | ||||
<TokenName> | <TokenName> | ||||
{ | { | ||||
data | data | ||||
.tokenInfo | .tokenInfo | ||||
.tokenName | .tokenName | ||||
} | } | ||||
</TokenName> | </TokenName> | ||||
</> | </> | ||||
)} | )} | ||||
</RightTextCtn> | </RightTextCtn> | ||||
) : ( | ) : ( | ||||
<RightTextCtn> | <RightTextCtn> | ||||
<TokenTxAmtReceived> | <TokenTxAmtReceived> | ||||
+{' '} | +{' '} | ||||
{data.tokenInfo.qtyReceived.toString()} | {data.tokenInfo.qtyReceived.toString()} | ||||
| | ||||
{ | { | ||||
data | data | ||||
.tokenInfo | .tokenInfo | ||||
.tokenTicker | .tokenTicker | ||||
} | } | ||||
</TokenTxAmtReceived> | </TokenTxAmtReceived> | ||||
<TokenName> | <TokenName> | ||||
{ | { | ||||
data | data | ||||
.tokenInfo | .tokenInfo | ||||
.tokenName | .tokenName | ||||
} | } | ||||
</TokenName> | </TokenName> | ||||
</RightTextCtn> | </RightTextCtn> | ||||
)} | )} | ||||
</> | </> | ||||
) : ( | ) : ( | ||||
<span>Token Tx</span> | <span> | ||||
Token Tx | |||||
</span> | |||||
)} | )} | ||||
</TokenInfo> | </TokenInfo> | ||||
) : ( | ) : ( | ||||
<> | <> | ||||
<TxInfo | <TxInfo | ||||
outgoing={data.outgoingTx} | outgoing={ | ||||
data.outgoingTx | |||||
} | |||||
> | > | ||||
{data.outgoingTx ? ( | {data.outgoingTx ? ( | ||||
<> | <> | ||||
<h3> | <h3> | ||||
- | - | ||||
{formatBalance( | {formatBalance( | ||||
fromLegacyDecimals( | fromLegacyDecimals( | ||||
data.amountSent, | data.amountSent, | ||||
), | ), | ||||
)}{' '} | )}{' '} | ||||
{ | { | ||||
currency.ticker | currency.ticker | ||||
} | } | ||||
</h3> | </h3> | ||||
{fiatPrice !== | {fiatPrice !== | ||||
null && | null && | ||||
!isNaN( | !isNaN( | ||||
data.amountSent, | data.amountSent, | ||||
) && ( | ) && ( | ||||
<h4> | <h4> | ||||
- | - | ||||
{ | { | ||||
currency | currency | ||||
.fiatCurrencies[ | .fiatCurrencies[ | ||||
fiatCurrency | fiatCurrency | ||||
] | ] | ||||
.symbol | .symbol | ||||
} | } | ||||
{( | {( | ||||
fromLegacyDecimals( | fromLegacyDecimals( | ||||
data.amountSent, | data.amountSent, | ||||
) * | ) * | ||||
fiatPrice | fiatPrice | ||||
).toFixed( | ).toFixed( | ||||
2, | 2, | ||||
)}{' '} | )}{' '} | ||||
{ | { | ||||
currency | currency | ||||
.fiatCurrencies | .fiatCurrencies | ||||
.fiatCurrency | .fiatCurrency | ||||
} | } | ||||
</h4> | </h4> | ||||
)} | )} | ||||
</> | </> | ||||
) : ( | ) : ( | ||||
<> | <> | ||||
<TokenTxAmtReceived> | <TokenTxAmtReceived> | ||||
+ | + | ||||
{formatBalance( | {formatBalance( | ||||
fromLegacyDecimals( | fromLegacyDecimals( | ||||
data.amountReceived, | data.amountReceived, | ||||
), | ), | ||||
)}{' '} | )}{' '} | ||||
{ | { | ||||
currency.ticker | currency.ticker | ||||
} | } | ||||
</TokenTxAmtReceived> | </TokenTxAmtReceived> | ||||
{fiatPrice !== | {fiatPrice !== | ||||
null && | null && | ||||
!isNaN( | !isNaN( | ||||
data.amountReceived, | data.amountReceived, | ||||
) && ( | ) && ( | ||||
<h4> | <h4> | ||||
+ | + | ||||
{ | { | ||||
currency | currency | ||||
.fiatCurrencies[ | .fiatCurrencies[ | ||||
fiatCurrency | fiatCurrency | ||||
] | ] | ||||
.symbol | .symbol | ||||
} | } | ||||
{( | {( | ||||
fromLegacyDecimals( | fromLegacyDecimals( | ||||
data.amountReceived, | data.amountReceived, | ||||
) * | ) * | ||||
fiatPrice | fiatPrice | ||||
).toFixed( | ).toFixed( | ||||
2, | 2, | ||||
)}{' '} | )}{' '} | ||||
{ | { | ||||
currency | currency | ||||
.fiatCurrencies | .fiatCurrencies | ||||
.fiatCurrency | .fiatCurrency | ||||
} | } | ||||
</h4> | </h4> | ||||
)} | )} | ||||
</> | </> | ||||
)} | )} | ||||
</TxInfo> | </TxInfo> | ||||
</> | </> | ||||
)} | )} | ||||
</> | |||||
)} | |||||
</> | |||||
{data.opReturnMessage && ( | {data.opReturnMessage && ( | ||||
<> | <> | ||||
<OpReturnType | <OpReturnType | ||||
received={!data.outgoingTx} | received={!data.outgoingTx} | ||||
> | > | ||||
{!data.outgoingTx && | {!data.outgoingTx && | ||||
!addressesInContactList.includes( | !addressesInContactList.includes( | ||||
data.replyAddress, | data.replyAddress, | ||||
▲ Show 20 Lines • Show All 193 Lines • Show Last 20 Lines |
While this approach "works" -- it's not the best way forward. This will require multiple API calls every time the app loads.
Instead, we want to follow the token info approach used for eToken txs. In this approach, the token info is added to the tx data object when the tx history is batch processed. Then it is stored in cache, making future API calls unnecessary.
You'll need to modify some functions to get the tokenInfo available in the data object that comes in as a prop to this component.
Start with addTokenTxDataToSingleTx in useBCH.js. Let me know if you have any questions.