From 9e35f0dead06c8b9776babee6adca6eb2a3b349a Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Fri, 12 Jan 2024 20:38:06 -0300 Subject: [PATCH 1/9] refactor: wallet screen to functional component --- src/screens/Wallet.js | 674 ++++++++++++++++++++---------------------- 1 file changed, 321 insertions(+), 353 deletions(-) diff --git a/src/screens/Wallet.js b/src/screens/Wallet.js index f7f2ced79..8bff3ffed 100644 --- a/src/screens/Wallet.js +++ b/src/screens/Wallet.js @@ -5,11 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -import React from 'react'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import hathorLib from '@hathor/wallet-lib'; import { t } from 'ttag'; import { get } from 'lodash'; -import { connect } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import ReactLoading from 'react-loading'; import SpanFmt from '../components/SpanFmt'; @@ -19,41 +19,16 @@ import WalletAddress from '../components/WalletAddress'; import TokenGeneralInfo from '../components/TokenGeneralInfo'; import TokenAdministrative from '../components/TokenAdministrative'; import HathorAlert from '../components/HathorAlert'; -import tokens from '../utils/tokens'; +import tokensUtils from '../utils/tokens'; import version from '../utils/version'; -import wallet from '../utils/wallet'; +import walletUtils from '../utils/wallet'; import BackButton from '../components/BackButton'; import colors from '../index.scss'; import { TOKEN_DOWNLOAD_STATUS } from '../sagas/tokens'; import { GlobalModalContext, MODAL_TYPES } from '../components/GlobalModal'; -import { - updateWords, - tokenFetchHistoryRequested, - tokenFetchBalanceRequested, -} from '../actions/index'; +import { tokenFetchBalanceRequested, tokenFetchHistoryRequested, updateWords, } from '../actions/index'; import LOCAL_STORE from '../storage'; - - -const mapDispatchToProps = dispatch => { - return { - updateWords: (data) => dispatch(updateWords(data)), - getHistory: (tokenId) => dispatch(tokenFetchHistoryRequested(tokenId)), - getBalance: (tokenId) => dispatch(tokenFetchBalanceRequested(tokenId)), - }; -}; - -const mapStateToProps = (state) => { - return { - selectedToken: state.selectedToken, - tokensHistory: state.tokensHistory, - tokensBalance: state.tokensBalance, - tokenMetadata: state.tokenMetadata || {}, - tokens: state.tokens, - wallet: state.wallet, - walletState: state.walletState, - useWalletService: state.useWalletService, - }; -}; +import { useHistory } from "react-router-dom"; /** @@ -61,135 +36,140 @@ const mapStateToProps = (state) => { * * @memberof Screens */ -class Wallet extends React.Component { - static contextType = GlobalModalContext; - - constructor(props) { - super(props); - - this.alertSuccessRef = React.createRef(); - } - - /** - * backupDone {boolean} if words backup was already done - * successMessage {string} Message to be shown on alert success - * shouldShowAdministrativeTab {boolean} If we should display the Administrative Tools tab - */ - state = { - backupDone: true, - successMessage: '', - hasTokenSignature: false, - shouldShowAdministrativeTab: false, - totalSupply: null, - canMint: false, - canMelt: false, - transactionsCount: null, - mintCount: null, - meltCount: null, - }; - - // Reference for the unregister confirm modal - unregisterModalRef = React.createRef(); - - componentDidMount = async () => { - this.setState({ - backupDone: LOCAL_STORE.isBackupDone() - }); - - this.initializeWalletScreen(); - } - - componentDidUpdate(prevProps) { - // the selected token changed, we should re-initialize the screen - if (this.props.selectedToken !== prevProps.selectedToken) { - this.initializeWalletScreen(); - } - - // if the new selected token history changed, we should fetch the token details again - const prevTokenHistory = get(prevProps.tokensHistory, this.props.selectedToken, { - status: TOKEN_DOWNLOAD_STATUS.LOADING, - updatedAt: -1, - data: [], - }); - const currentTokenHistory = get(this.props.tokensHistory, this.props.selectedToken, { +function Wallet() { + // Modal context + const context = useContext(GlobalModalContext); + + // State + /** backupDone {boolean} if words backup was already done */ + const [backupDone, setBackupDone] = useState(LOCAL_STORE.isBackupDone()); + /** successMessage {string} Message to be shown on alert success */ + const [successMessage, setSuccessMessage] = useState(''); + /* shouldShowAdministrativeTab {boolean} If we should display the Administrative Tools tab */ + const [shouldShowAdministrativeTab, setShouldShowAdministrativeTab] = useState(false); // TODO: Refactor this name + const [totalSupply, setTotalSupply] = useState(null); + const [canMint, setCanMint] = useState(false); + const [canMelt, setCanMelt] = useState(false); + const [transactionsCount, setTransactionsCount] = useState(null); + const [mintCount, setMintCount] = useState(null); + const [meltCount, setMeltCount] = useState(null); + + // Redux state + const { + selectedToken, + tokensHistory, + tokensBalance, + tokenMetadata, + tokens, + wallet, + walletState, + } = useSelector((state) => { + return { + selectedToken: state.selectedToken, + tokensHistory: state.tokensHistory, + tokensBalance: state.tokensBalance, + tokenMetadata: state.tokenMetadata || {}, + tokens: state.tokens, + wallet: state.wallet, + walletState: state.walletState, + }; + }); + + // Token history timestamp to check if we should fetch the token details again + const [tokenHistoryTimestamp, setTokenHistoryTimestamp] = useState(-1); + + // Refs + const alertSuccessRef = useRef(null); + const unregisterModalRef = useRef(null); + + // Navigation and actions + const history = useHistory(); + const dispatch = useDispatch(); + + // Initialize the screen on mount + useEffect(() => { + initializeWalletScreen(); + }, []); + + // Re-initialize the screen when the selected token changes + useEffect(() => { + initializeWalletScreen(); + }, [selectedToken]); + + // The tokens history has changed, check the last timestamp to define if we should fetch the token details again + useEffect(() => { + const currentTokenHistory = get(tokensHistory, selectedToken, { status: TOKEN_DOWNLOAD_STATUS.LOADING, updatedAt: -1, data: [], }); - if (prevTokenHistory.updatedAt !== currentTokenHistory.updatedAt) { - this.updateTokenInfo(); - this.updateWalletInfo(); + if (tokenHistoryTimestamp !== currentTokenHistory.updatedAt) { // TODO: Test this logic + updateTokenInfo(); + updateWalletInfo(); } - } + }, [tokensHistory]); /** * Resets the state data and triggers token information requests */ - async initializeWalletScreen() { - this.shouldShowAdministrativeTab(this.props.selectedToken); - const signature = tokens.getTokenSignature(this.props.selectedToken); - - this.setState({ - hasTokenSignature: !!signature, - totalSupply: null, - canMint: false, - canMelt: false, - transactionsCount: null, - shouldShowAdministrativeTab: false, - }); + async function initializeWalletScreen() { + calculateShouldShowAdministrativeTab(selectedToken); // TODO: Refactor to something more synchronous + + setTotalSupply(null); + setCanMint(false); + setCanMelt(false); + setTransactionsCount(null); + setShouldShowAdministrativeTab(false); // FIXME: Will override calculate above // No need to download token info and wallet info if the token is hathor - if (this.props.selectedToken === hathorLib.constants.HATHOR_TOKEN_CONFIG.uid) { + if (selectedToken === hathorLib.constants.HATHOR_TOKEN_CONFIG.uid) { return; } - await this.updateTokenInfo(); - await this.updateWalletInfo(); + await updateTokenInfo(); + await updateWalletInfo(); } /** * Update token state after didmount or props update */ - updateWalletInfo = async () => { - const tokenUid = this.props.selectedToken; - const mintUtxos = await this.props.wallet.getMintAuthority(tokenUid, { many: true }); - const meltUtxos = await this.props.wallet.getMeltAuthority(tokenUid, { many: true }); + const updateWalletInfo = async () => { + const tokenUid = selectedToken; + const mintUtxos = await wallet.getMintAuthority(tokenUid, { many: true }); + const meltUtxos = await wallet.getMeltAuthority(tokenUid, { many: true }); // The user might have changed token while we are downloading, we should ignore - if (this.props.selectedToken !== tokenUid) { + if (selectedToken !== tokenUid) { // TODO: Probably needs a refactor return; } const mintCount = mintUtxos.length; const meltCount = meltUtxos.length; - - this.setState({ - mintCount, - meltCount, - }); + setMintCount(mintCount); + setMeltCount(meltCount); } - async updateTokenInfo() { - const tokenUid = this.props.selectedToken; + async function updateTokenInfo() { + const tokenUid = selectedToken; + + // No need to fetch token info if the token is hathor if (tokenUid === hathorLib.constants.HATHOR_TOKEN_CONFIG.uid) { return; } - const tokenDetails = await this.props.wallet.getTokenDetails(tokenUid); + const tokenDetails = await wallet.getTokenDetails(tokenUid); // The user might have changed token while we are downloading, we should ignore - if (this.props.selectedToken !== tokenUid) { + if (selectedToken !== tokenUid) { // TODO: Probably needs a refactor return; } - const { totalSupply, totalTransactions, authorities } = tokenDetails; + const { totalSupply: newTotalSupply, totalTransactions, authorities } = tokenDetails; - this.setState({ - totalSupply, - canMint: authorities.mint, - canMelt: authorities.melt, - transactionsCount: totalTransactions, - }); + setTotalSupply(newTotalSupply); + setCanMint(authorities.mint); + setCanMelt(authorities.melt); + setTransactionsCount(totalTransactions); } /** @@ -197,67 +177,66 @@ class Wallet extends React.Component { * * @param {Object} e Event emitted when user click */ - backupClicked = (e) => { + const backupClicked = (e) => { e.preventDefault(); - this.context.showModal(MODAL_TYPES.BACKUP_WORDS, { + context.showModal(MODAL_TYPES.BACKUP_WORDS, { needPassword: true, - validationSuccess: this.backupSuccess, + validationSuccess: backupSuccess, }); } /** * Called when the backup of words was done with success, then close the modal and show alert success */ - backupSuccess = () => { - this.context.hideModal(); + const backupSuccess = () => { + context.hideModal(); LOCAL_STORE.markBackupDone(); - this.props.updateWords(null); - this.setState({ backupDone: true, successMessage: t`Backup completed!` }, () => { - this.alertSuccessRef.current.show(3000); - }); + dispatch(updateWords(null)); + setBackupDone(true); + setSuccessMessage(t`Backup completed!`); + alertSuccessRef.current.show(3000); } /** * Called when user clicks to unregister the token, then opens the modal */ - unregisterClicked = () => { - this.context.showModal(MODAL_TYPES.CONFIRM, { - ref: this.unregisterModalRef, + const unregisterClicked = () => { + context.showModal(MODAL_TYPES.CONFIRM, { + ref: unregisterModalRef, modalID: 'unregisterModal', title: t`Unregister token`, - body: this.getUnregisterBody(), - handleYes: this.unregisterConfirmed, + body: getUnregisterBody(), // TODO: Probably can inline this + handleYes: unregisterConfirmed, // TODO: Check if the state data is correct there }); } /** * Called when user clicks to sign the token, then opens the modal */ - signClicked = () => { - const token = this.props.tokens.find((token) => token.uid === this.props.selectedToken); + const signClicked = () => { + const token = tokens.find((token) => token.uid === selectedToken); if (LOCAL_STORE.isHardwareWallet() && version.isLedgerCustomTokenAllowed()) { - this.context.showModal(MODAL_TYPES.LEDGER_SIGN_TOKEN, { + context.showModal(MODAL_TYPES.LEDGER_SIGN_TOKEN, { token, modalId: 'signTokenDataModal', - cb: this.updateTokenSignature, }) } } /** - * When user confirms the unregister of the token, hide the modal and execute it + * When user confirms the unregistration of the token, hide the modal and execute it */ - unregisterConfirmed = async () => { - const tokenUid = this.props.selectedToken; + const unregisterConfirmed = async () => { + const tokenUid = selectedToken; try { - await tokens.unregisterToken(tokenUid); - wallet.setTokenAlwaysShow(tokenUid, false); // Remove this token from "always show" - this.context.hideModal(); + await tokensUtils.unregisterToken(tokenUid); + walletUtils.setTokenAlwaysShow(tokenUid, false); // Remove this token from "always show" + context.hideModal(); } catch (e) { - this.unregisterModalRef.current.updateErrorMessage(e.message); + unregisterModalRef.current.updateErrorMessage(e.message); } } @@ -266,65 +245,53 @@ class Wallet extends React.Component { * * This will set the shouldShowAdministrativeTab state param based on the response of getMintAuthority and getMeltAuthority */ - shouldShowAdministrativeTab = async (tokenId) => { - const mintAuthorities = await this.props.wallet.getMintAuthority(tokenId, { skipSpent: false }); + const calculateShouldShowAdministrativeTab = async (tokenId) => { + const mintAuthorities = await wallet.getMintAuthority(tokenId, { skipSpent: false }); if (mintAuthorities.length > 0) { - return this.setState({ - shouldShowAdministrativeTab: true, - }); + return setShouldShowAdministrativeTab(true); } - const meltAuthorities = await this.props.wallet.getMeltAuthority(tokenId, { skipSpent: false }); + const meltAuthorities = await wallet.getMeltAuthority(tokenId, { skipSpent: false }); if (meltAuthorities.length > 0) { - return this.setState({ - shouldShowAdministrativeTab: true, - }); + return setShouldShowAdministrativeTab(true); } - return this.setState({ - shouldShowAdministrativeTab: false, - }); + return setShouldShowAdministrativeTab(false); } - goToAllAddresses = () => { - this.props.history.push('/addresses/'); + const goToAllAddresses = () => { + history.push('/addresses/'); } // Trigger a render when we sign a token - updateTokenSignature = (value) => { - this.setState({ - hasTokenSignature: value, - }); - } - - retryDownload = (e, tokenId) => { + const retryDownload = (e, tokenId) => { e.preventDefault(); const balanceStatus = get( - this.props.tokensBalance, - `${this.props.selectedToken}.status`, + tokensBalance, + `${selectedToken}.status`, TOKEN_DOWNLOAD_STATUS.LOADING, ); const historyStatus = get( - this.props.tokensHistory, - `${this.props.selectedToken}.status`, + tokensHistory, + `${selectedToken}.status`, TOKEN_DOWNLOAD_STATUS.LOADING, ); // We should only retry the request that failed: if (historyStatus === TOKEN_DOWNLOAD_STATUS.FAILED) { - this.props.getHistory(tokenId); + dispatch(tokenFetchHistoryRequested(tokenId)); } if (balanceStatus === TOKEN_DOWNLOAD_STATUS.FAILED) { - this.props.getBalance(tokenId); + dispatch(tokenFetchBalanceRequested(tokenId)); } } - getUnregisterBody() { - const token = this.props.tokens.find((token) => token.uid === this.props.selectedToken); + function getUnregisterBody() { // TODO: Probably will be inlined + const token = tokens.find((token) => token.uid === selectedToken); if (token === undefined) return null; return ( @@ -335,206 +302,207 @@ class Wallet extends React.Component { ) } - render() { - const token = this.props.tokens.find((token) => token.uid === this.props.selectedToken); - const tokenHistory = get(this.props.tokensHistory, this.props.selectedToken, { - status: TOKEN_DOWNLOAD_STATUS.LOADING, - data: [], - }); - const tokenBalance = get(this.props.tokensBalance, this.props.selectedToken, { - status: TOKEN_DOWNLOAD_STATUS.LOADING, - data: { - available: 0, - locked: 0, - }, - }); - - const renderBackupAlert = () => { - return ( -
- {t`You haven't done the backup of your wallet yet. You should do it as soon as possible for your own safety.`} this.backupClicked(e)}>{t`Do it now`} -
- ) - } + const token = tokens.find((token) => token.uid === selectedToken); + const tokenHistory = get(tokensHistory, selectedToken, { + status: TOKEN_DOWNLOAD_STATUS.LOADING, + data: [], + }); + const tokenBalance = get(tokensBalance, selectedToken, { + status: TOKEN_DOWNLOAD_STATUS.LOADING, + data: { + available: 0, + locked: 0, + }, + }); + + const renderBackupAlert = () => { // TODO: Double check this ref + return ( +
+ {t`You haven't done the backup of your wallet yet. You should do it as soon as possible for your own safety.`} backupClicked(e)}>{t`Do it now`} +
+ ) + } - const renderWallet = () => { - return ( -
-
-
- -
- + const renderWallet = () => { + return ( +
+
+
+
-
-
- -
-
+ +
+
+
+ +
-
- +
+ +
+ ); + } + + const renderTabAdmin = () => { + if (shouldShowAdministrativeTab) { + return ( +
  • + {t`Administrative Tools`} +
  • ); + } else { + return null; } + } - const renderTabAdmin = () => { - if (this.state.shouldShowAdministrativeTab) { - return ( -
  • - {t`Administrative Tools`} -
  • - ); - } else { - return null; - } + const renderTokenData = (token) => { + if (hathorLib.tokensUtils.isHathorToken(selectedToken)) { + return renderWallet(); } - const renderTokenData = (token) => { - if (hathorLib.tokensUtils.isHathorToken(this.props.selectedToken)) { - return renderWallet(); - } else { - return ( -
    - -
    -
    - {renderWallet()} -
    -
    - + +
    +
    + {renderWallet()} +
    +
    + +
    + { + shouldShowAdministrativeTab && ( +
    +
    - { - this.shouldShowAdministrativeTab && ( -
    - -
    - ) - } -
    -
    - ); - } - } - - const renderSignTokenIcon = () => { - // Don't show if it's HTR - if (hathorLib.tokensUtils.isHathorToken(this.props.selectedToken)) return null; + ) + } +
    +
    + ); + } - const signature = tokens.getTokenSignature(this.props.selectedToken); - // Show only if we don't have a signature on storage - if (signature === null) return - return - } + const renderSignTokenIcon = () => { + // Don't show if it's HTR + if (hathorLib.tokensUtils.isHathorToken(selectedToken)) return null; - const renderUnlockedWallet = () => { - let template; - /** - * We only show the loading message if we are syncing the entire history - * This will happen on the first history load and if we lose connection - * to the fullnode. - */ - if (this.props.walletState === hathorLib.HathorWallet.SYNCING) { - template = ( -
    - -

    {t`Loading token information, please wait...`}

    -
    - ) - } else if ( - tokenHistory.status === TOKEN_DOWNLOAD_STATUS.FAILED - || tokenBalance.status === TOKEN_DOWNLOAD_STATUS.FAILED) { - template = ( -
    - -

    - - {t`Token load failed, please`}  - this.retryDownload(e, token.uid)} href="true"> - {t`try again`} - - ... - -

    + const signature = tokensUtils.getTokenSignature(selectedToken); + // Show only if we don't have a signature on storage + if (signature === null) return + return + } -
    - ) - } else { - template = ( - <> -
    -

    - {token ? token.name : ''} - {!hathorLib.tokensUtils.isHathorToken(this.props.selectedToken) && } - {LOCAL_STORE.isHardwareWallet() && version.isLedgerCustomTokenAllowed() && renderSignTokenIcon()} -

    -
    - {renderTokenData(token)} - - ) - } + const renderUnlockedWallet = () => { + let template; + /** + * We only show the loading message if we are syncing the entire history + * This will happen on the first history load and if we lose connection + * to the fullnode. + */ + if (walletState === hathorLib.HathorWallet.SYNCING) { + template = ( +
    + +

    {t`Loading token information, please wait...`}

    +
    + ) + } else if ( + tokenHistory.status === TOKEN_DOWNLOAD_STATUS.FAILED + || tokenBalance.status === TOKEN_DOWNLOAD_STATUS.FAILED) { + template = ( +
    + +

    + + {t`Token load failed, please`}  + retryDownload(e, token.uid)} href="true"> + {t`try again`} + + ... + +

    - return ( -
    - { template }
    - ); + ) + } else { + template = ( + <> +
    +

    + {token ? token.name : ''} + {!hathorLib.tokensUtils.isHathorToken(selectedToken) && } + {LOCAL_STORE.isHardwareWallet() && version.isLedgerCustomTokenAllowed() && renderSignTokenIcon()} +

    +
    + {renderTokenData(token)} + + ) } return ( -
    - {!this.state.backupDone && renderBackupAlert()} -
    - {/* This back button is not 100% perfect because when the user has just unlocked the wallet, it would go back to it when clicked - * There is no easy way to get the previous path - * I could use a lib (https://github.com/hinok/react-router-last-location) - * Or handle it in our code, saving the last accessed screen - * XXX Is it worth it to do anything about it just to prevent this case? - */} - - {renderUnlockedWallet()} -
    - +
    + { template }
    ); } + + return ( +
    + {!backupDone && renderBackupAlert()} +
    + {/* This back button is not 100% perfect because when the user has just unlocked the wallet, it would go back to it when clicked + * There is no easy way to get the previous path + * I could use a lib (https://github.com/hinok/react-router-last-location) + * Or handle it in our code, saving the last accessed screen + * XXX Is it worth it to do anything about it just to prevent this case? + */} + + {renderUnlockedWallet()} +
    + +
    + ); } -export default connect(mapStateToProps, mapDispatchToProps)(Wallet); +export default Wallet; From a74cace73af72744ff0a05981596f08fe5d18224 Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Fri, 12 Jan 2024 20:54:23 -0300 Subject: [PATCH 2/9] chore: suppress missing error message --- src/screens/Wallet.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/screens/Wallet.js b/src/screens/Wallet.js index 8bff3ffed..9745f8bda 100644 --- a/src/screens/Wallet.js +++ b/src/screens/Wallet.js @@ -47,6 +47,7 @@ function Wallet() { const [successMessage, setSuccessMessage] = useState(''); /* shouldShowAdministrativeTab {boolean} If we should display the Administrative Tools tab */ const [shouldShowAdministrativeTab, setShouldShowAdministrativeTab] = useState(false); // TODO: Refactor this name + const [errorMessage, setErrorMessage] = useState(''); // TODO: Metadata token error are being suppressed as of now const [totalSupply, setTotalSupply] = useState(null); const [canMint, setCanMint] = useState(false); const [canMelt, setCanMelt] = useState(false); From debf58f961e0c120d55915a9de85db2551687594 Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Wed, 24 Jan 2024 17:04:39 -0300 Subject: [PATCH 3/9] refactor: moves auxiliary functions to closures --- src/screens/Wallet.js | 98 ++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/src/screens/Wallet.js b/src/screens/Wallet.js index 9745f8bda..9c91a9953 100644 --- a/src/screens/Wallet.js +++ b/src/screens/Wallet.js @@ -185,60 +185,74 @@ function Wallet() { needPassword: true, validationSuccess: backupSuccess, }); - } - /** - * Called when the backup of words was done with success, then close the modal and show alert success - */ - const backupSuccess = () => { - context.hideModal(); - LOCAL_STORE.markBackupDone(); - - dispatch(updateWords(null)); - setBackupDone(true); - setSuccessMessage(t`Backup completed!`); - alertSuccessRef.current.show(3000); + /** + * Called when the backup of words was done with success, then close the modal and show alert success + */ + function backupSuccess() { + context.hideModal(); + LOCAL_STORE.markBackupDone(); + + dispatch(updateWords(null)); + setBackupDone(true); + setSuccessMessage(t`Backup completed!`); + alertSuccessRef.current.show(3000); + } } /** * Called when user clicks to unregister the token, then opens the modal */ const unregisterClicked = () => { + const tokenUid = selectedToken; + const token = tokens.find((token) => token.uid === tokenUid); + if (token === undefined) return; + context.showModal(MODAL_TYPES.CONFIRM, { ref: unregisterModalRef, modalID: 'unregisterModal', title: t`Unregister token`, - body: getUnregisterBody(), // TODO: Probably can inline this - handleYes: unregisterConfirmed, // TODO: Check if the state data is correct there + body: getUnregisterBody(), + handleYes: unregisterConfirmed, }); - } - /** - * Called when user clicks to sign the token, then opens the modal - */ - const signClicked = () => { - const token = tokens.find((token) => token.uid === selectedToken); + function getUnregisterBody() { + return ( +
    +

    {t`Are you sure you want to unregister the token **${token.name} (${token.symbol})**?`}

    +

    {t`You won't lose your tokens, you just won't see this token on the side bar anymore.`}

    +
    + ) + } - if (LOCAL_STORE.isHardwareWallet() && version.isLedgerCustomTokenAllowed()) { - context.showModal(MODAL_TYPES.LEDGER_SIGN_TOKEN, { - token, - modalId: 'signTokenDataModal', - }) + /** + * When user confirms the unregistration of the token, hide the modal and execute it + */ + async function unregisterConfirmed() { + try { + await tokensUtils.unregisterToken(tokenUid); + walletUtils.setTokenAlwaysShow(tokenUid, false); // Remove this token from "always show" + context.hideModal(); + } catch (e) { + unregisterModalRef.current.updateErrorMessage(e.message); + } } } /** - * When user confirms the unregistration of the token, hide the modal and execute it + * Called when user clicks to sign the token, then opens the modal */ - const unregisterConfirmed = async () => { - const tokenUid = selectedToken; - try { - await tokensUtils.unregisterToken(tokenUid); - walletUtils.setTokenAlwaysShow(tokenUid, false); // Remove this token from "always show" - context.hideModal(); - } catch (e) { - unregisterModalRef.current.updateErrorMessage(e.message); + const signClicked = () => { + // Can only sign on a hardware wallet on a version with custom tokens allowed + if (!LOCAL_STORE.isHardwareWallet() || !version.isLedgerCustomTokenAllowed()) { + return; } + + const token = tokens.find((token) => token.uid === selectedToken); + context.showModal(MODAL_TYPES.LEDGER_SIGN_TOKEN, { + token, + modalId: 'signTokenDataModal', + }) } /* @@ -291,18 +305,6 @@ function Wallet() { } } - function getUnregisterBody() { // TODO: Probably will be inlined - const token = tokens.find((token) => token.uid === selectedToken); - if (token === undefined) return null; - - return ( -
    -

    {t`Are you sure you want to unregister the token **${token.name} (${token.symbol})**?`}

    -

    {t`You won't lose your tokens, you just won't see this token on the side bar anymore.`}

    -
    - ) - } - const token = tokens.find((token) => token.uid === selectedToken); const tokenHistory = get(tokensHistory, selectedToken, { status: TOKEN_DOWNLOAD_STATUS.LOADING, @@ -316,9 +318,9 @@ function Wallet() { }, }); - const renderBackupAlert = () => { // TODO: Double check this ref + const renderBackupAlert = () => { return ( -
    +
    {t`You haven't done the backup of your wallet yet. You should do it as soon as possible for your own safety.`} backupClicked(e)}>{t`Do it now`}
    ) From 3f45d02346160fc6e9e3ac3cc7cd571e21e59f41 Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Wed, 24 Jan 2024 17:17:26 -0300 Subject: [PATCH 4/9] refactor: retryDownloadClicked --- src/screens/Wallet.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/screens/Wallet.js b/src/screens/Wallet.js index 9c91a9953..216a4d335 100644 --- a/src/screens/Wallet.js +++ b/src/screens/Wallet.js @@ -255,6 +255,7 @@ function Wallet() { }) } + // TODO: Understand when this calculation should be done. Maybe when token data was updated? /* * We show the administrative tools tab only for the users that one day had an authority output, even if it was already spent * @@ -280,31 +281,34 @@ function Wallet() { history.push('/addresses/'); } - // Trigger a render when we sign a token - const retryDownload = (e, tokenId) => { + /** + * Retries the download of a single token's balance and history + * @param {object} e Event + * @param {string} tokenId + */ + const retryDownloadClicked = (e, tokenId) => { e.preventDefault(); const balanceStatus = get( tokensBalance, - `${selectedToken}.status`, + `${tokenId}.status`, TOKEN_DOWNLOAD_STATUS.LOADING, ); const historyStatus = get( tokensHistory, - `${selectedToken}.status`, + `${tokenId}.status`, TOKEN_DOWNLOAD_STATUS.LOADING, ); - // We should only retry the request that failed: - + // We should only retry requests that have failed: if (historyStatus === TOKEN_DOWNLOAD_STATUS.FAILED) { dispatch(tokenFetchHistoryRequested(tokenId)); } - if (balanceStatus === TOKEN_DOWNLOAD_STATUS.FAILED) { dispatch(tokenFetchBalanceRequested(tokenId)); } } + // Rendering process below const token = tokens.find((token) => token.uid === selectedToken); const tokenHistory = get(tokensHistory, selectedToken, { status: TOKEN_DOWNLOAD_STATUS.LOADING, @@ -367,7 +371,6 @@ function Wallet() { return renderWallet(); } - // TODO: Add "calculateShouldShowAdministrativeTab()" here? return (
      @@ -459,7 +462,7 @@ function Wallet() {

      {t`Token load failed, please`}  - retryDownload(e, token.uid)} href="true"> + retryDownloadClicked(e, token.uid)} href="true"> {t`try again`} ... From b28c6cd6da21e733509e14423a7209aac2326414 Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Wed, 24 Jan 2024 18:42:18 -0300 Subject: [PATCH 5/9] fix: data flow on reload --- src/screens/Wallet.js | 61 +++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/screens/Wallet.js b/src/screens/Wallet.js index 216a4d335..30bf2c51f 100644 --- a/src/screens/Wallet.js +++ b/src/screens/Wallet.js @@ -46,7 +46,7 @@ function Wallet() { /** successMessage {string} Message to be shown on alert success */ const [successMessage, setSuccessMessage] = useState(''); /* shouldShowAdministrativeTab {boolean} If we should display the Administrative Tools tab */ - const [shouldShowAdministrativeTab, setShouldShowAdministrativeTab] = useState(false); // TODO: Refactor this name + const [shouldShowAdministrativeTab, setShouldShowAdministrativeTab] = useState(false); const [errorMessage, setErrorMessage] = useState(''); // TODO: Metadata token error are being suppressed as of now const [totalSupply, setTotalSupply] = useState(null); const [canMint, setCanMint] = useState(false); @@ -99,15 +99,17 @@ function Wallet() { // The tokens history has changed, check the last timestamp to define if we should fetch the token details again useEffect(() => { - const currentTokenHistory = get(tokensHistory, selectedToken, { + const selectedTokenHistory = get(tokensHistory, selectedToken, { status: TOKEN_DOWNLOAD_STATUS.LOADING, updatedAt: -1, data: [], }); - if (tokenHistoryTimestamp !== currentTokenHistory.updatedAt) { // TODO: Test this logic - updateTokenInfo(); - updateWalletInfo(); + // Check if the selected token history has changed from the last fetch + if (tokenHistoryTimestamp !== selectedTokenHistory.updatedAt) { + setTokenHistoryTimestamp(selectedTokenHistory.updatedAt); + updateTokenInfo(selectedToken); + updateWalletInfo(selectedToken); } }, [tokensHistory]); @@ -115,58 +117,57 @@ function Wallet() { * Resets the state data and triggers token information requests */ async function initializeWalletScreen() { - calculateShouldShowAdministrativeTab(selectedToken); // TODO: Refactor to something more synchronous - + // Reset the screen state setTotalSupply(null); setCanMint(false); setCanMelt(false); setTransactionsCount(null); - setShouldShowAdministrativeTab(false); // FIXME: Will override calculate above + setShouldShowAdministrativeTab(false); // No need to download token info and wallet info if the token is hathor if (selectedToken === hathorLib.constants.HATHOR_TOKEN_CONFIG.uid) { return; } - await updateTokenInfo(); - await updateWalletInfo(); + // Fires the fetching of all token data for the correct exhibition on screen + calculateShouldShowAdministrativeTab(selectedToken); + updateTokenInfo(selectedToken); + updateWalletInfo(selectedToken); } /** * Update token state after didmount or props update */ - const updateWalletInfo = async () => { - const tokenUid = selectedToken; + const updateWalletInfo = async (tokenUid) => { const mintUtxos = await wallet.getMintAuthority(tokenUid, { many: true }); const meltUtxos = await wallet.getMeltAuthority(tokenUid, { many: true }); - // The user might have changed token while we are downloading, we should ignore - if (selectedToken !== tokenUid) { // TODO: Probably needs a refactor + // If the user has changed the selectedToken while we were fetching the data, discard it + if (selectedToken !== tokenUid) { return; } + // Update the state with the new data const mintCount = mintUtxos.length; const meltCount = meltUtxos.length; setMintCount(mintCount); setMeltCount(meltCount); } - async function updateTokenInfo() { - const tokenUid = selectedToken; - + async function updateTokenInfo(tokenUid) { // No need to fetch token info if the token is hathor if (tokenUid === hathorLib.constants.HATHOR_TOKEN_CONFIG.uid) { return; } const tokenDetails = await wallet.getTokenDetails(tokenUid); - // The user might have changed token while we are downloading, we should ignore - if (selectedToken !== tokenUid) { // TODO: Probably needs a refactor + // If the user has changed the selectedToken while we were fetching the data, discard it + if (selectedToken !== tokenUid) { return; } + // Update the state with the new data const { totalSupply: newTotalSupply, totalTransactions, authorities } = tokenDetails; - setTotalSupply(newTotalSupply); setCanMint(authorities.mint); setCanMelt(authorities.melt); @@ -277,6 +278,9 @@ function Wallet() { return setShouldShowAdministrativeTab(false); } + /** + * @deprecated This should be replaced by usage of `useHistory` inside the child component + */ const goToAllAddresses = () => { history.push('/addresses/'); } @@ -325,7 +329,8 @@ function Wallet() { const renderBackupAlert = () => { return (

      - {t`You haven't done the backup of your wallet yet. You should do it as soon as possible for your own safety.`} backupClicked(e)}>{t`Do it now`} + { t`You haven't done the backup of your wallet yet. You should do it as soon as possible for your own safety.` } + backupClicked(e) }>{ t`Do it now` }
      ) } @@ -355,15 +360,15 @@ function Wallet() { } const renderTabAdmin = () => { - if (shouldShowAdministrativeTab) { - return ( -
    • - {t`Administrative Tools`} -
    • - ); - } else { + if (!shouldShowAdministrativeTab) { return null; } + + return ( +
    • + {t`Administrative Tools`} +
    • + ); } const renderTokenData = (token) => { From bc6232b6e56cf38d30d3f83ae41fef191583c629 Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Wed, 24 Jan 2024 18:46:09 -0300 Subject: [PATCH 6/9] refactor: rearrange functions --- src/screens/Wallet.js | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/screens/Wallet.js b/src/screens/Wallet.js index 30bf2c51f..f1c6a4c13 100644 --- a/src/screens/Wallet.js +++ b/src/screens/Wallet.js @@ -174,6 +174,27 @@ function Wallet() { setTransactionsCount(totalTransactions); } + /* + * We show the administrative tools tab only for the users that one day had an authority output, even if it was already spent + * + * This will set the shouldShowAdministrativeTab state param based on the response of getMintAuthority and getMeltAuthority + */ + const calculateShouldShowAdministrativeTab = async (tokenId) => { + const mintAuthorities = await wallet.getMintAuthority(tokenId, { skipSpent: false }); + + if (mintAuthorities.length > 0) { + return setShouldShowAdministrativeTab(true); + } + + const meltAuthorities = await wallet.getMeltAuthority(tokenId, { skipSpent: false }); + + if (meltAuthorities.length > 0) { + return setShouldShowAdministrativeTab(true); + } + + return setShouldShowAdministrativeTab(false); + } + /** * Triggered when user clicks to do the backup of words, then opens backup modal * @@ -256,28 +277,6 @@ function Wallet() { }) } - // TODO: Understand when this calculation should be done. Maybe when token data was updated? - /* - * We show the administrative tools tab only for the users that one day had an authority output, even if it was already spent - * - * This will set the shouldShowAdministrativeTab state param based on the response of getMintAuthority and getMeltAuthority - */ - const calculateShouldShowAdministrativeTab = async (tokenId) => { - const mintAuthorities = await wallet.getMintAuthority(tokenId, { skipSpent: false }); - - if (mintAuthorities.length > 0) { - return setShouldShowAdministrativeTab(true); - } - - const meltAuthorities = await wallet.getMeltAuthority(tokenId, { skipSpent: false }); - - if (meltAuthorities.length > 0) { - return setShouldShowAdministrativeTab(true); - } - - return setShouldShowAdministrativeTab(false); - } - /** * @deprecated This should be replaced by usage of `useHistory` inside the child component */ From bb5e3ad5621ec281af9d2078d510930b3aae2777 Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Wed, 24 Jan 2024 19:06:41 -0300 Subject: [PATCH 7/9] docs: improves docstrings --- src/screens/Wallet.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/screens/Wallet.js b/src/screens/Wallet.js index f1c6a4c13..d55f29d60 100644 --- a/src/screens/Wallet.js +++ b/src/screens/Wallet.js @@ -97,7 +97,7 @@ function Wallet() { initializeWalletScreen(); }, [selectedToken]); - // The tokens history has changed, check the last timestamp to define if we should fetch the token details again + // When the tokens history changes, check the last timestamp to define if we should fetch the token details again useEffect(() => { const selectedTokenHistory = get(tokensHistory, selectedToken, { status: TOKEN_DOWNLOAD_STATUS.LOADING, @@ -105,7 +105,6 @@ function Wallet() { data: [], }); - // Check if the selected token history has changed from the last fetch if (tokenHistoryTimestamp !== selectedTokenHistory.updatedAt) { setTokenHistoryTimestamp(selectedTokenHistory.updatedAt); updateTokenInfo(selectedToken); @@ -124,12 +123,12 @@ function Wallet() { setTransactionsCount(null); setShouldShowAdministrativeTab(false); - // No need to download token info and wallet info if the token is hathor + // No need to download token info and mint/melt info if the token is hathor if (selectedToken === hathorLib.constants.HATHOR_TOKEN_CONFIG.uid) { return; } - // Fires the fetching of all token data for the correct exhibition on screen + // Fires the fetching of all token data calculateShouldShowAdministrativeTab(selectedToken); updateTokenInfo(selectedToken); updateWalletInfo(selectedToken); @@ -137,6 +136,7 @@ function Wallet() { /** * Update token state after didmount or props update + * @param {string} tokenUid */ const updateWalletInfo = async (tokenUid) => { const mintUtxos = await wallet.getMintAuthority(tokenUid, { many: true }); @@ -154,6 +154,11 @@ function Wallet() { setMeltCount(meltCount); } + /** + * Fetches mint and melt data for a token + * @param {string} tokenUid + * @returns {Promise} + */ async function updateTokenInfo(tokenUid) { // No need to fetch token info if the token is hathor if (tokenUid === hathorLib.constants.HATHOR_TOKEN_CONFIG.uid) { @@ -174,7 +179,7 @@ function Wallet() { setTransactionsCount(totalTransactions); } - /* + /** * We show the administrative tools tab only for the users that one day had an authority output, even if it was already spent * * This will set the shouldShowAdministrativeTab state param based on the response of getMintAuthority and getMeltAuthority From f85bf7c974a88b6332fb2dc3df38bb3f271a50ee Mon Sep 17 00:00:00 2001 From: tuliomir Date: Tue, 30 Jan 2024 13:43:57 -0300 Subject: [PATCH 8/9] style: linter --- src/screens/Wallet.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/screens/Wallet.js b/src/screens/Wallet.js index d55f29d60..13b420473 100644 --- a/src/screens/Wallet.js +++ b/src/screens/Wallet.js @@ -9,7 +9,7 @@ import React, { useContext, useEffect, useRef, useState } from 'react'; import hathorLib from '@hathor/wallet-lib'; import { t } from 'ttag'; import { get } from 'lodash'; -import { useDispatch, useSelector } from "react-redux"; +import { useDispatch, useSelector } from 'react-redux'; import ReactLoading from 'react-loading'; import SpanFmt from '../components/SpanFmt'; @@ -28,7 +28,7 @@ import { TOKEN_DOWNLOAD_STATUS } from '../sagas/tokens'; import { GlobalModalContext, MODAL_TYPES } from '../components/GlobalModal'; import { tokenFetchBalanceRequested, tokenFetchHistoryRequested, updateWords, } from '../actions/index'; import LOCAL_STORE from '../storage'; -import { useHistory } from "react-router-dom"; +import { useHistory } from 'react-router-dom'; /** From 8098579ce2419fbdc73223ab0442d0842b6f5d70 Mon Sep 17 00:00:00 2001 From: tuliomir Date: Thu, 1 Feb 2024 11:42:24 -0300 Subject: [PATCH 9/9] refactor: removes unused code with a comment --- src/screens/Wallet.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/screens/Wallet.js b/src/screens/Wallet.js index 13b420473..f886666df 100644 --- a/src/screens/Wallet.js +++ b/src/screens/Wallet.js @@ -47,7 +47,9 @@ function Wallet() { const [successMessage, setSuccessMessage] = useState(''); /* shouldShowAdministrativeTab {boolean} If we should display the Administrative Tools tab */ const [shouldShowAdministrativeTab, setShouldShowAdministrativeTab] = useState(false); - const [errorMessage, setErrorMessage] = useState(''); // TODO: Metadata token error are being suppressed as of now + // XXX: There is an important `errorMessage` state that was not being set in the previous version + // It should be set for both the tokenMetadata error handling ( that are currently ignored ) + // and the TokenGeneralInfo child component in a future moment const [totalSupply, setTotalSupply] = useState(null); const [canMint, setCanMint] = useState(false); const [canMelt, setCanMelt] = useState(false); @@ -401,7 +403,6 @@ function Wallet() {