From 4ccb820546b19f570b2e79483d657652d0e666f4 Mon Sep 17 00:00:00 2001 From: Patrick Tajima Date: Tue, 12 Apr 2022 17:34:23 -0700 Subject: [PATCH] fix: Add 'zero balance' data when refreshAccountOwner is rejected --- .../frontend/src/components/buy/BuyNear.js | 4 ++-- .../src/components/common/AlertBanner.js | 2 ++ .../src/components/profile/Profile.js | 14 +++++++++++-- .../receive-money/ReceiveContainerWrapper.js | 7 +++---- .../src/components/send/SendContainerV2.js | 2 +- .../components/staking/StakingContainer.js | 13 ++++++------ .../staking/components/SelectAccount.js | 2 +- .../components/staking/components/Staking.js | 11 +++++----- .../frontend/src/components/wallet/Wallet.js | 9 ++++---- .../frontend/src/redux/actions/account.js | 5 +---- .../src/redux/reducers/account/index.js | 15 +++++++++++++ .../src/redux/reducers/selectors/balance.js | 21 ++++++++++++++++++- .../redux/sharedThunks/refreshAccountOwner.js | 4 ++++ .../src/redux/slices/account/index.js | 2 ++ .../redux/slices/availableAccounts/index.js | 4 ++++ packages/frontend/src/routes/WalletWrapper.js | 5 +++-- .../frontend/src/translations/en.global.json | 3 +++ 17 files changed, 90 insertions(+), 33 deletions(-) diff --git a/packages/frontend/src/components/buy/BuyNear.js b/packages/frontend/src/components/buy/BuyNear.js index 13a6a3476b..00ff862a51 100644 --- a/packages/frontend/src/components/buy/BuyNear.js +++ b/packages/frontend/src/components/buy/BuyNear.js @@ -12,7 +12,7 @@ import OkCoinLogo from '../../images/ok-coin-logo.svg'; import OkexLogo from '../../images/okex-logo.svg'; import RainbowBridgeLogo from '../../images/rainbow-bridge-logo.svg'; import { Mixpanel } from '../../mixpanel'; -import { selectAccountLocalStorageAccountId } from '../../redux/slices/account'; +import { selectAccountId } from '../../redux/slices/account'; import { isMoonpayAvailable, getSignedUrl } from '../../utils/moonpay'; import FormButton from '../common/FormButton'; import Container from '../common/styled/Container.css'; @@ -149,7 +149,7 @@ const StyledContainer = styled(Container)` `; export function BuyNear({ history }) { - const accountId = useSelector(selectAccountLocalStorageAccountId); + const accountId = useSelector(selectAccountId); const [moonPayAvailable, setMoonPayAvailable] = useState(null); const [signedMoonPayUrl, setSignedMoonPayUrl] = useState(null); diff --git a/packages/frontend/src/components/common/AlertBanner.js b/packages/frontend/src/components/common/AlertBanner.js index 44b12e8b07..78b3a38a2d 100644 --- a/packages/frontend/src/components/common/AlertBanner.js +++ b/packages/frontend/src/components/common/AlertBanner.js @@ -15,6 +15,7 @@ const Container = styled.div` border-radius: 4px; margin-bottom: 30px; align-items: center; + word-break: break-word; @media (max-width: 450px) { margin: -25px -14px 30px -14px; @@ -76,6 +77,7 @@ const Container = styled.div` font-style: italic; margin-left: 20px; font-size: 13px; + line-height: 150%; } button, a { diff --git a/packages/frontend/src/components/profile/Profile.js b/packages/frontend/src/components/profile/Profile.js index 126d8a2fe0..61f5396ece 100644 --- a/packages/frontend/src/components/profile/Profile.js +++ b/packages/frontend/src/components/profile/Profile.js @@ -24,12 +24,14 @@ import { selectAccountHasLockup, selectAccountId, selectAccountLedgerKey, - selectAccountLocalStorageAccountId + selectAccountLocalStorageAccountId, + selectAccountExists } from '../../redux/slices/account'; import { selectAllAccountsHasLockup } from '../../redux/slices/allAccounts'; import { actions as recoveryMethodsActions, selectRecoveryMethodsByAccountId } from '../../redux/slices/recoveryMethods'; import { selectNearTokenFiatValueUSD } from '../../redux/slices/tokenFiatValues'; import isMobile from '../../utils/isMobile'; +import AlertBanner from '../common/AlertBanner'; import FormButton from '../common/FormButton'; import SkeletonLoading from '../common/SkeletonLoading'; import Container from '../common/styled/Container.css'; @@ -139,6 +141,7 @@ const StyledContainer = styled(Container)` export function Profile({ match }) { const [transferring, setTransferring] = useState(false); + const accountExists = useSelector(selectAccountExists); const has2fa = useSelector(selectAccountHas2fa); const authorizedApps = useSelector(selectAccountAuthorizedApps); const ledgerKey = useSelector(selectAccountLedgerKey); @@ -147,7 +150,7 @@ export function Profile({ match }) { const nearTokenFiatValueUSD = useSelector(selectNearTokenFiatValueUSD); const accountIdFromUrl = match.params.accountId; const accountId = accountIdFromUrl || loginAccountId || accountLocalStorageAccountId; - const isOwner = accountId && accountId === loginAccountId; + const isOwner = accountId && accountId === loginAccountId && accountExists; const account = useAccount(accountId); const dispatch = useDispatch(); const profileBalance = selectProfileBalance(account); @@ -230,6 +233,13 @@ export function Profile({ match }) { }
+ {accountExists === false && + + }

{profileBalance ? ( ); diff --git a/packages/frontend/src/components/send/SendContainerV2.js b/packages/frontend/src/components/send/SendContainerV2.js index e25abca156..e559768721 100644 --- a/packages/frontend/src/components/send/SendContainerV2.js +++ b/packages/frontend/src/components/send/SendContainerV2.js @@ -167,7 +167,7 @@ const SendContainerV2 = ({ setUserInputAmount(formattedTokenAmount.replace(/,/g, '')); } }} - availableToSend={selectedToken.balance || '0'} + availableToSend={selectedToken.balance} continueAllowed={enterAmountIsComplete()} onContinue={() => { setActiveView(VIEWS.ENTER_RECEIVER); diff --git a/packages/frontend/src/components/staking/StakingContainer.js b/packages/frontend/src/components/staking/StakingContainer.js index 361ed4fe35..6a6c9c4247 100644 --- a/packages/frontend/src/components/staking/StakingContainer.js +++ b/packages/frontend/src/components/staking/StakingContainer.js @@ -11,7 +11,7 @@ import { handleStakingAction, handleUpdateCurrent } from '../../redux/actions/staking'; -import { selectAccountHas2fa, selectAccountHasLockup, selectAccountId, selectBalance, selectAccountLocalStorageAccountId } from '../../redux/slices/account'; +import { selectAccountHas2fa, selectAccountHasLockup, selectAccountId, selectBalance } from '../../redux/slices/account'; import { selectLedgerHasLedger } from '../../redux/slices/ledger'; import { selectStakingSlice } from '../../redux/slices/staking'; import { selectStatusSlice } from '../../redux/slices/status'; @@ -166,7 +166,6 @@ const StyledContainer = styled(Container)` export function StakingContainer({ history, match }) { const dispatch = useDispatch(); - const accountLocalStorageAccountId = useSelector(selectAccountLocalStorageAccountId); const accountId = useSelector(selectAccountId); const has2fa = useSelector(selectAccountHas2fa); const balance = useSelector(selectBalance); @@ -177,9 +176,11 @@ export function StakingContainer({ history, match }) { const hasLockup = useSelector(selectAccountHasLockup); const { currentAccount } = staking; - const stakingAccounts = staking.accounts.length - ? staking.accounts - : [{ accountId: accountLocalStorageAccountId, totalUnstaked: '0', totalStaked: '0' }]; + const currentAccountDataForInactiveAccount = { + accountId: accountId, + ...currentAccount + }; + const stakingAccounts = staking.accounts.length ? staking.accounts : [currentAccountDataForInactiveAccount]; const validators = staking.allValidators; const currentValidators = currentAccount.validators; const validatorId = history.location.pathname.split('/')[2]; @@ -235,7 +236,7 @@ export function StakingContainer({ history, match }) { currentValidators={currentValidators} onSwitchAccount={handleSwitchAccount} accounts={stakingAccounts} - activeAccountId={currentAccount.accountId || accountLocalStorageAccountId} + activeAccount={currentAccount} accountId={accountId} loading={status.mainLoader && !stakingAccounts.length} loadingDetails={(status.mainLoader && !stakingAccounts.length) || loadingBalance} diff --git a/packages/frontend/src/components/staking/components/SelectAccount.js b/packages/frontend/src/components/staking/components/SelectAccount.js index 171daf0106..787a188305 100644 --- a/packages/frontend/src/components/staking/components/SelectAccount.js +++ b/packages/frontend/src/components/staking/components/SelectAccount.js @@ -48,7 +48,7 @@ const Container = styled.div` export default function SelectAccount({ accounts, onChange, selectedAccount }) { return ( - !!account.totalUnstaked) ? (e) => onChange(e) : null} selectedValue={selectedAccount}> + 1 && accounts.every((account) => !!account.totalUnstaked) ? (e) => onChange(e) : null} selectedValue={selectedAccount}> {accounts.map((account, i) => diff --git a/packages/frontend/src/components/staking/components/Staking.js b/packages/frontend/src/components/staking/components/Staking.js index fa59b9e164..9d697bec4e 100644 --- a/packages/frontend/src/components/staking/components/Staking.js +++ b/packages/frontend/src/components/staking/components/Staking.js @@ -21,14 +21,13 @@ export default function Staking({ totalPending, onSwitchAccount, accounts, - activeAccountId, + activeAccount, loading, hasLockup, loadingDetails, stakeFromAccount, selectedValidator, - multipleAccounts, - accountId + multipleAccounts }) { const NEARAsTokenWithMetadata = useSelector(selectNEARAsTokenWithMetadata); @@ -46,7 +45,7 @@ export default function Staking({ onSwitchAccount(e.target.value)} - selectedAccount={activeAccountId} + selectedAccount={activeAccount.accountId} /> } )} - : + : : } @@ -328,15 +329,15 @@ export function Wallet({ ); } -const FungibleTokens = ({ balance, tokensLoader, fungibleTokens, accountId }) => { - const zeroBalanceAccount = !accountId && !tokensLoader; +const FungibleTokens = ({ balance, tokensLoader, fungibleTokens, accountExists }) => { + const zeroBalanceAccount = accountExists === false; return ( <>
({ - accountId, - ...showAlert({ onlyError: true, data: { accountId } }) - // TODO: Should we show an alert if the account is not found / has no record on chain? - // Show zero balance instead of error with an on-page disclaimer that the acccount either doesn't exist or has no record on chain, yet? + accountId }) ], REFRESH_URL: null, diff --git a/packages/frontend/src/redux/reducers/account/index.js b/packages/frontend/src/redux/reducers/account/index.js index 734aac4888..d7cb7cc919 100644 --- a/packages/frontend/src/redux/reducers/account/index.js +++ b/packages/frontend/src/redux/reducers/account/index.js @@ -22,6 +22,7 @@ import { import refreshAccountOwner from '../../sharedThunks/refreshAccountOwner'; const initialState = { + accountExists: null, formLoader: false, sentMessage: false, requestPending: null, @@ -104,6 +105,7 @@ const account = handleActions({ return { ...state, ...payload, + accountExists: true, balance: { ...payload?.balance, ...state.balance @@ -113,6 +115,19 @@ const account = handleActions({ loader: false }; }, + [refreshAccountOwner.rejected]: (state, { error, payload }) => { + return { + ...state, + ...payload, + accountExists: error.message.includes('does not exist while viewing') ? false : null, + accountId: state.localStorage.accountFound ? state.localStorage.accountId : '', + balance: { + balanceAvailable: '0' + }, + ledger: undefined, + loader: false + }; + }, [staking.updateAccount]: (state, { ready, error, payload }) => (!ready || error) ? state diff --git a/packages/frontend/src/redux/reducers/selectors/balance.js b/packages/frontend/src/redux/reducers/selectors/balance.js index 05876eb801..6d5cc64dac 100644 --- a/packages/frontend/src/redux/reducers/selectors/balance.js +++ b/packages/frontend/src/redux/reducers/selectors/balance.js @@ -3,8 +3,27 @@ import BN from 'bn.js'; import { MIN_BALANCE_FOR_GAS } from '../../../config'; export const selectProfileBalance = (walletAccount) => { + let walletBalance = { + available: '0', + inStakingPools: { + availableForWithdraw: '0', + pendingRelease: '0', + staked: '0', + sum: '0', + }, + reservedForStorage: '0', + reservedForTransactions: '0', + walletBalance: '0' + }; + const balance = walletAccount?.balance; + if (walletAccount?.accountExists === false) { + return { + walletBalance + }; + } + if (!balance || !balance.available) { return false; } @@ -26,7 +45,7 @@ export const selectProfileBalance = (walletAccount) => { const lockupIdExists = !!lockedAmount; - const walletBalance = { + walletBalance = { walletBalance: walletAccount?.amount, reservedForStorage: stateStaked.toString(), reservedForTransactions: BN.min(new BN(available), new BN(MIN_BALANCE_FOR_GAS)).toString(), diff --git a/packages/frontend/src/redux/sharedThunks/refreshAccountOwner.js b/packages/frontend/src/redux/sharedThunks/refreshAccountOwner.js index e6ebe6088e..56afc54c0e 100644 --- a/packages/frontend/src/redux/sharedThunks/refreshAccountOwner.js +++ b/packages/frontend/src/redux/sharedThunks/refreshAccountOwner.js @@ -28,8 +28,12 @@ export default createAsyncThunk( } if (nextAccountId) { dispatch(makeAccountActive(nextAccountId)); + //FIX: Automatic switching when account doesn't exist makes it impossible to switch to a zero balance account + // However, redux 'availableAccounts' array becomes empty if you switch to a zero balance account and refresh the page } + console.log('createAsyncThunk refreshAccountOwner wallet.accounts',wallet.accounts); + // TODO: Make sure "problem creating" only shows for actual creation return { resetAccount: { diff --git a/packages/frontend/src/redux/slices/account/index.js b/packages/frontend/src/redux/slices/account/index.js index b63cb54671..585925ca3b 100644 --- a/packages/frontend/src/redux/slices/account/index.js +++ b/packages/frontend/src/redux/slices/account/index.js @@ -12,6 +12,8 @@ export const selectAccountId = createSelector(selectAccountSlice, (account) => a export const selectActiveAccountIdIsImplicitAccount = createSelector(selectAccountSlice, (account) => isImplicitAccount(account.accountId)); +export const selectAccountExists = createSelector(selectAccountSlice, (account) => account.accountExists); + export const selectAccountHas2fa = createSelector(selectAccountSlice, (account) => account.has2fa); export const selectAccountHasLockup = createSelector(selectAccountSlice, (account) => account.hasLockup); diff --git a/packages/frontend/src/redux/slices/availableAccounts/index.js b/packages/frontend/src/redux/slices/availableAccounts/index.js index 722b9233a2..cf02901bbb 100644 --- a/packages/frontend/src/redux/slices/availableAccounts/index.js +++ b/packages/frontend/src/redux/slices/availableAccounts/index.js @@ -20,6 +20,10 @@ const availableAccountsSlice = createSlice({ builder.addCase(refreshAccountOwner.fulfilled, (state, action) => { set(state, ['items'], Object.keys((action.payload && action.payload.accounts) || {}).sort()); }); + builder.addCase(refreshAccountOwner.rejected, (state, action) => { + // FIX: should accounts be loaded from localStorage regardless of status? + set(state, ['items'], Object.keys((action.payload && action.payload.accounts) || {}).sort()); + }); handleAsyncThunkStatus({ asyncThunk: refreshAccountOwner, buildStatusPath: () => [], diff --git a/packages/frontend/src/routes/WalletWrapper.js b/packages/frontend/src/routes/WalletWrapper.js index 68e348c762..63013b6ee5 100644 --- a/packages/frontend/src/routes/WalletWrapper.js +++ b/packages/frontend/src/routes/WalletWrapper.js @@ -4,7 +4,7 @@ import { useSelector, useDispatch } from 'react-redux'; import { Wallet } from '../components/wallet/Wallet'; import { useFungibleTokensIncludingNEAR } from '../hooks/fungibleTokensIncludingNEAR'; import { Mixpanel } from '../mixpanel/index'; -import { selectAccountId, selectBalance } from '../redux/slices/account'; +import { selectAccountId, selectBalance, selectAccountExists } from '../redux/slices/account'; import { selectAvailableAccounts } from '../redux/slices/availableAccounts'; import { selectCreateFromImplicitSuccess, selectCreateCustomName, actions as createFromImplicitActions } from '../redux/slices/createFromImplicit'; import { selectLinkdropAmount, actions as linkdropActions } from '../redux/slices/linkdrop'; @@ -20,8 +20,8 @@ export function WalletWrapper({ tab, setTab }) { - const accountId = useSelector(selectAccountId); + const accountExists = useSelector(selectAccountExists); const balance = useSelector(selectBalance); const dispatch = useDispatch(); const linkdropAmount = useSelector(selectLinkdropAmount); @@ -47,6 +47,7 @@ export function WalletWrapper({ tab={tab} setTab={setTab} accountId={accountId} + accountExists={accountExists} balance={balance} linkdropAmount={linkdropAmount} createFromImplicitSuccess={createFromImplicitSuccess} diff --git a/packages/frontend/src/translations/en.global.json b/packages/frontend/src/translations/en.global.json index efd03ee812..2bf76d0d3d 100644 --- a/packages/frontend/src/translations/en.global.json +++ b/packages/frontend/src/translations/en.global.json @@ -741,6 +741,9 @@ "walletBalance": "Wallet balance", "walletId": "Wallet ID" }, + "accountDoesNotExistBanner": { + "desc": "No activity has been recorded for this account. Send NEAR to your account to begin using all features of the NEAR Wallet." + }, "authorizedApps": { "title": "Authorized Apps" },