From 7c5941676032abdc145dcb3e532f7ef23853fc2b Mon Sep 17 00:00:00 2001 From: Josh Leonard Date: Tue, 19 Apr 2022 11:34:35 -0600 Subject: [PATCH 1/6] chore: reduce wallet page routing complexity --- .../common/async/__mocks__/lib.ts | 14 + .../brave_wallet_ui/common/async/handlers.ts | 35 ++ .../common/hooks/use-password-strength.ts | 57 +++ .../common/reducers/wallet_reducer.ts | 2 +- .../components/desktop/index.ts | 5 +- .../edit-visible-assets-modal/index.tsx | 44 +- .../components/desktop/top-tab-nav/index.tsx | 3 - .../desktop/views/accounts/account.tsx | 283 +++++++++++ .../desktop/views/accounts/accounts.tsx | 163 ++++++ .../desktop/views/accounts/index.tsx | 376 +------------- .../components/desktop/views/crypto/index.tsx | 380 +++++++------- .../components/desktop/views/index.ts | 15 +- .../components/token-lists/index.tsx | 43 +- .../desktop/views/portfolio/index.tsx | 480 +----------------- .../views/portfolio/portfolio-asset.tsx | 408 +++++++++++++++ .../views/portfolio/portfolio-overview.tsx | 248 +++++++++ .../desktop/wallet-more-popup/index.tsx | 41 +- .../create-password/index.tsx | 88 +++- .../import-meta-mask-or-legacy/index.tsx | 226 +++++++-- .../wallet-onboarding/restore/index.tsx | 163 +++--- .../wallet-onboarding/verify/index.tsx | 11 +- .../wallet-onboarding/welcome/index.tsx | 67 ++- .../extension/connected-header/index.tsx | 18 +- .../extension/connected-panel/index.tsx | 93 ++-- .../protected-routing/protected-route.tsx | 41 ++ components/brave_wallet_ui/constants/types.ts | 27 +- .../options/top-nav-options.ts | 7 + .../page/actions/wallet_page_actions.ts | 4 +- .../page/async/wallet_page_async_handler.ts | 6 +- components/brave_wallet_ui/page/container.tsx | 476 +++++++---------- .../brave_wallet_ui/panel/container.tsx | 8 +- .../stories/mock-data/user-accounts.ts | 7 + .../stories/screens/backup-wallet.tsx | 76 ++- .../stories/screens/crypto-story-view.tsx | 208 +++----- .../stories/screens/onboarding.tsx | 317 ++---------- .../stories/wallet-components.tsx | 63 +-- .../stories/wallet-concept.tsx | 271 ++++------ .../stories/wallet-extension-panels.tsx | 10 +- .../wrappers/wallet-page-story-wrapper.tsx | 58 +++ .../brave_wallet_ui/utils/account-utils.ts | 27 + 40 files changed, 2624 insertions(+), 2245 deletions(-) create mode 100644 components/brave_wallet_ui/common/hooks/use-password-strength.ts create mode 100644 components/brave_wallet_ui/components/desktop/views/accounts/account.tsx create mode 100644 components/brave_wallet_ui/components/desktop/views/accounts/accounts.tsx create mode 100644 components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx create mode 100644 components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-overview.tsx create mode 100644 components/brave_wallet_ui/components/shared/protected-routing/protected-route.tsx create mode 100644 components/brave_wallet_ui/stories/wrappers/wallet-page-story-wrapper.tsx create mode 100644 components/brave_wallet_ui/utils/account-utils.ts diff --git a/components/brave_wallet_ui/common/async/__mocks__/lib.ts b/components/brave_wallet_ui/common/async/__mocks__/lib.ts index 4d4277c1235d..f3ff20b213b1 100644 --- a/components/brave_wallet_ui/common/async/__mocks__/lib.ts +++ b/components/brave_wallet_ui/common/async/__mocks__/lib.ts @@ -1,3 +1,8 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import { mockBasicAttentionToken, mockEthToken } from '../../../stories/mock-data/mock-asset-options' import { BraveWallet, GetChecksumEthAddressReturnInfo, GetEthAddrReturnInfo } from '../../../constants/types' @@ -74,3 +79,12 @@ export const findUnstoppableDomainAddress = async (address: string) => { export const getChecksumEthAddress = async () => { return {} as GetChecksumEthAddressReturnInfo } + +export const isStrongPassword = (value: string) => { + return ( + (value.length > 7) && // is at least 7 characters + /[-’/`~!#*$@_%+=.,^&(){}[\]|;:”<>?\\]/g.test(value) && // contains a special character + value.toLowerCase() !== value && // contains an uppercase character + /\d/.test(value) // contains a number + ) +} diff --git a/components/brave_wallet_ui/common/async/handlers.ts b/components/brave_wallet_ui/common/async/handlers.ts index d38a8fdc5b4b..7c2943f97ed2 100644 --- a/components/brave_wallet_ui/common/async/handlers.ts +++ b/components/brave_wallet_ui/common/async/handlers.ts @@ -232,6 +232,41 @@ handler.on(WalletActions.removeFavoriteApp.getType(), async (store: Store, appIt await refreshWalletInfo(store) }) +handler.on(WalletActions.chainChangedEvent.getType(), async (store: Store, payload: ChainChangedEventPayloadType) => { + const keyringService = getAPIProxy().keyringService + const state = getWalletState(store) + const { accounts } = state + const selectedAccountAddress = await keyringService.getSelectedAccount(payload.coin) + const selectedAccount = accounts.find((account) => account.address === selectedAccountAddress.address) ?? accounts[0] + store.dispatch(WalletActions.setSelectedAccount(selectedAccount)) + store.dispatch(WalletActions.setSelectedCoin(payload.coin)) +}) + +handler.on(WalletActions.selectNetwork.getType(), async (store: Store, payload: BraveWallet.NetworkInfo) => { + if (payload) { + const jsonRpcService = getAPIProxy().jsonRpcService + await jsonRpcService.setNetwork(payload.chainId, payload.coin) + store.dispatch(WalletActions.setNetwork(payload)) + } +}) + +handler.on(WalletActions.selectAccount.getType(), async (store: Store, payload: WalletAccountType) => { + const { keyringService } = getAPIProxy() + const state = getWalletState(store) + const { defaultNetworks } = state + + if (!(defaultNetworks.length > 0)) { + return + } + + const defaultCoinTypesNetwork = defaultNetworks.find((network) => network.coin === payload.coin) ?? defaultNetworks[0] + await keyringService.setSelectedAccount(payload.address, payload.coin) + store.dispatch(WalletActions.setNetwork(defaultCoinTypesNetwork)) + store.dispatch(WalletActions.setSelectedAccount(payload)) + store.dispatch(WalletActions.setSelectedCoin(payload.coin)) + await store.dispatch(refreshTransactionHistory(payload.address)) +}) + handler.on(WalletActions.initialized.getType(), async (store: Store, payload: WalletInfo) => { const keyringService = getAPIProxy().keyringService const state = getWalletState(store) diff --git a/components/brave_wallet_ui/common/hooks/use-password-strength.ts b/components/brave_wallet_ui/common/hooks/use-password-strength.ts new file mode 100644 index 000000000000..b8a3479debf4 --- /dev/null +++ b/components/brave_wallet_ui/common/hooks/use-password-strength.ts @@ -0,0 +1,57 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import { useLib } from './useLib' + +export const usePasswordStrength = () => { + // custom hooks + const { isStrongPassword: checkIsStrongPassword } = useLib() + + // state + const [password, setPassword] = React.useState('') + const [confirmedPassword, setConfirmedPassword] = React.useState('') + const [isStrongPassword, setIsStrongPassword] = React.useState(false) + + const onPasswordChanged = React.useCallback(async (value: string) => { + setPassword(value) + const isStrong = await checkIsStrongPassword(value) + setIsStrongPassword(isStrong) + }, []) + + const hasPasswordError = React.useMemo(() => { + if (password === '') { + return false + } + return !isStrongPassword + }, [password, isStrongPassword]) + + const hasConfirmedPasswordError = React.useMemo(() => { + if (confirmedPassword === '') { + return false + } else { + return confirmedPassword !== password + } + }, [confirmedPassword, password]) + + const isValid = !( + hasConfirmedPasswordError || + hasPasswordError || + password === '' || + confirmedPassword === '' + ) + + return { + confirmedPassword, + password, + onPasswordChanged, + setConfirmedPassword, + isStrongPassword, + isValid, + hasConfirmedPasswordError, + hasPasswordError, + checkIsStrongPassword + } +} diff --git a/components/brave_wallet_ui/common/reducers/wallet_reducer.ts b/components/brave_wallet_ui/common/reducers/wallet_reducer.ts index e17c1b6816fc..879c5ce0a96e 100644 --- a/components/brave_wallet_ui/common/reducers/wallet_reducer.ts +++ b/components/brave_wallet_ui/common/reducers/wallet_reducer.ts @@ -62,7 +62,7 @@ const defaultState: WalletState = { isEip1559: true } } - } as BraveWallet.NetworkInfo, + }, accounts: [], userVisibleTokensInfo: [], transactions: {}, diff --git a/components/brave_wallet_ui/components/desktop/index.ts b/components/brave_wallet_ui/components/desktop/index.ts index 88d4063eda3f..1c28b22bf610 100644 --- a/components/brave_wallet_ui/components/desktop/index.ts +++ b/components/brave_wallet_ui/components/desktop/index.ts @@ -24,7 +24,7 @@ import TransactionPopup from './transaction-popup' import SwapTooltip from './swap-tooltip' import WithHideBalancePlaceholder from './with-hide-balance-placeholder' import NetworkFilterSelector from './network-filter-selector' -import { CryptoView, PortfolioView } from './views' +import { CryptoView, PortfolioAsset, PortfolioOverview } from './views' import { OnboardingWelcome, OnboardingBackup, @@ -49,7 +49,8 @@ export { PortfolioTransactionItem, AddButton, CryptoView, - PortfolioView, + PortfolioAsset, + PortfolioOverview, OnboardingWelcome, OnboardingBackup, OnboardingRecovery, diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx index 0b3705ade95d..a577ae858c18 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/edit-visible-assets-modal/index.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { BraveWallet } from '../../../../constants/types' +import { BraveWallet, WalletState } from '../../../../constants/types' import { PopupModal, AssetWatchlistItem, @@ -37,32 +37,34 @@ import { // Utils import Amount from '../../../../utils/amount' +// hooks +import { useAssetManagement, useLib, useTokenInfo } from '../../../../common/hooks' +import { useSelector } from 'react-redux' + export interface Props { onClose: () => void - onAddCustomAsset: (token: BraveWallet.BlockchainToken) => void - onUpdateVisibleAssets: (updatedTokensList: BraveWallet.BlockchainToken[]) => void - addUserAssetError: boolean - fullAssetList: BraveWallet.BlockchainToken[] - userVisibleTokensInfo: BraveWallet.BlockchainToken[] - selectedNetwork: BraveWallet.NetworkInfo - networkList: BraveWallet.NetworkInfo[] - onFindTokenInfoByContractAddress: (contractAddress: string) => void - foundTokenInfoByContractAddress?: BraveWallet.BlockchainToken } -const EditVisibleAssetsModal = (props: Props) => { +const EditVisibleAssetsModal = ({ onClose }: Props) => { + // redux const { - fullAssetList, - userVisibleTokensInfo, addUserAssetError, + userVisibleTokensInfo, selectedNetwork, networkList, - onClose, + fullTokenList + } = useSelector(({ wallet }: { wallet: WalletState }) => wallet) + + // custom hooks + const { getBlockchainTokenInfo } = useLib() + const { onAddCustomAsset, + onUpdateVisibleAssets + } = useAssetManagement() + const { onFindTokenInfoByContractAddress, - onUpdateVisibleAssets, foundTokenInfoByContractAddress - } = props + } = useTokenInfo(getBlockchainTokenInfo, userVisibleTokensInfo, fullTokenList, selectedNetwork) // Token List States const [filteredTokenList, setFilteredTokenList] = React.useState([]) @@ -200,15 +202,15 @@ const EditVisibleAssetsModal = (props: Props) => { : userVisibleTokensInfo.map((token) => token.contractAddress.toLowerCase()) const fullAssetsListPlusNativeToken = userVisibleContracts.includes('') - ? fullAssetList - : [nativeAsset, ...fullAssetList] + ? fullTokenList + : [nativeAsset, ...fullTokenList] const filteredTokenRegistry = fullAssetsListPlusNativeToken.filter((token) => !userVisibleContracts.includes(token.contractAddress.toLowerCase())) return isUserVisibleTokensInfoEmpty ? filteredTokenRegistry : [...userVisibleTokensInfo, ...filteredTokenRegistry] - }, [fullAssetList, userVisibleTokensInfo]) + }, [fullTokenList, userVisibleTokensInfo]) React.useEffect(() => { // Added this timeout to throttle setting the list @@ -305,9 +307,9 @@ const EditVisibleAssetsModal = (props: Props) => { return false } - return !fullAssetList + return !fullTokenList .some(each => each.contractAddress.toLowerCase() === token.contractAddress.toLowerCase()) - }, [fullAssetList]) + }, [fullTokenList]) const addOrRemoveTokenFromList = (selected: boolean, token: BraveWallet.BlockchainToken) => { if (selected) { diff --git a/components/brave_wallet_ui/components/desktop/top-tab-nav/index.tsx b/components/brave_wallet_ui/components/desktop/top-tab-nav/index.tsx index 5e8ad847c5d5..cbe30644bbba 100644 --- a/components/brave_wallet_ui/components/desktop/top-tab-nav/index.tsx +++ b/components/brave_wallet_ui/components/desktop/top-tab-nav/index.tsx @@ -28,7 +28,6 @@ export interface Props { hasMoreButtons?: boolean showMore?: boolean onSelectTab: (id: TopTabNavTypes | AddAccountNavTypes | AccountSettingsNavTypes) => void - onClickLock?: () => void onClickSettings?: () => void onClickBackup?: () => void onClickMore?: () => void @@ -43,7 +42,6 @@ function TopTabNav (props: Props) { onClickMore, onClickSettings, onClickBackup, - onClickLock, onSelectTab } = props @@ -73,7 +71,6 @@ function TopTabNav (props: Props) { {showMore && diff --git a/components/brave_wallet_ui/components/desktop/views/accounts/account.tsx b/components/brave_wallet_ui/components/desktop/views/accounts/account.tsx new file mode 100644 index 000000000000..acd57ad3118f --- /dev/null +++ b/components/brave_wallet_ui/components/desktop/views/accounts/account.tsx @@ -0,0 +1,283 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import { Redirect, useParams } from 'react-router' +import { useDispatch, useSelector } from 'react-redux' +import { create } from 'ethereum-blockies' + +import { + AccountSettingsNavTypes, + UpdateAccountNamePayloadType, + BraveWallet, + CoinTypesMap, + WalletState, + PageState, + WalletRoutes +} from '../../../../constants/types' + +// utils +import { reduceAddress } from '../../../../utils/reduce-address' +import { copyToClipboard } from '../../../../utils/copy-to-clipboard' +import { getLocale } from '../../../../../common/locale' +import { sortTransactionByDate } from '../../../../utils/tx-utils' + +// Styled Components +import { + StyledWrapper, + SubDivider, + Button, + TopRow, + WalletInfoRow, + WalletAddress, + WalletName, + AccountCircle, + WalletInfoLeftSide, + QRCodeIcon, + EditIcon, + SubviewSectionTitle, + TransactionPlaceholderContainer +} from './style' +import { TransactionPlaceholderText, Spacer } from '../portfolio/style' + +// Components +import { BackButton, Tooltip } from '../../../shared' +import { + PortfolioAssetItem, + AccountSettingsModal, + PortfolioTransactionItem +} from '../..' + +// Hooks +import { useBalance } from '../../../../common/hooks' + +// Actions +import { WalletActions } from '../../../../common/actions' + +export interface Props { + onViewPrivateKey: (address: string, isDefault: boolean, coin: BraveWallet.CoinType) => void + onDoneViewingPrivateKey: () => void + toggleNav: () => void + onUpdateAccountName: (payload: UpdateAccountNamePayloadType) => { success: boolean } + onRemoveAccount: (address: string, hardware: boolean, coin: BraveWallet.CoinType) => void + goBack: () => void +} + +export const Account = (props: Props) => { + const { + goBack, + onViewPrivateKey, + onDoneViewingPrivateKey, + toggleNav, + onUpdateAccountName, + onRemoveAccount + } = props + + const { id: accountId } = useParams<{ id: string }>() + + // redux + const dispatch = useDispatch() + const { + accounts, + transactions, + transactionSpotPrices, + userVisibleTokensInfo, + defaultCurrencies, + networkList + } = useSelector(({ wallet }: { wallet: WalletState }) => wallet) + + const privateKey = useSelector(({ page }: { page: PageState }) => page.privateKey) + + // state + const [showEditModal, setShowEditModal] = React.useState(false) + const [editTab, setEditTab] = React.useState('details') + + // custom hooks + const getBalance = useBalance(networkList) + + // memos + const selectedAccount = React.useMemo(() => { + return accounts.find(({ address }) => + address.toLowerCase() === accountId?.toLowerCase() + ) + }, [accounts, accountId]) + + const orb = React.useMemo(() => { + if (selectedAccount) { + return create({ seed: selectedAccount.address.toLowerCase(), size: 8, scale: 16 }).toDataURL() + } + }, [selectedAccount]) + + const transactionList = React.useMemo(() => { + if (selectedAccount?.address && transactions[selectedAccount.address]) { + return sortTransactionByDate(transactions[selectedAccount.address], 'descending') + } else { + return [] + } + }, [selectedAccount, transactions]) + + const accountsTokensList = React.useMemo(() => { + if (!selectedAccount) { + return [] + } + // Since LOCALHOST's chainId is shared between coinType's + // this check will make sure we are returning the correct + // LOCALHOST asset for each account. + const coinName = CoinTypesMap[selectedAccount?.coin ?? 0] + const localHostCoins = userVisibleTokensInfo.filter((token) => token.chainId === BraveWallet.LOCALHOST_CHAIN_ID) + const accountsLocalHost = localHostCoins.find((token) => token.symbol.toUpperCase() === coinName) + const chainList = networkList.filter((network) => network.coin === selectedAccount?.coin).map((network) => network.chainId) ?? [] + const list = + userVisibleTokensInfo.filter((token) => chainList.includes(token?.chainId ?? '') && + token.chainId !== BraveWallet.LOCALHOST_CHAIN_ID) ?? [] + if (accountsLocalHost) { + return [...list, accountsLocalHost] + } + return list + }, [userVisibleTokensInfo, selectedAccount, networkList]) + + const nonFungibleTokens = React.useMemo( + () => accountsTokensList.filter(({ isErc721 }) => isErc721), + [accountsTokensList] + ) + + const funigbleTokens = React.useMemo( + () => accountsTokensList.filter(({ isErc721 }) => !isErc721), + [accountsTokensList] + ) + + // methods + const onCopyToClipboard = React.useCallback(async () => { + if (selectedAccount) { + await copyToClipboard(selectedAccount.address) + } + }, [selectedAccount]) + + const onShowEditModal = React.useCallback(() => { + setShowEditModal(true) + }, []) + + const onCloseEditModal = React.useCallback(() => { + setShowEditModal(false) + setEditTab('details') + }, []) + + // effects + React.useEffect(() => { + if (selectedAccount) { + dispatch(WalletActions.selectAccount(selectedAccount)) + } + }, [selectedAccount]) + + // redirect (asset not found) + if (!selectedAccount) { + return + } + + // render + return ( + + + + + + + + + {selectedAccount.name} + + {reduceAddress(selectedAccount.address)} + + + + + + + {getLocale('braveWalletAccountsAssets')} + + + + {funigbleTokens.map((item) => + + )} + + + + {nonFungibleTokens?.length !== 0 && + <> + + {getLocale('braveWalletTopNavNFTS')} + + {nonFungibleTokens?.map((item) => + + )} + + + } + + + + {getLocale('braveWalletTransactions')} + + + + {transactionList.length !== 0 ? ( + <> + {transactionList.map((transaction) => + + )} + + ) : ( + + {getLocale('braveWalletTransactionPlaceholder')} + + )} + + {showEditModal && selectedAccount && + + } + + ) +} + +export default Account diff --git a/components/brave_wallet_ui/components/desktop/views/accounts/accounts.tsx b/components/brave_wallet_ui/components/desktop/views/accounts/accounts.tsx new file mode 100644 index 000000000000..041ff8591514 --- /dev/null +++ b/components/brave_wallet_ui/components/desktop/views/accounts/accounts.tsx @@ -0,0 +1,163 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import { useSelector } from 'react-redux' + +import { + WalletAccountType, + BraveWallet, + AddAccountNavTypes, + WalletState +} from '../../../../constants/types' + +// utils +import { getLocale } from '../../../../../common/locale' +import { groupAccountsById, sortAccountsByName } from '../../../../utils/account-utils' + +// Styled Components +import { + StyledWrapper, + SectionTitle, + PrimaryListContainer, + SecondaryListContainer, + DisclaimerText, + SubDivider, + ButtonRow, + StyledButton, + HardwareIcon, + ButtonText, + WalletIcon +} from './style' + +// Components +import { + AccountListItem, + AddButton +} from '../..' + +export interface Props { + onClickAddAccount: (tabId: AddAccountNavTypes) => () => void + onRemoveAccount: (address: string, hardware: boolean, coin: BraveWallet.CoinType) => void + onSelectAccount: (account: WalletAccountType) => void +} + +export const Accounts = ({ + onSelectAccount, + onClickAddAccount, + onRemoveAccount +}: Props) => { + // redux + const accounts = useSelector(({ wallet }: { wallet: WalletState }) => wallet.accounts) + + // memos + const primaryAccounts = React.useMemo(() => { + return accounts.filter((account) => account.accountType === 'Primary') + }, [accounts]) + + const secondaryAccounts = React.useMemo(() => { + return accounts.filter((account) => account.accountType === 'Secondary') + }, [accounts]) + + const trezorAccounts = React.useMemo(() => { + const foundTrezorAccounts = accounts.filter((account) => account.accountType === 'Trezor') + return groupAccountsById(foundTrezorAccounts, 'deviceId') + }, [accounts]) + + const ledgerAccounts = React.useMemo(() => { + const foundLedgerAccounts = accounts.filter((account) => account.accountType === 'Ledger') + return groupAccountsById(foundLedgerAccounts, 'deviceId') + }, [accounts]) + + return ( + + + {getLocale('braveWalletAccountsPrimary')} + + {getLocale('braveWalletAccountsPrimaryDisclaimer')} + + + + + {primaryAccounts.map((account) => + + )} + + + + + + + {getLocale('braveWalletAccountsSecondary')} + + {getLocale('braveWalletAccountsSecondaryDisclaimer')} + + + + + {secondaryAccounts.map((account) => + + )} + + + {Object.keys(trezorAccounts).map(key => + + {sortAccountsByName(trezorAccounts[key]).map((account: WalletAccountType) => + + )} + + )} + + {Object.keys(ledgerAccounts).map(key => + + {sortAccountsByName(ledgerAccounts[key]).map((account: WalletAccountType) => + + )} + + )} + + + + + {getLocale('braveWalletAddAccountImport')} + + + + {getLocale('braveWalletAddAccountImportHardware')} + + + + ) +} + +export default Accounts diff --git a/components/brave_wallet_ui/components/desktop/views/accounts/index.tsx b/components/brave_wallet_ui/components/desktop/views/accounts/index.tsx index 8f3686d745bf..2a660069bafd 100644 --- a/components/brave_wallet_ui/components/desktop/views/accounts/index.tsx +++ b/components/brave_wallet_ui/components/desktop/views/accounts/index.tsx @@ -1,371 +1,7 @@ -import * as React from 'react' +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. -import { - WalletAccountType, - AccountSettingsNavTypes, - UpdateAccountNamePayloadType, - AccountTransactions, - BraveWallet, - DefaultCurrencies, - AddAccountNavTypes, - CoinTypesMap -} from '../../../../constants/types' -import { reduceAddress } from '../../../../utils/reduce-address' -import { copyToClipboard } from '../../../../utils/copy-to-clipboard' -import { create } from 'ethereum-blockies' -import { getLocale } from '../../../../../common/locale' -import { sortTransactionByDate } from '../../../../utils/tx-utils' - -// Styled Components -import { - StyledWrapper, - SectionTitle, - PrimaryListContainer, - SecondaryListContainer, - DisclaimerText, - SubDivider, - Button, - TopRow, - WalletInfoRow, - WalletAddress, - WalletName, - AccountCircle, - WalletInfoLeftSide, - QRCodeIcon, - EditIcon, - SubviewSectionTitle, - TransactionPlaceholderContainer, - ButtonRow, - StyledButton, - HardwareIcon, - ButtonText, - WalletIcon -} from './style' - -import { TransactionPlaceholderText, Spacer } from '../portfolio/style' - -// Components -import { BackButton, Tooltip } from '../../../shared' -import { - AccountListItem, - AddButton, - PortfolioAssetItem, - AccountSettingsModal, - PortfolioTransactionItem -} from '../../' - -// Hooks -import { useBalance } from '../../../../common/hooks' - -export interface Props { - accounts: WalletAccountType[] - transactions: AccountTransactions - privateKey: string - networkList: BraveWallet.NetworkInfo[] - userVisibleTokensInfo: BraveWallet.BlockchainToken[] - transactionSpotPrices: BraveWallet.AssetPrice[] - selectedAccount: WalletAccountType | undefined - defaultCurrencies: DefaultCurrencies - onViewPrivateKey: (address: string, isDefault: boolean, coin: BraveWallet.CoinType) => void - onDoneViewingPrivateKey: () => void - toggleNav: () => void - onClickAddAccount: (tabId: AddAccountNavTypes) => () => void - onUpdateAccountName: (payload: UpdateAccountNamePayloadType) => { success: boolean } - onRemoveAccount: (address: string, hardware: boolean, coin: BraveWallet.CoinType) => void - onSelectAccount: (account: WalletAccountType) => void - goBack: () => void -} - -function Accounts (props: Props) { - const { - accounts, - transactions, - privateKey, - transactionSpotPrices, - userVisibleTokensInfo, - selectedAccount, - defaultCurrencies, - networkList, - goBack, - onSelectAccount, - onViewPrivateKey, - onDoneViewingPrivateKey, - toggleNav, - onClickAddAccount, - onUpdateAccountName, - onRemoveAccount - } = props - - const getBalance = useBalance(networkList) - - const groupById = (accounts: WalletAccountType[], key: string) => { - return accounts.reduce((result, obj) => { - (result[obj[key]] = result[obj[key]] || []).push(obj) - return result - }, {}) - } - - const primaryAccounts = React.useMemo(() => { - return accounts.filter((account) => account.accountType === 'Primary') - }, [accounts]) - - const secondaryAccounts = React.useMemo(() => { - return accounts.filter((account) => account.accountType === 'Secondary') - }, [accounts]) - - const trezorAccounts = React.useMemo(() => { - const foundTrezorAccounts = accounts.filter((account) => account.accountType === 'Trezor') - return groupById(foundTrezorAccounts, 'deviceId') - }, [accounts]) - - const ledgerAccounts = React.useMemo(() => { - const foundLedgerAccounts = accounts.filter((account) => account.accountType === 'Ledger') - return groupById(foundLedgerAccounts, 'deviceId') - }, [accounts]) - - const [showEditModal, setShowEditModal] = React.useState(false) - const [editTab, setEditTab] = React.useState('details') - - const sortAccountsByName = React.useCallback((accounts: WalletAccountType[]) => { - return [...accounts].sort(function (a: WalletAccountType, b: WalletAccountType) { - if (a.name < b.name) { - return -1 - } - - if (a.name > b.name) { - return 1 - } - - return 0 - }) - }, []) - - const onCopyToClipboard = async () => { - if (selectedAccount) { - await copyToClipboard(selectedAccount.address) - } - } - - const onChangeTab = (id: AccountSettingsNavTypes) => { - setEditTab(id) - } - - const onShowEditModal = () => { - setShowEditModal(!showEditModal) - } - - const onCloseEditModal = () => { - setShowEditModal(!showEditModal) - setEditTab('details') - } - - const orb = React.useMemo(() => { - if (selectedAccount) { - return create({ seed: selectedAccount.address.toLowerCase(), size: 8, scale: 16 }).toDataURL() - } - }, [selectedAccount]) - - const transactionList = React.useMemo(() => { - if (selectedAccount?.address && transactions[selectedAccount.address]) { - return sortTransactionByDate(transactions[selectedAccount.address], 'descending') - } else { - return [] - } - }, [selectedAccount, transactions]) - - const accountsTokensList = React.useMemo(() => { - if (!selectedAccount) { - return [] - } - // Since LOCALHOST's chainId is shared between coinType's - // this check will make sure we are returning the correct - // LOCALHOST asset for each account. - const coinName = CoinTypesMap[selectedAccount?.coin ?? 0] - const localHostCoins = userVisibleTokensInfo.filter((token) => token.chainId === BraveWallet.LOCALHOST_CHAIN_ID) - const accountsLocalHost = localHostCoins.find((token) => token.symbol.toUpperCase() === coinName) - const chainList = networkList.filter((network) => network.coin === selectedAccount?.coin).map((network) => network.chainId) ?? [] - const list = - userVisibleTokensInfo.filter((token) => chainList.includes(token?.chainId ?? '') && - token.chainId !== BraveWallet.LOCALHOST_CHAIN_ID) ?? [] - if (accountsLocalHost) { - return [...list, accountsLocalHost] - } - return list - }, [userVisibleTokensInfo, selectedAccount, networkList]) - - const erc271Tokens = React.useMemo(() => - accountsTokensList.filter((token) => token.isErc721), - [accountsTokensList] - ) - - return ( - - {selectedAccount && - - } - {!selectedAccount ? ( - <> - {getLocale('braveWalletAccountsPrimary')} - {getLocale('braveWalletAccountsPrimaryDisclaimer')} - - - {primaryAccounts.map((account) => - - )} - - - - - {getLocale('braveWalletAccountsSecondary')} - {getLocale('braveWalletAccountsSecondaryDisclaimer')} - - - {secondaryAccounts.map((account) => - - )} - - {Object.keys(trezorAccounts).map(key => - - {sortAccountsByName(trezorAccounts[key]).map((account: WalletAccountType) => - - )} - - )} - {Object.keys(ledgerAccounts).map(key => - - {sortAccountsByName(ledgerAccounts[key]).map((account: WalletAccountType) => - - )} - - )} - - - - {getLocale('braveWalletAddAccountImport')} - - - - {getLocale('braveWalletAddAccountImportHardware')} - - - - ) : ( - <> - - - - {selectedAccount.name} - - {reduceAddress(selectedAccount.address)} - - - - - - {getLocale('braveWalletAccountsAssets')} - - {accountsTokensList.filter((token) => !token.isErc721).map((item) => - - )} - - {erc271Tokens?.length !== 0 && - <> - - {getLocale('braveWalletTopNavNFTS')} - - {erc271Tokens?.map((item) => - - )} - - - } - - {getLocale('braveWalletTransactions')} - - {transactionList.length !== 0 ? ( - <> - {transactionList.map((transaction) => - - )} - - ) : ( - - {getLocale('braveWalletTransactionPlaceholder')} - - )} - - )} - {showEditModal && selectedAccount && - - } - - ) -} - -export default Accounts +export { Account } from './account' +export { Accounts } from './accounts' diff --git a/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx b/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx index e403c5333eaf..781870957eaa 100644 --- a/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx +++ b/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx @@ -1,23 +1,39 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' -import { Route, useHistory, useLocation, useParams } from 'react-router-dom' -import { StyledWrapper } from './style' +import { useDispatch } from 'react-redux' +import { Route, useHistory, useParams, Switch } from 'react-router-dom' + +// actions +import { WalletPageActions } from '../../../../page/actions' + +// utils +import { getLocale } from '../../../../../common/locale' + +// types import { BraveWallet, TopTabNavTypes, - AccountTransactions, WalletAccountType, UpdateAccountNamePayloadType, WalletRoutes, - DefaultCurrencies, AddAccountNavTypes } from '../../../../constants/types' -import { TopNavOptions } from '../../../../options/top-nav-options' -import { TopTabNav, WalletBanner, AddAccountModal } from '../../' -import { getLocale } from '../../../../../common/locale' -import { PortfolioView, AccountsView } from '../' -import { - HardwareWalletConnectOpts -} from '../../popup-modals/add-account-modal/hardware-wallet-connect/types' +import { TOP_NAV_OPTIONS } from '../../../../options/top-nav-options' +import { HardwareWalletConnectOpts } from '../../popup-modals/add-account-modal/hardware-wallet-connect/types' + +// style +import { StyledWrapper } from './style' + +// components +import { TopTabNav, WalletBanner, AddAccountModal, EditVisibleAssetsModal } from '../../' +import { PortfolioOverview } from '../portfolio/portfolio-overview' +import { PortfolioAsset } from '../portfolio/portfolio-asset' +import { Accounts } from '../accounts/accounts' +import { Account } from '../accounts/account' interface ParamsType { category?: TopTabNavTypes @@ -25,9 +41,6 @@ interface ParamsType { } export interface Props { - onLockWallet: () => void - onShowBackup: () => void - onSelectAsset: (asset: BraveWallet.BlockchainToken | undefined) => void onCreateAccount: (name: string, coin: BraveWallet.CoinType) => void onImportAccount: (accountName: string, privateKey: string, coin: BraveWallet.CoinType) => void onImportFilecoinAccount: (accountName: string, key: string, network: string) => void @@ -35,40 +48,24 @@ export interface Props { onAddHardwareAccounts: (selected: BraveWallet.HardwareWalletAccount[]) => void getBalance: (address: string, coin: BraveWallet.CoinType) => Promise onUpdateAccountName: (payload: UpdateAccountNamePayloadType) => { success: boolean } - onShowAddModal: () => void - onHideAddModal: () => void onRemoveAccount: (address: string, hardware: boolean, coin: BraveWallet.CoinType) => void onViewPrivateKey: (address: string, isDefault: boolean, coin: BraveWallet.CoinType) => void onDoneViewingPrivateKey: () => void onImportAccountFromJson: (accountName: string, password: string, json: string) => void onSetImportError: (error: boolean) => void onOpenWalletSettings: () => void - defaultCurrencies: DefaultCurrencies hasImportError: boolean - transactionSpotPrices: BraveWallet.AssetPrice[] - privateKey: string - userVisibleTokensInfo: BraveWallet.BlockchainToken[] needsBackup: boolean accounts: WalletAccountType[] - networkList: BraveWallet.NetworkInfo[] - transactions: AccountTransactions isFilecoinEnabled: boolean isSolanaEnabled: boolean - showAddModal: boolean selectedNetwork: BraveWallet.NetworkInfo defaultWallet: BraveWallet.DefaultWallet isMetaMaskInstalled: boolean - onShowVisibleAssetsModal: (showModal: boolean) => void - showVisibleAssetsModal: boolean } const CryptoView = (props: Props) => { - const { pathname: walletLocation } = useLocation() - let history = useHistory() const { - onLockWallet, - onShowBackup, - onSelectAsset, onCreateAccount, onConnectHardwareWallet, onAddHardwareAccounts, @@ -82,213 +79,222 @@ const CryptoView = (props: Props) => { onImportAccountFromJson, onSetImportError, onOpenWalletSettings, - onShowAddModal, - onHideAddModal, - onShowVisibleAssetsModal, - showVisibleAssetsModal, - defaultCurrencies, defaultWallet, hasImportError, - userVisibleTokensInfo, - transactionSpotPrices, - privateKey, selectedNetwork, needsBackup, accounts, - networkList, - transactions, isFilecoinEnabled, isSolanaEnabled, - showAddModal, isMetaMaskInstalled } = props + + // state const [hideNav, setHideNav] = React.useState(false) const [showBackupWarning, setShowBackupWarning] = React.useState(needsBackup) const [showDefaultWalletBanner, setShowDefaultWalletBanner] = React.useState(needsBackup) - const [selectedAccount, setSelectedAccount] = React.useState() const [showMore, setShowMore] = React.useState(false) const [addAccountModalTab, setAddAccountModalTab] = React.useState('create') - let { category, id } = useParams() + // routing + const history = useHistory() + const { category } = useParams() - const onSelectTab = (path: TopTabNavTypes) => { - history.push(`/crypto/${path}`) - } - - React.useEffect(() => { - if (category === 'portfolio') { - if (id !== undefined) { - if (id === 'add-asset') { - onShowVisibleAssetsModal(true) - } else { - // If the id length is greater than 15 assumes it's a contractAddress - const asset = id?.length > 15 - ? userVisibleTokensInfo.find((token) => token.contractAddress === id) - : userVisibleTokensInfo.find((token) => token.symbol.toLowerCase() === id?.toLowerCase()) - onSelectAsset(asset) - setHideNav(true) - } - } else { - onSelectAsset(undefined) - setHideNav(false) - } - } - if (category === 'accounts') { - if (id !== undefined) { - if (id === 'add-account') { - onShowAddModal() - } else { - const account = accounts.find((a) => a.address.toLowerCase() === id?.toLowerCase()) - setSelectedAccount(account) - setHideNav(true) - } - } else { - setSelectedAccount(undefined) - setHideNav(false) - } + // redux + const dispatch = useDispatch() + + // methods + const onShowBackup = React.useCallback(() => { + history.push(WalletRoutes.Backup) + }, []) + + const onShowVisibleAssetsModal = React.useCallback((showModal: boolean) => { + if (showModal) { + history.push(`${WalletRoutes.AddAssetModal}`) + } else { + history.push(`${WalletRoutes.Portfolio}`) } - }, [id, userVisibleTokensInfo, category]) + }, []) + + const onSelectTab = React.useCallback((path: TopTabNavTypes) => { + history.push(`/crypto/${path}`) + }, []) - const toggleNav = () => { + const toggleNav = React.useCallback(() => { setHideNav(!hideNav) - } + }, [hideNav]) - const onDismissBackupWarning = () => { + const onDismissBackupWarning = React.useCallback(() => { setShowBackupWarning(false) - } + }, []) - const onDismissDefaultWalletBanner = () => { + const onDismissDefaultWalletBanner = React.useCallback(() => { setShowDefaultWalletBanner(false) - } + }, []) - const onCloseAddModal = () => { - if (walletLocation.includes(WalletRoutes.Accounts)) { - history.push(WalletRoutes.Accounts) - } - onHideAddModal() - } + const onCloseAddModal = React.useCallback(() => { + history.push(WalletRoutes.Accounts) + dispatch(WalletPageActions.setShowAddModal(false)) + }, []) - const onClickAddAccount = (tabId: AddAccountNavTypes) => () => { + const onClickAddAccount = React.useCallback((tabId: AddAccountNavTypes) => () => { setAddAccountModalTab(tabId) - onShowAddModal() - } + dispatch(WalletPageActions.setShowAddModal(true)) + history.push(WalletRoutes.AddAccountModal) + }, []) - const goBack = () => { - setSelectedAccount(undefined) + const goBack = React.useCallback(() => { history.push(WalletRoutes.Accounts) setHideNav(false) - } + }, []) - const onSelectAccount = (account: WalletAccountType | undefined) => { + const onSelectAccount = React.useCallback((account: WalletAccountType | undefined) => { if (account) { history.push(`${WalletRoutes.Accounts}/${account.address}`) } - } + }, []) - const onClickSettings = () => { + const onClickSettings = React.useCallback(() => { chrome.tabs.create({ url: 'chrome://settings/wallet' }, () => { if (chrome.runtime.lastError) { console.error('tabs.create failed: ' + chrome.runtime.lastError.message) } }) - } + }, []) - const onClickShowMore = () => { + const onClickShowMore = React.useCallback(() => { setShowMore(true) - } + }, []) - const onClickHideMore = () => { + const onClickHideMore = React.useCallback(() => { if (showMore) { setShowMore(false) } - } + }, [showMore]) - return ( - - {!hideNav && - <> - - {(defaultWallet !== BraveWallet.DefaultWallet.BraveWallet && - (defaultWallet !== BraveWallet.DefaultWallet.BraveWalletPreferExtension || (defaultWallet === BraveWallet.DefaultWallet.BraveWalletPreferExtension && isMetaMaskInstalled))) && - showDefaultWalletBanner && - - } - {needsBackup && showBackupWarning && - - } - - } + const hideVisibleAssetsModal = React.useCallback(() => onShowVisibleAssetsModal(false), []) - - ( + <> + + {(defaultWallet !== BraveWallet.DefaultWallet.BraveWallet && + (defaultWallet !== BraveWallet.DefaultWallet.BraveWalletPreferExtension || (defaultWallet === BraveWallet.DefaultWallet.BraveWalletPreferExtension && isMetaMaskInstalled))) && + showDefaultWalletBanner && + - - - - - - {showAddModal && - } + + ), [ + category, + defaultWallet, + isMetaMaskInstalled, + needsBackup, + onClickSettings, + onClickShowMore, + onDismissBackupWarning, + onDismissDefaultWalletBanner, + onOpenWalletSettings, + onSelectTab, + onShowBackup, + showBackupWarning, + showDefaultWalletBanner, + showMore + ]) + + // render + return ( + + + {/* Portfolio */} + + {nav} + + + + + + {nav} + + + + + + + + {/* Accounts */} + + {nav} + + + + + + {nav} + + + + + + + + ) } diff --git a/components/brave_wallet_ui/components/desktop/views/index.ts b/components/brave_wallet_ui/components/desktop/views/index.ts index 84cf2dc26189..a598dc393afc 100644 --- a/components/brave_wallet_ui/components/desktop/views/index.ts +++ b/components/brave_wallet_ui/components/desktop/views/index.ts @@ -1,9 +1,16 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +import { Account, Accounts } from './accounts' import CryptoView from './crypto' -import PortfolioView from './portfolio' -import AccountsView from './accounts' +import { PortfolioAsset, PortfolioOverview } from './portfolio' export { + Account, + Accounts, CryptoView, - PortfolioView, - AccountsView + PortfolioAsset, + PortfolioOverview } diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/components/token-lists/index.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/components/token-lists/index.tsx index e762c7383a22..4e184822c4a4 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/components/token-lists/index.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/components/token-lists/index.tsx @@ -1,10 +1,17 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' +import { useHistory } from 'react-router' // Types import { BraveWallet, UserAssetInfoType, - DefaultCurrencies + DefaultCurrencies, + WalletRoutes } from '../../../../../../constants/types' // Utils @@ -36,7 +43,6 @@ export interface Props { networks: BraveWallet.NetworkInfo[] onSetFilteredAssetList: (filteredList: UserAssetInfoType[]) => void onSelectAsset: (asset: BraveWallet.BlockchainToken | undefined) => () => void - onShowAssetModal: () => void } const TokenLists = (props: Props) => { @@ -48,14 +54,26 @@ const TokenLists = (props: Props) => { hideBalances, networks, onSelectAsset, - onShowAssetModal, onSetFilteredAssetList } = props - const erc721TokenList = React.useMemo(() => filteredAssetList.filter((token) => token.asset.isErc721), [filteredAssetList]) + // routing + const history = useHistory() + + // memos + const nonFungibleTokenList = React.useMemo( + () => filteredAssetList.filter(({ asset }) => asset.isErc721), + [filteredAssetList] + ) + + const fungibleTokenList = React.useMemo( + () => filteredAssetList.filter(({ asset }) => !asset.isErc721), + [filteredAssetList] + ) + // methods // This filters a list of assets when the user types in search bar - const onFilterAssets = (event: React.ChangeEvent) => { + const onFilterAssets = React.useCallback((event: React.ChangeEvent) => { const search = event.target.value if (search === '') { onSetFilteredAssetList(userAssetList) @@ -70,15 +88,20 @@ const TokenLists = (props: Props) => { }) onSetFilteredAssetList(filteredList) } - } + }, [onSetFilteredAssetList, userAssetList]) + + const showAddAssetsModal = React.useCallback(() => { + history.push(WalletRoutes.AddAssetModal) + }, []) + // render return ( <> - {filteredAssetList.filter((asset) => !asset.asset.isErc721).map((item) => + {fungibleTokenList.map((item) => { networks={networks} /> )} - {erc721TokenList.length !== 0 && + {nonFungibleTokenList.length !== 0 && <> {getLocale('braveWalletTopNavNFTS')} - {erc721TokenList.map((item) => + {nonFungibleTokenList.map((item) => { diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/index.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/index.tsx index 75d15da58f47..983c088c51fc 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/index.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/index.tsx @@ -1,477 +1,3 @@ -import * as React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { useHistory } from 'react-router' - -// Constants -import { - BraveWallet, - UserAssetInfoType, - AddAccountNavTypes, - WalletState, - PageState, - SupportedTestNetworks, - WalletRoutes -} from '../../../../constants/types' -import { getLocale } from '../../../../../common/locale' -import { CurrencySymbols } from '../../../../utils/currency-symbols' - -// Utils -import { sortTransactionByDate } from '../../../../utils/tx-utils' -import { getTokensNetwork, getTokensCoinType } from '../../../../utils/network-utils' -import Amount from '../../../../utils/amount' - -// Options -import { ChartTimelineOptions } from '../../../../options/chart-timeline-options' - -// Components -import { BackButton, LoadingSkeleton, withPlaceholderIcon } from '../../../shared' -import { - ChartControlBar, - LineChart, - EditVisibleAssetsModal, - WithHideBalancePlaceholder -} from '../../' - -// import NFTDetails from './components/nft-details' -import TokenLists from './components/token-lists' -import AccountsAndTransactionsList from './components/accounts-and-transctions-list' - -// Hooks -import { useAssetManagement, useBalance, usePricing, useTokenInfo, useTransactionParser } from '../../../../common/hooks' -import { useLib } from '../../../../common/hooks/useLib' - -// Styled Components -import { - StyledWrapper, - TopRow, - BalanceTitle, - BalanceText, - AssetIcon, - AssetRow, - AssetColumn, - PriceRow, - AssetNameText, - DetailText, - InfoColumn, - PriceText, - PercentBubble, - PercentText, - ArrowIcon, - BalanceRow, - ShowBalanceButton, - NetworkDescription -} from './style' -import { AllNetworksOption } from '../../../../options/network-filter-options' -import { mojoTimeDeltaToJSDate } from '../../../../../common/mojomUtils' -import { WalletPageActions } from '../../../../page/actions' -import { WalletActions } from '../../../../common/actions' - -export interface Props { - toggleNav: () => void - onClickAddAccount: (tabId: AddAccountNavTypes) => () => void - onShowVisibleAssetsModal: (showModal: boolean) => void - showVisibleAssetsModal: boolean -} - -const AssetIconWithPlaceholder = withPlaceholderIcon(AssetIcon, { size: 'big', marginLeft: 0, marginRight: 12 }) - -const Portfolio = (props: Props) => { - const { - toggleNav, - onClickAddAccount, - onShowVisibleAssetsModal, - showVisibleAssetsModal - } = props - - // routing - const history = useHistory() - - // redux - const dispatch = useDispatch() - const { - defaultCurrencies, - addUserAssetError, - userVisibleTokensInfo, - selectedNetwork, - portfolioPriceHistory, - selectedPortfolioTimeline, - accounts, - networkList, - transactions, - isFetchingPortfolioPriceHistory, - transactionSpotPrices, - fullTokenList, - selectedNetworkFilter - } = useSelector(({ wallet }: { wallet: WalletState }) => wallet) - - const { - isFetchingPriceHistory: isLoading, - selectedTimeline, - selectedAsset, - selectedAssetPriceHistory, - selectedAssetFiatPrice, - selectedAssetCryptoPrice - } = useSelector(({ page }: { page: PageState }) => page) - - // custom hooks - const { getBlockchainTokenInfo } = useLib() - const getAccountBalance = useBalance(networkList) - const { computeFiatAmount } = usePricing(transactionSpotPrices) - const { - onFindTokenInfoByContractAddress, - foundTokenInfoByContractAddress - } = useTokenInfo(getBlockchainTokenInfo, userVisibleTokensInfo, fullTokenList, selectedNetwork) - const { - onAddCustomAsset, - onUpdateVisibleAssets - } = useAssetManagement() - - // This will scrape all the user's accounts and combine the asset balances for a single asset - const fullAssetBalance = React.useCallback((asset: BraveWallet.BlockchainToken) => { - const tokensCoinType = getTokensCoinType(networkList, asset) - const amounts = accounts.filter((account) => account.coin === tokensCoinType).map((account) => - getAccountBalance(account, asset)) - - // If a user has not yet created a FIL or SOL account, - // we return 0 until they create an account - if (amounts.length === 0) { - return '0' - } - - return amounts.reduce(function (a, b) { - return a !== '' && b !== '' - ? new Amount(a).plus(b).format() - : '' - }) - }, [accounts, networkList, getAccountBalance]) - - // This looks at the users asset list and returns the full balance for each asset - const userAssetList = React.useMemo(() => { - const allAssets = userVisibleTokensInfo.map((asset) => ({ - asset: asset, - assetBalance: fullAssetBalance(asset) - }) as UserAssetInfoType) - // By default we dont show any testnetwork assets - if (selectedNetworkFilter.chainId === AllNetworksOption.chainId) { - return allAssets.filter((asset) => !SupportedTestNetworks.includes(asset.asset.chainId)) - } - // If chainId is Localhost we also do a check for coinType to return - // the correct asset - if (selectedNetworkFilter.chainId === BraveWallet.LOCALHOST_CHAIN_ID) { - return allAssets.filter((asset) => - asset.asset.chainId === selectedNetworkFilter.chainId && - getTokensCoinType(networkList, asset.asset) === selectedNetworkFilter.coin - ) - } - // Filter by all other assets by chainId's - return allAssets.filter((asset) => asset.asset.chainId === selectedNetworkFilter.chainId) - }, [userVisibleTokensInfo, selectedNetworkFilter, fullAssetBalance, networkList]) - - // This will scrape all of the user's accounts and combine the fiat value for every asset - const fullPortfolioFiatBalance = React.useMemo(() => { - const visibleAssetOptions = userAssetList - .filter((token) => - token.asset.visible && - !token.asset.isErc721 - ) - - if (visibleAssetOptions.length === 0) { - return '' - } - - const visibleAssetFiatBalances = visibleAssetOptions - .map((item) => { - return computeFiatAmount(item.assetBalance, item.asset.symbol, item.asset.decimals) - }) - - const grandTotal = visibleAssetFiatBalances.reduce(function (a, b) { - return a.plus(b) - }) - return grandTotal.formatAsFiat() - }, [userAssetList, computeFiatAmount]) - - // state - const [filteredAssetList, setfilteredAssetList] = React.useState(userAssetList) - const [hoverBalance, setHoverBalance] = React.useState() - const [hoverPrice, setHoverPrice] = React.useState() - const [hideBalances, setHideBalances] = React.useState(false) - const selectedAssetsNetwork = React.useMemo(() => { - if (!selectedAsset) { - return selectedNetwork - } - return getTokensNetwork(networkList, selectedAsset) - }, [selectedNetwork, selectedAsset, networkList]) - - // more custom hooks - const parseTransaction = useTransactionParser(selectedAssetsNetwork, accounts, transactionSpotPrices, userVisibleTokensInfo) - - // memos / computed - - const formattedPriceHistory = React.useMemo(() => { - return selectedAssetPriceHistory.map((obj) => { - return { - date: mojoTimeDeltaToJSDate(obj.date), - close: Number(obj.price) - } - }) - }, [selectedAssetPriceHistory]) - - const priceHistory = React.useMemo(() => { - if (parseFloat(fullPortfolioFiatBalance) === 0) { - return [] - } else { - return portfolioPriceHistory - } - }, [portfolioPriceHistory, fullPortfolioFiatBalance]) - - const transactionsByNetwork = React.useMemo(() => { - const accountsByNetwork = accounts.filter((account) => account.coin === selectedAssetsNetwork.coin) - return accountsByNetwork.map((account) => { - return transactions[account.address] - }).flat(1) - }, [accounts, transactions, selectedAssetsNetwork]) - - const selectedAssetTransactions = React.useMemo((): BraveWallet.TransactionInfo[] => { - if (selectedAsset) { - const filteredTransactions = transactionsByNetwork.filter((tx) => { - return tx && parseTransaction(tx).symbol === selectedAsset?.symbol - }) - return sortTransactionByDate(filteredTransactions, 'descending') - } - return [] - }, [selectedAsset, transactionsByNetwork, parseTransaction]) - - const fullAssetBalances = React.useMemo(() => { - if (selectedAsset?.contractAddress === '') { - return filteredAssetList.find( - (asset) => - asset.asset.symbol.toLowerCase() === selectedAsset?.symbol.toLowerCase() && - asset.asset.chainId === selectedAsset?.chainId - ) - } - return filteredAssetList.find( - (asset) => - asset.asset.contractAddress.toLowerCase() === selectedAsset?.contractAddress.toLowerCase() && - asset.asset.chainId === selectedAsset?.chainId - ) - }, [filteredAssetList, selectedAsset]) - - const formattedFullAssetBalance = fullAssetBalances?.assetBalance - ? '(' + new Amount(fullAssetBalances?.assetBalance ?? '') - .divideByDecimals(selectedAsset?.decimals ?? 18) - .formatAsAsset(6, selectedAsset?.symbol ?? '') + ')' - : '' - - const fullAssetFiatBalance = React.useMemo(() => fullAssetBalances?.assetBalance - ? computeFiatAmount( - fullAssetBalances.assetBalance, - fullAssetBalances.asset.symbol, - fullAssetBalances.asset.decimals - ) - : Amount.empty(), - [fullAssetBalances] - ) - - // methods - const onChangeTimeline = (timeline: BraveWallet.AssetPriceTimeframe) => { - if (selectedAsset) { - dispatch(WalletPageActions.selectAsset({ - asset: selectedAsset, - timeFrame: timeline - })) - } else { - dispatch(WalletActions.selectPortfolioTimeline(timeline)) - } - } - - const selectAsset = (asset: BraveWallet.BlockchainToken | undefined) => { - if (asset) { - if (asset.contractAddress === '') { - history.push(`${WalletRoutes.Portfolio}/${asset.symbol}`) - return - } - history.push(`${WalletRoutes.Portfolio}/${asset.contractAddress}`) - } else { - dispatch(WalletPageActions.selectAsset({ asset, timeFrame: selectedTimeline })) - history.push(WalletRoutes.Portfolio) - } - } - - const onSelectAsset = (asset: BraveWallet.BlockchainToken) => () => { - selectAsset(asset) - toggleNav() - } - - const goBack = () => { - selectAsset(undefined) - setfilteredAssetList(userAssetList) - toggleNav() - } - - const onUpdateBalance = (value: number | undefined) => { - if (!selectedAsset) { - if (value) { - setHoverBalance(new Amount(value).formatAsFiat()) - } else { - setHoverBalance(undefined) - } - } else { - if (value) { - setHoverPrice(new Amount(value).formatAsFiat()) - } else { - setHoverPrice(undefined) - } - } - } - - const toggleShowVisibleAssetModal = () => { - onShowVisibleAssetsModal(!showVisibleAssetsModal) - } - - const onToggleHideBalances = () => { - setHideBalances(!hideBalances) - } - - // effects - React.useEffect(() => { - setfilteredAssetList(userAssetList) - }, [userAssetList]) - - // render - return ( - - - - {!selectedAsset ? ( - {getLocale('braveWalletBalance')} - ) : ( - - )} - - - {!selectedAsset?.isErc721 && - - } - - - - - {!selectedAsset ? ( - - - {fullPortfolioFiatBalance !== '' - ? `${CurrencySymbols[defaultCurrencies.fiat]}${hoverBalance || fullPortfolioFiatBalance}` - : - } - - - ) : ( - <> - {!selectedAsset.isErc721 && - - - - - {selectedAsset.name} - {selectedAsset.symbol} on {selectedAssetsNetwork?.chainName ?? ''} - - - {/* {selectedAsset.name} {getLocale('braveWalletPrice')} ({selectedAsset.symbol}) */} - - {CurrencySymbols[defaultCurrencies.fiat]}{hoverPrice || (selectedAssetFiatPrice ? new Amount(selectedAssetFiatPrice.price).formatAsFiat() : 0.00)} - - - {selectedAssetFiatPrice ? Number(selectedAssetFiatPrice.assetTimeframeChange).toFixed(2) : 0.00}% - - - - { - selectedAssetCryptoPrice - ? new Amount(selectedAssetCryptoPrice.price) - .formatAsAsset(undefined, defaultCurrencies.crypto) - : '' - } - - - } - - )} - - {!selectedAsset?.isErc721 && - - } - - {/* Temp Commented out until we have an API to get NFT MetaData */} - {/* {selectedAsset?.isErc721 && - - } */} - - - - {!selectedAsset && - - } - - {showVisibleAssetsModal && - - } - - ) -} - -export default Portfolio +export * from './portfolio-asset' +export { PortfolioAsset } from './portfolio-asset' +export { PortfolioOverview } from './portfolio-overview' diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx new file mode 100644 index 000000000000..e2b2600989ad --- /dev/null +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx @@ -0,0 +1,408 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { Redirect, useHistory, useParams } from 'react-router' + +// types +import { + BraveWallet, + UserAssetInfoType, + AddAccountNavTypes, + WalletState, + PageState, + SupportedTestNetworks, + WalletRoutes +} from '../../../../constants/types' +import { CurrencySymbols } from '../../../../utils/currency-symbols' + +// Utils +import Amount from '../../../../utils/amount' +import { mojoTimeDeltaToJSDate } from '../../../../../common/mojomUtils' +import { sortTransactionByDate } from '../../../../utils/tx-utils' +import { getTokensNetwork, getTokensCoinType } from '../../../../utils/network-utils' + +// actions +import { WalletPageActions } from '../../../../page/actions' + +// Options +import { ChartTimelineOptions } from '../../../../options/chart-timeline-options' +import { AllNetworksOption } from '../../../../options/network-filter-options' + +// Components +import { BackButton, withPlaceholderIcon } from '../../../shared' +import { + ChartControlBar, + LineChart +} from '../../' +// import NFTDetails from './components/nft-details' +import AccountsAndTransactionsList from './components/accounts-and-transctions-list' + +// Hooks +import { useBalance, usePricing, useTransactionParser } from '../../../../common/hooks' + +// Styled Components +import { + StyledWrapper, + TopRow, + AssetIcon, + AssetRow, + AssetColumn, + PriceRow, + AssetNameText, + DetailText, + InfoColumn, + PriceText, + PercentBubble, + PercentText, + ArrowIcon, + BalanceRow, + ShowBalanceButton, + NetworkDescription +} from './style' +import { Skeleton } from '../../../shared/loading-skeleton/styles' + +interface Props { + onClickAddAccount: (tabId: AddAccountNavTypes) => () => void +} + +const AssetIconWithPlaceholder = withPlaceholderIcon(AssetIcon, { size: 'big', marginLeft: 0, marginRight: 12 }) + +export const PortfolioAsset = (props: Props) => { + const { + onClickAddAccount + } = props + + // routing + const history = useHistory() + const { id: assetId } = useParams<{ id?: string }>() + + // redux + const dispatch = useDispatch() + const { + defaultCurrencies, + userVisibleTokensInfo, + selectedNetwork, + portfolioPriceHistory, + selectedPortfolioTimeline, + accounts, + networkList, + transactions, + isFetchingPortfolioPriceHistory, + transactionSpotPrices, + selectedNetworkFilter + } = useSelector(({ wallet }: { wallet: WalletState }) => wallet) + + const { + isFetchingPriceHistory: isLoading, + selectedAsset, + selectedAssetCryptoPrice, + selectedAssetFiatPrice, + selectedAssetPriceHistory, + selectedTimeline + } = useSelector(({ page }: { page: PageState }) => page) + + // custom hooks + const getAccountBalance = useBalance(networkList) + + // memos + // This will scrape all the user's accounts and combine the asset balances for a single asset + const fullAssetBalance = React.useCallback((asset: BraveWallet.BlockchainToken) => { + const tokensCoinType = getTokensCoinType(networkList, asset) + const amounts = accounts.filter((account) => account.coin === tokensCoinType).map((account) => + getAccountBalance(account, asset)) + + // If a user has not yet created a FIL or SOL account, + // we return 0 until they create an account + if (amounts.length === 0) { + return '0' + } + + return amounts.reduce(function (a, b) { + return a !== '' && b !== '' + ? new Amount(a).plus(b).format() + : '' + }) + }, [accounts, networkList, getAccountBalance]) + + // This looks at the users asset list and returns the full balance for each asset + const userAssetList = React.useMemo(() => { + const allAssets = userVisibleTokensInfo.map((asset) => ({ + asset: asset, + assetBalance: fullAssetBalance(asset) + }) as UserAssetInfoType) + // By default we dont show any testnetwork assets + if (selectedNetworkFilter.chainId === AllNetworksOption.chainId) { + return allAssets.filter((asset) => !SupportedTestNetworks.includes(asset.asset.chainId)) + } + // If chainId is Localhost we also do a check for coinType to return + // the correct asset + if (selectedNetworkFilter.chainId === BraveWallet.LOCALHOST_CHAIN_ID) { + return allAssets.filter((asset) => + asset.asset.chainId === selectedNetworkFilter.chainId && + getTokensCoinType(networkList, asset.asset) === selectedNetworkFilter.coin + ) + } + // Filter by all other assets by chainId's + return allAssets.filter((asset) => asset.asset.chainId === selectedNetworkFilter.chainId) + }, [userVisibleTokensInfo, selectedNetworkFilter, fullAssetBalance, networkList]) + + // state + const [filteredAssetList, setfilteredAssetList] = React.useState(userAssetList) + const [hoverPrice, setHoverPrice] = React.useState() + const [hideBalances, setHideBalances] = React.useState(false) + const selectedAssetsNetwork = React.useMemo(() => { + if (!selectedAsset) { + return selectedNetwork + } + return getTokensNetwork(networkList, selectedAsset) + }, [selectedNetwork, selectedAsset, networkList]) + + // more custom hooks + const parseTransaction = useTransactionParser(selectedAssetsNetwork, accounts, transactionSpotPrices, userVisibleTokensInfo) + const { computeFiatAmount } = usePricing(transactionSpotPrices) + + // memos / computed + const selectedAssetFromParams = React.useMemo(() => { + if (!assetId) { + return undefined + } + + // If the id length is greater than 15 assumes it's a contractAddress + return assetId.length > 15 + ? userVisibleTokensInfo.find((token) => token.contractAddress === assetId) + : userVisibleTokensInfo.find((token) => token.symbol.toLowerCase() === assetId?.toLowerCase()) + }, [assetId, userVisibleTokensInfo, selectedTimeline]) + + // This will scrape all of the user's accounts and combine the fiat value for every asset + const fullPortfolioFiatBalance = React.useMemo(() => { + const visibleAssetOptions = userAssetList + .filter((token) => + token.asset.visible && + !token.asset.isErc721 + ) + + if (visibleAssetOptions.length === 0) { + return '' + } + + const visibleAssetFiatBalances = visibleAssetOptions + .map((item) => { + return computeFiatAmount(item.assetBalance, item.asset.symbol, item.asset.decimals) + }) + + const grandTotal = visibleAssetFiatBalances.reduce(function (a, b) { + return a.plus(b) + }) + return grandTotal.formatAsFiat() + }, [userAssetList, computeFiatAmount]) + + const formattedPriceHistory = React.useMemo(() => { + return selectedAssetPriceHistory.map((obj) => { + return { + date: mojoTimeDeltaToJSDate(obj.date), + close: Number(obj.price) + } + }) + }, [selectedAssetPriceHistory]) + + const priceHistory = React.useMemo(() => { + if (parseFloat(fullPortfolioFiatBalance) === 0) { + return [] + } else { + return portfolioPriceHistory + } + }, [portfolioPriceHistory, fullPortfolioFiatBalance]) + + const transactionsByNetwork = React.useMemo(() => { + const accountsByNetwork = accounts.filter((account) => account.coin === selectedAssetsNetwork.coin) + return accountsByNetwork.map((account) => { + return transactions[account.address] + }).flat(1) + }, [accounts, transactions, selectedAssetsNetwork]) + + const selectedAssetTransactions = React.useMemo((): BraveWallet.TransactionInfo[] => { + if (selectedAsset) { + const filteredTransactions = transactionsByNetwork.filter((tx) => { + return tx && parseTransaction(tx).symbol === selectedAsset?.symbol + }) + return sortTransactionByDate(filteredTransactions, 'descending') + } + return [] + }, [selectedAsset, transactionsByNetwork, parseTransaction]) + + const fullAssetBalances = React.useMemo(() => { + if (selectedAsset?.contractAddress === '') { + return filteredAssetList.find( + (asset) => + asset.asset.symbol.toLowerCase() === selectedAsset?.symbol.toLowerCase() && + asset.asset.chainId === selectedAsset?.chainId + ) + } + return filteredAssetList.find( + (asset) => + asset.asset.contractAddress.toLowerCase() === selectedAsset?.contractAddress.toLowerCase() && + asset.asset.chainId === selectedAsset?.chainId + ) + }, [filteredAssetList, selectedAsset]) + + const fullAssetFiatBalance = React.useMemo(() => fullAssetBalances?.assetBalance + ? computeFiatAmount( + fullAssetBalances.assetBalance, + fullAssetBalances.asset.symbol, + fullAssetBalances.asset.decimals + ) + : Amount.empty(), + [fullAssetBalances] + ) + + const formattedFullAssetBalance = fullAssetBalances?.assetBalance + ? '(' + new Amount(fullAssetBalances?.assetBalance ?? '') + .divideByDecimals(selectedAsset?.decimals ?? 18) + .formatAsAsset(6, selectedAsset?.symbol ?? '') + ')' + : '' + + const isNftAsset = selectedAssetFromParams?.isErc721 + + // methods + const onChangeTimeline = React.useCallback((timeline: BraveWallet.AssetPriceTimeframe) => { + dispatch(WalletPageActions.selectAsset({ + asset: selectedAsset, + timeFrame: timeline + })) + }, [selectedAsset]) + + const goBack = React.useCallback(() => { + // if (nftMetadata) { + // dispatch(WalletPageActions.updateNFTMetadata(undefined)) + // } + dispatch(WalletPageActions.selectAsset({ asset: undefined, timeFrame: selectedTimeline })) + history.push(WalletRoutes.Portfolio) + setfilteredAssetList(userAssetList) + }, [ + userAssetList, + selectedTimeline + ]) + + const onUpdateBalance = React.useCallback((value: number | undefined) => { + setHoverPrice(value ? new Amount(value).formatAsFiat() : undefined) + }, []) + + const onToggleHideBalances = React.useCallback(() => { + setHideBalances(prevHideBalances => !prevHideBalances) + }, []) + + // effect + React.useEffect(() => { + setfilteredAssetList(userAssetList) + }, [userAssetList]) + + React.useEffect(() => { + if (selectedAssetFromParams) { + // load token data + dispatch(WalletPageActions.selectAsset({ asset: selectedAssetFromParams, timeFrame: selectedTimeline })) + } + }, [selectedAssetFromParams]) + + // token list needs to load before we can find an asset to select from the url params + if (userVisibleTokensInfo.length === 0) { + return + } + + // asset not found + if (!selectedAssetFromParams) { + return + } + + // render + return ( + + + + + + + {!isNftAsset && + + } + + + + + {!isNftAsset && + + + + + {selectedAssetFromParams.name} + {selectedAssetFromParams.symbol} on {selectedAssetsNetwork?.chainName ?? ''} + + + {/* {selectedAsset.name} {getLocale('braveWalletPrice')} ({selectedAsset.symbol}) */} + + {CurrencySymbols[defaultCurrencies.fiat]}{hoverPrice || (selectedAssetFiatPrice ? new Amount(selectedAssetFiatPrice.price).formatAsFiat() : 0.00)} + + + {selectedAssetFiatPrice ? Number(selectedAssetFiatPrice.assetTimeframeChange).toFixed(2) : 0.00}% + + + + { + selectedAssetCryptoPrice + ? new Amount(selectedAssetCryptoPrice.price) + .formatAsAsset(undefined, defaultCurrencies.crypto) + : '' + } + + + } + + {!isNftAsset && + + } + + {/* {selectedAsset?.isErc721 && + + } */} + + + + ) +} + +export default PortfolioAsset diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-overview.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-overview.tsx new file mode 100644 index 000000000000..d751885ae193 --- /dev/null +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-overview.tsx @@ -0,0 +1,248 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useHistory } from 'react-router' + +// Constants +import { + BraveWallet, + UserAssetInfoType, + WalletState, + PageState, + SupportedTestNetworks, + WalletRoutes +} from '../../../../constants/types' +import { getLocale } from '../../../../../common/locale' +import { CurrencySymbols } from '../../../../utils/currency-symbols' + +// Utils +import { getTokensCoinType } from '../../../../utils/network-utils' +import Amount from '../../../../utils/amount' + +// Options +import { ChartTimelineOptions } from '../../../../options/chart-timeline-options' +import { AllNetworksOption } from '../../../../options/network-filter-options' + +// Components +import { LoadingSkeleton } from '../../../shared' +import { + ChartControlBar, + LineChart, + WithHideBalancePlaceholder +} from '../../' + +// import NFTDetails from './components/nft-details' +import TokenLists from './components/token-lists' + +// Hooks +import { useBalance, usePricing } from '../../../../common/hooks' + +// Styled Components +import { + StyledWrapper, + TopRow, + BalanceTitle, + BalanceText, + BalanceRow, + ShowBalanceButton +} from './style' + +// actions +import { WalletActions } from '../../../../common/actions' +import { WalletPageActions } from '../../../../page/actions' + +export const PortfolioOverview = () => { + // routing + const history = useHistory() + + // redux + const dispatch = useDispatch() + const { + defaultCurrencies, + userVisibleTokensInfo, + portfolioPriceHistory, + selectedPortfolioTimeline, + accounts, + networkList, + isFetchingPortfolioPriceHistory, + transactionSpotPrices, + selectedNetworkFilter + } = useSelector(({ wallet }: { wallet: WalletState }) => wallet) + + const selectedTimeline = useSelector(({ page }: { page: PageState }) => page.selectedTimeline) + + // custom hooks + const getAccountAssetBalance = useBalance(networkList) + const { computeFiatAmount } = usePricing(transactionSpotPrices) + + // memos / computed + + // This will scrape all the user's accounts and combine the asset balances for a single asset + const fullAssetBalance = React.useCallback((asset: BraveWallet.BlockchainToken) => { + const tokensCoinType = getTokensCoinType(networkList, asset) + const amounts = accounts + .filter((account) => account.coin === tokensCoinType) + .map((account) => getAccountAssetBalance(account, asset)) + + // If a user has not yet created a FIL or SOL account, + // we return 0 until they create an account + if (amounts.length === 0) { + return '0' + } + + return amounts.reduce(function (a, b) { + return a !== '' && b !== '' + ? new Amount(a).plus(b).format() + : '' + }) + }, [accounts, networkList, getAccountAssetBalance]) + + // This looks at the users asset list and returns the full balance for each asset + const userAssetList = React.useMemo(() => { + const allAssets = userVisibleTokensInfo.map((asset) => ({ + asset: asset, + assetBalance: fullAssetBalance(asset) + }) as UserAssetInfoType) + // By default we dont show any testnetwork assets + if (selectedNetworkFilter.chainId === AllNetworksOption.chainId) { + return allAssets.filter((asset) => !SupportedTestNetworks.includes(asset.asset.chainId)) + } + // If chainId is Localhost we also do a check for coinType to return + // the correct asset + if (selectedNetworkFilter.chainId === BraveWallet.LOCALHOST_CHAIN_ID) { + return allAssets.filter((asset) => + asset.asset.chainId === selectedNetworkFilter.chainId && + getTokensCoinType(networkList, asset.asset) === selectedNetworkFilter.coin + ) + } + // Filter by all other assets by chainId's + return allAssets.filter((asset) => asset.asset.chainId === selectedNetworkFilter.chainId) + }, [userVisibleTokensInfo, selectedNetworkFilter, fullAssetBalance, networkList]) + + // This will scrape all of the user's accounts and combine the fiat value for every asset + const fullPortfolioFiatBalance = React.useMemo(() => { + const visibleAssetOptions = userAssetList + .filter((token) => + token.asset.visible && + !token.asset.isErc721 + ) + + if (visibleAssetOptions.length === 0) { + return '' + } + + const visibleAssetFiatBalances = visibleAssetOptions + .map((item) => { + return computeFiatAmount(item.assetBalance, item.asset.symbol, item.asset.decimals) + }) + + const grandTotal = visibleAssetFiatBalances.reduce(function (a, b) { + return a.plus(b) + }) + return grandTotal.formatAsFiat() + }, [userAssetList, computeFiatAmount]) + + const priceHistory = React.useMemo(() => { + if (parseFloat(fullPortfolioFiatBalance) === 0) { + return [] + } + return portfolioPriceHistory + }, [portfolioPriceHistory, fullPortfolioFiatBalance]) + + // state + const [filteredAssetList, setfilteredAssetList] = React.useState(userAssetList) + const [hoverBalance, setHoverBalance] = React.useState() + const [hideBalances, setHideBalances] = React.useState(false) + + // methods + const onChangeTimeline = React.useCallback((timeline: BraveWallet.AssetPriceTimeframe) => { + dispatch(WalletActions.selectPortfolioTimeline(timeline)) + }, []) + + const onSelectAsset = React.useCallback((asset: BraveWallet.BlockchainToken) => () => { + if (asset.contractAddress === '') { + history.push(`${WalletRoutes.Portfolio}/${asset.symbol}`) + return + } + history.push(`${WalletRoutes.Portfolio}/${asset.contractAddress}`) + + dispatch(WalletPageActions.selectAsset({ asset, timeFrame: selectedTimeline })) + }, [selectedTimeline]) + + const onUpdateBalance = React.useCallback((value: number | undefined) => { + setHoverBalance(value ? new Amount(value).formatAsFiat() : undefined) + }, []) + + const onToggleHideBalances = React.useCallback(() => { + setHideBalances(!hideBalances) + }, [hideBalances]) + + // effects + React.useEffect(() => { + setfilteredAssetList(userAssetList) + }, [userAssetList]) + + React.useEffect(() => { + dispatch(WalletPageActions.selectAsset({ asset: undefined, timeFrame: selectedTimeline })) + }, [selectedTimeline]) + + // render + return ( + + + + {getLocale('braveWalletBalance')} + + + + + + + + + + {fullPortfolioFiatBalance !== '' + ? `${CurrencySymbols[defaultCurrencies.fiat]}${hoverBalance || fullPortfolioFiatBalance}` + : + } + + + + + + + + ) +} + +export default PortfolioOverview diff --git a/components/brave_wallet_ui/components/desktop/wallet-more-popup/index.tsx b/components/brave_wallet_ui/components/desktop/wallet-more-popup/index.tsx index f8882569d5ad..09c0777cc827 100644 --- a/components/brave_wallet_ui/components/desktop/wallet-more-popup/index.tsx +++ b/components/brave_wallet_ui/components/desktop/wallet-more-popup/index.tsx @@ -1,5 +1,17 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' +import { useDispatch } from 'react-redux' + +// actions +import { WalletActions } from '../../../common/actions' + +// utils import { getLocale } from '../../../../common/locale' + // Styled Components import { StyledWrapper, @@ -14,51 +26,60 @@ import { export interface Props { onClickSetting?: () => void - onClickLock?: () => void onClickViewOnBlockExplorer?: () => void onClickBackup?: () => void } const WalletMorePopup = (props: Props) => { const { - onClickLock, onClickSetting, onClickViewOnBlockExplorer, onClickBackup } = props - const onClickConnectedSites = () => { + // redux + const dispatch = useDispatch() + + // methods + const lockWallet = React.useCallback(() => { + dispatch(WalletActions.lockWallet()) + }, []) + + const onClickConnectedSites = React.useCallback(() => { chrome.tabs.create({ url: 'brave://settings/content/ethereum' }, () => { if (chrome.runtime.lastError) { console.error('tabs.create failed: ' + chrome.runtime.lastError.message) } }) - } + }, []) return ( - {onClickLock && - - - {getLocale('braveWalletWalletPopupLock')} - - } + + + + {getLocale('braveWalletWalletPopupLock')} + + {onClickBackup && {getLocale('braveWalletBackupButton')} } + {getLocale('braveWalletWalletPopupConnectedSites')} + {onClickSetting && {getLocale('braveWalletWalletPopupSettings')} } + {onClickViewOnBlockExplorer && diff --git a/components/brave_wallet_ui/components/desktop/wallet-onboarding/create-password/index.tsx b/components/brave_wallet_ui/components/desktop/wallet-onboarding/create-password/index.tsx index f16279e67d66..0dc98d919287 100644 --- a/components/brave_wallet_ui/components/desktop/wallet-onboarding/create-password/index.tsx +++ b/components/brave_wallet_ui/components/desktop/wallet-onboarding/create-password/index.tsx @@ -1,5 +1,29 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' +import { useDispatch } from 'react-redux' +import { useHistory } from 'react-router' + +// utils +import { getLocale } from '../../../../../common/locale' + +// actions +import { WalletPageActions } from '../../../../page/actions' + +// constants +import { WalletRoutes } from '../../../../constants/types' + +// hooks +import { usePasswordStrength } from '../../../../common/hooks/use-password-strength' +// components +import { PasswordInput } from '../../../shared' +import { NavButton } from '../../../extension' + +// style import { StyledWrapper, Title, @@ -8,42 +32,53 @@ import { InputColumn, Description } from './style' -import { PasswordInput } from '../../../shared' -import { NavButton } from '../../../extension' -import { getLocale } from '../../../../../common/locale' -export interface Props { - onSubmit: () => void - onPasswordChanged: (value: string) => void - onConfirmPasswordChanged: (value: string) => void - disabled: boolean - hasPasswordError: boolean - hasConfirmPasswordError: boolean -} +export const OnboardingCreatePassword = () => { + // routing + const history = useHistory() + + // redux + const dispatch = useDispatch() -function OnboardingCreatePassword (props: Props) { + // custom hooks const { - onSubmit, onPasswordChanged, - onConfirmPasswordChanged, - disabled, - hasConfirmPasswordError, - hasPasswordError - } = props + password, + hasConfirmedPasswordError, + hasPasswordError, + confirmedPassword, + setConfirmedPassword + } = usePasswordStrength() + + // computed + const disabled = hasConfirmedPasswordError || + hasPasswordError || + password === '' || + confirmedPassword === '' + + // methods + const onSubmit = React.useCallback(() => { + dispatch(WalletPageActions.createWallet({ password })) + history.push(WalletRoutes.OnboardingBackupWallet) + }, [password]) - const handleKeyDown = (event: React.KeyboardEvent) => { + const handleKeyDown = React.useCallback((event: React.KeyboardEvent) => { if (event.key === 'Enter' && !disabled) { onSubmit() } - } + }, [onSubmit, disabled]) + // render return ( + + {getLocale('braveWalletCreatePasswordTitle')} {getLocale('braveWalletCreatePasswordDescription')} + - + + + ) } diff --git a/components/brave_wallet_ui/components/desktop/wallet-onboarding/import-meta-mask-or-legacy/index.tsx b/components/brave_wallet_ui/components/desktop/wallet-onboarding/import-meta-mask-or-legacy/index.tsx index a79e9d76038b..f64ff2832bef 100644 --- a/components/brave_wallet_ui/components/desktop/wallet-onboarding/import-meta-mask-or-legacy/index.tsx +++ b/components/brave_wallet_ui/components/desktop/wallet-onboarding/import-meta-mask-or-legacy/index.tsx @@ -1,8 +1,25 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useHistory, useLocation } from 'react-router' + +// actions +import { WalletPageActions } from '../../../../page/actions' + +// utils +import { getLocale } from '../../../../../common/locale' + +// types import { - WalletOnboardingSteps, - ImportWalletError + PageState, + WalletRoutes } from '../../../../constants/types' + +// components import { StyledWrapper, Title, @@ -17,70 +34,155 @@ import { import { Checkbox } from 'brave-ui' import { PasswordInput } from '../../../shared' import { NavButton } from '../../../extension' -import { getLocale } from '../../../../../common/locale' -export interface Props { - onSubmit: () => void - onPasswordChanged: (value: string) => void - onConfirmPasswordChanged: (value: string) => void - onImportPasswordChanged: (value: string) => void - onClickLost: () => void - onUseSamePassword: (selected: boolean) => void - password: string - confirmedPassword: string - useSamePassword: boolean - importError: ImportWalletError - hasPasswordError: boolean - hasConfirmPasswordError: boolean - disabled: boolean - onboardingStep: WalletOnboardingSteps - needsNewPassword: boolean - useSamePasswordVerified: boolean - importPassword: string -} +// hooks +import { usePasswordStrength } from '../../../../common/hooks/use-password-strength' + +function OnboardingImportMetaMaskOrLegacy () { + // routing + const history = useHistory() + const { pathname: currentRoute } = useLocation() -function OnboardingImportMetaMaskOrLegacy (props: Props) { + // redux + const dispatch = useDispatch() + const importWalletError = useSelector(({ page }: { page: PageState }) => page.importWalletError) + + // state + const [needsNewPassword, setNeedsNewPassword] = React.useState(false) + const [useSamePassword, setUseSamePassword] = React.useState(false) + const [useSamePasswordVerified, setUseSamePasswordVerified] = React.useState(false) + const [importPassword, setImportPassword] = React.useState('') + const [isStrongImportPassword, setIsStrongImportPassword] = React.useState(false) + + // custom hooks const { - onSubmit, - onPasswordChanged, - onConfirmPasswordChanged, - onImportPasswordChanged, - onClickLost, - onUseSamePassword, - useSamePasswordVerified, - importPassword, - password, + checkIsStrongPassword, confirmedPassword, - useSamePassword, + hasConfirmedPasswordError, hasPasswordError, - hasConfirmPasswordError, - importError, - onboardingStep, - disabled, - needsNewPassword - } = props - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === 'Enter' && !disabled) { - onSubmit() + onPasswordChanged, + password, + setConfirmedPassword + } = usePasswordStrength() + + // memos + React.useMemo(() => { + if (importWalletError.hasError) { + setUseSamePassword(false) + setNeedsNewPassword(false) + setUseSamePasswordVerified(false) + } + }, [importWalletError]) + + // computed + const isMetaMask = currentRoute === WalletRoutes.OnboardingImportMetaMask + + const isCreateWalletDisabled = hasConfirmedPasswordError || + hasPasswordError || + password === '' || + confirmedPassword === '' + + const isImportDisabled = isCreateWalletDisabled || importPassword === '' + + // methods + const setImportWalletError = React.useCallback((hasError: boolean) => { + dispatch(WalletPageActions.setImportWalletError({ hasError })) + }, []) + + const handleImportPasswordChanged = React.useCallback(async (value: string) => { + if (importWalletError.hasError) { + setImportWalletError(false) } - } - const isMetaMask = onboardingStep === WalletOnboardingSteps.OnboardingImportMetaMask + if (needsNewPassword || useSamePasswordVerified) { + setNeedsNewPassword(false) + setUseSamePassword(false) + } - const onSelectUseSamePassword = (key: string, selected: boolean) => { + setImportPassword(value) + + const isStrong = await checkIsStrongPassword(value) + setIsStrongImportPassword(isStrong) + }, [ + importWalletError, + needsNewPassword, + useSamePasswordVerified, + setImportWalletError, + checkIsStrongPassword, + setIsStrongImportPassword + ]) + + const onImportMetaMask = React.useCallback((password: string, newPassword: string) => { + dispatch(WalletPageActions.importFromMetaMask({ password, newPassword })) + }, []) + + const onImportCryptoWallets = React.useCallback((password: string, newPassword: string) => { + dispatch(WalletPageActions.importFromCryptoWallets({ password, newPassword })) + }, []) + + const onImport = React.useCallback( + () => { + if (currentRoute === WalletRoutes.OnboardingImportMetaMask) { + onImportMetaMask(importPassword, confirmedPassword) + } else { + onImportCryptoWallets(importPassword, confirmedPassword) + } + }, + [ + currentRoute, + importPassword, + confirmedPassword, + onImportMetaMask, + onImportCryptoWallets + ] + ) + + const onClickLost = React.useCallback(() => { + history.push(WalletRoutes.OnboardingCreatePassword) + }, []) + + const handleKeyDown = React.useCallback((event: React.KeyboardEvent) => { + if (event.key === 'Enter' && !isImportDisabled) { + onImport() + } + }, [isImportDisabled, onImport]) + + const onSelectUseSamePassword = React.useCallback((key: string, selected: boolean) => { if (key === 'useSamePassword') { - onUseSamePassword(selected) + setUseSamePassword(selected) } - } + }, []) + // effects + React.useEffect(() => { + if (useSamePassword) { + onPasswordChanged(importPassword) + setConfirmedPassword(importPassword) + if (!isStrongImportPassword) { + setNeedsNewPassword(true) + setUseSamePasswordVerified(false) + } else { + setNeedsNewPassword(false) + setUseSamePasswordVerified(true) + } + } else { + onPasswordChanged('') + setConfirmedPassword('') + setNeedsNewPassword(false) + setUseSamePasswordVerified(false) + } + }, [useSamePassword]) + + // render return ( + {isMetaMask ? ( ) : ( )} + {getLocale('braveWalletImportTitle').replace('$1', isMetaMask @@ -88,6 +190,7 @@ function OnboardingImportMetaMaskOrLegacy (props: Props) { : getLocale('braveWalletImportBraveLegacyTitle') )} + {getLocale('braveWalletImportDescription').replace('$1', isMetaMask @@ -95,15 +198,17 @@ function OnboardingImportMetaMaskOrLegacy (props: Props) { : getLocale('braveWalletImportBraveLegacyTitle') )} + + {!useSamePasswordVerified && {needsNewPassword @@ -111,6 +216,7 @@ function OnboardingImportMetaMaskOrLegacy (props: Props) { : getLocale('braveWalletImportFromExternalCreatePassword')} } + {!needsNewPassword && @@ -118,6 +224,7 @@ function OnboardingImportMetaMaskOrLegacy (props: Props) { } + {!useSamePasswordVerified && <> {getLocale('braveWalletCreatePasswordDescription')} @@ -133,17 +240,26 @@ function OnboardingImportMetaMaskOrLegacy (props: Props) { } - + + + {!isMetaMask && - {getLocale('braveWalletImportBraveLegacyAltButton')} + + {getLocale('braveWalletImportBraveLegacyAltButton')} + } ) diff --git a/components/brave_wallet_ui/components/desktop/wallet-onboarding/restore/index.tsx b/components/brave_wallet_ui/components/desktop/wallet-onboarding/restore/index.tsx index c69c60c22ba4..dc415b4e03d7 100644 --- a/components/brave_wallet_ui/components/desktop/wallet-onboarding/restore/index.tsx +++ b/components/brave_wallet_ui/components/desktop/wallet-onboarding/restore/index.tsx @@ -1,16 +1,21 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' import { useDispatch, useSelector } from 'react-redux' import { useHistory, useLocation } from 'react-router' +// Utils +import { getLocale } from '../../../../../common/locale' +import { copyToClipboard } from '../../../../utils/copy-to-clipboard' + // Components import { PasswordInput, BackButton } from '../../../shared' import { NavButton } from '../../../extension' import { Checkbox } from 'brave-ui' -// Utils -import { getLocale } from '../../../../../common/locale' -import { copyToClipboard } from '../../../../utils/copy-to-clipboard' - // Styles import { StyledWrapper, @@ -26,7 +31,7 @@ import { } from './style' // hooks -import { useLib } from '../../../../common/hooks/useLib' +import { usePasswordStrength } from '../../../../common/hooks/use-password-strength' import * as WalletPageActions from '../../../../page/actions/wallet_page_actions' import { PageState, WalletRoutes, WalletState } from '../../../../constants/types' @@ -38,30 +43,62 @@ export const OnboardingRestore = () => { // redux const dispatch = useDispatch() - const { - isWalletCreated, - isWalletLocked - } = useSelector(({ wallet }: { wallet: WalletState }) => wallet) - const { invalidMnemonic } = useSelector(({ page }: { page: PageState }) => page) + const isWalletCreated = useSelector(({ wallet }: { wallet: WalletState }) => wallet.isWalletCreated) + const isWalletLocked = useSelector(({ wallet }: { wallet: WalletState }) => wallet.isWalletLocked) + const invalidMnemonic = useSelector(({ page }: { page: PageState }) => page.invalidMnemonic) // custom hooks - const { isStrongPassword: checkIsStrongPassword } = useLib() + const { + hasConfirmedPasswordError, + hasPasswordError, + password, + onPasswordChanged: handlePasswordChanged, + setConfirmedPassword: handleConfirmPasswordChanged, + isValid: isPasswordValid + } = usePasswordStrength() // state const [showRecoveryPhrase, setShowRecoveryPhrase] = React.useState(false) const [isLegacyWallet, setIsLegacyWallet] = React.useState(false) - const [isStrongPassword, setIsStrongPassword] = React.useState(false) - const [password, setPassword] = React.useState('') - const [confirmedPassword, setConfirmedPassword] = React.useState('') const [recoveryPhrase, setRecoveryPhrase] = React.useState('') + // memos + const isValidRecoveryPhrase = React.useMemo(() => { + if (recoveryPhrase.trim().split(/\s+/g).length >= 12) { + return true + } else { + return false + } + }, [recoveryPhrase]) + + // computed + const isDisabled = !isValidRecoveryPhrase || !isPasswordValid + // methods - const onBack = () => { + const toggleShowRestore = React.useCallback(() => { + if (walletLocation === WalletRoutes.Restore) { + // If a user has not yet created a wallet and clicks Restore + // from the panel, we need to route to onboarding if they click back. + if (!isWalletCreated) { + history.push(WalletRoutes.Onboarding) + return + } + // If a user has created a wallet and clicks Restore from the panel + // while the wallet is locked, we need to route to unlock if they click back. + if (isWalletCreated && isWalletLocked) { + history.push(WalletRoutes.Unlock) + } + } else { + history.push(WalletRoutes.Restore) + } + }, [walletLocation, isWalletCreated, isWalletLocked]) + + const onBack = React.useCallback(() => { toggleShowRestore() setRecoveryPhrase('') - } + }, [toggleShowRestore]) - const onSubmitRestore = () => { + const onSubmitRestore = React.useCallback(async () => { dispatch(WalletPageActions.restoreWallet({ // added an additional trim here in case the phrase length is // 12, 15, 18 or 21 long and has a space at the end. @@ -69,19 +106,10 @@ export const OnboardingRestore = () => { password, isLegacy: isLegacyWallet })) - } - - const handlePasswordChanged = async (value: string) => { - setPassword(value) - const isStrong = await checkIsStrongPassword(value) - setIsStrongPassword(isStrong) - } - - const handleConfirmPasswordChanged = (value: string) => { - setConfirmedPassword(value) - } + history.push(WalletRoutes.Portfolio) + }, [recoveryPhrase, password, isLegacyWallet]) - const handleRecoveryPhraseChanged = (event: React.ChangeEvent) => { + const handleRecoveryPhraseChanged = React.useCallback((event: React.ChangeEvent) => { const value = event.target.value // This prevents there from being a space at the begining of the phrase. @@ -100,74 +128,29 @@ export const OnboardingRestore = () => { } else { setRecoveryPhrase(removePeriod) } - } + }, []) - const handleKeyDown = (event: React.KeyboardEvent) => { + const handleKeyDown = React.useCallback((event: React.KeyboardEvent) => { if (event.key === 'Enter' && !isDisabled) { onSubmitRestore() } - } + }, [onSubmitRestore, isDisabled]) - const onShowRecoveryPhrase = (key: string, selected: boolean) => { + const onShowRecoveryPhrase = React.useCallback((key: string, selected: boolean) => { if (key === 'showPhrase') { setShowRecoveryPhrase(selected) } - } + }, []) - const onSetIsLegacyWallet = (key: string, selected: boolean) => { + const onSetIsLegacyWallet = React.useCallback((key: string, selected: boolean) => { if (key === 'isLegacy') { setIsLegacyWallet(selected) } - } + }, []) - const onClearClipboard = () => { + const onClearClipboard = React.useCallback(() => { copyToClipboard('') - } - - const toggleShowRestore = React.useCallback(() => { - if (walletLocation === WalletRoutes.Restore) { - // If a user has not yet created a wallet and clicks Restore - // from the panel, we need to route to onboarding if they click back. - if (!isWalletCreated) { - history.push(WalletRoutes.Onboarding) - return - } - // If a user has created a wallet and clicks Restore from the panel - // while the wallet is locked, we need to route to unlock if they click back. - if (isWalletCreated && isWalletLocked) { - history.push(WalletRoutes.Unlock) - } - } else { - history.push(WalletRoutes.Restore) - } - }, [walletLocation, isWalletCreated]) - - // memos - const isValidRecoveryPhrase = React.useMemo(() => { - if (recoveryPhrase.trim().split(/\s+/g).length >= 12) { - return false - } else { - return true - } - }, [recoveryPhrase]) - - const checkPassword = React.useMemo(() => { - if (password === '') { - return false - } - return !isStrongPassword - }, [password, isStrongPassword]) - - const checkConfirmedPassword = React.useMemo(() => { - if (confirmedPassword === '') { - return false - } else { - return confirmedPassword !== password - } - }, [confirmedPassword, password]) - - // computed - const isDisabled = isValidRecoveryPhrase || checkConfirmedPassword || checkPassword || password === '' || confirmedPassword === '' + }, []) // effects React.useEffect(() => { @@ -185,9 +168,12 @@ export const OnboardingRestore = () => { return ( <> + + {getLocale('braveWalletRestoreTite')} {getLocale('braveWalletRestoreDescription')} + { autoComplete='off' onPaste={onClearClipboard} /> + {invalidMnemonic && {getLocale('braveWalletRestoreError')}} + {recoveryPhrase.split(' ').length === 24 && @@ -206,36 +194,41 @@ export const OnboardingRestore = () => { } +
{getLocale('braveWalletRestoreShowPhrase')}
+ {getLocale('braveWalletRestoreFormText')} {getLocale('braveWalletCreatePasswordDescription')} +
+ +
) diff --git a/components/brave_wallet_ui/components/desktop/wallet-onboarding/verify/index.tsx b/components/brave_wallet_ui/components/desktop/wallet-onboarding/verify/index.tsx index 5ce89b00dc82..f2e181ec9f94 100644 --- a/components/brave_wallet_ui/components/desktop/wallet-onboarding/verify/index.tsx +++ b/components/brave_wallet_ui/components/desktop/wallet-onboarding/verify/index.tsx @@ -1,4 +1,5 @@ import * as React from 'react' +import { useSelector } from 'react-redux' import { StyledWrapper, @@ -15,20 +16,22 @@ import { PlaceholderBubble } from './style' import { NavButton } from '../../../extension' -import { RecoveryObject } from '../../../../constants/types' +import { PageState, RecoveryObject } from '../../../../constants/types' import { getLocale } from '../../../../../common/locale' import { DropBoundary } from '../../../shared/drop-boundary' import { unbiasedRandom } from '../../../../utils/random-utils' export interface Props { - recoveryPhrase: string[] onNextStep: () => void } export function OnboardingVerify ({ - onNextStep, - recoveryPhrase + onNextStep }: Props) { + // redux + const mnemonic = useSelector(({ page }: { page: PageState }) => page?.mnemonic) + const recoveryPhrase = React.useMemo(() => (mnemonic || '').split(' '), [mnemonic]) + // state const [hasVerifyError, setVerifyError] = React.useState(false) const [sortedPhrase, setSortedPhrase] = React.useState([]) diff --git a/components/brave_wallet_ui/components/desktop/wallet-onboarding/welcome/index.tsx b/components/brave_wallet_ui/components/desktop/wallet-onboarding/welcome/index.tsx index d23364ef8707..1a989c020426 100644 --- a/components/brave_wallet_ui/components/desktop/wallet-onboarding/welcome/index.tsx +++ b/components/brave_wallet_ui/components/desktop/wallet-onboarding/welcome/index.tsx @@ -1,5 +1,19 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' +import { useSelector } from 'react-redux' +import { useHistory } from 'react-router' + +// utils +import { getLocale, splitStringForTag } from '../../../../../common/locale' +// types +import { PageState, WalletRoutes } from '../../../../constants/types' + +// style import { StyledWrapper, Title, @@ -15,38 +29,62 @@ import { CryptoWalletsAlertTitle, CryptoWalletsAlertDescription } from './style' + +// components import { NavButton } from '../../../extension' -import { getLocale, splitStringForTag } from '../../../../../common/locale' -export interface Props { - onSetup: () => void - onRestore: () => void - onClickImportMetaMask: () => void - isMetaMaskInitialized: boolean - isCryptoWalletsInitialized: boolean -} +export const OnboardingWelcome = () => { + // routing + const history = useHistory() + + // redux + const { + isCryptoWalletsInitialized, + isMetaMaskInitialized + } = useSelector(({ page }: { page: PageState }) => page) + + // methods + const onRestore = React.useCallback(() => { + history.push(WalletRoutes.Restore) + }, []) -function OnboardingWelcome (props: Props) { - const { onRestore, onSetup, onClickImportMetaMask, isMetaMaskInitialized, isCryptoWalletsInitialized } = props + const onClickImportMetaMask = React.useCallback(() => { + history.push(WalletRoutes.OnboardingImportMetaMask) + }, []) - const onClickSettings = () => { + const onClickSettings = React.useCallback(() => { chrome.tabs.create({ url: 'chrome://settings/wallet' }, () => { if (chrome.runtime.lastError) { console.error('tabs.create failed: ' + chrome.runtime.lastError.message) } }) - } + }, []) - const walletAlertText = getLocale('braveWalletCryptoWalletsDescriptionTwo') - const { beforeTag, duringTag, afterTag } = splitStringForTag(walletAlertText) + const onSetup = React.useCallback(() => { + if (isCryptoWalletsInitialized) { + history.push(WalletRoutes.OnboardingImportCryptoWallets) + return + } + history.push(WalletRoutes.OnboardingCreatePassword) + }, [isCryptoWalletsInitialized]) + + // computed + const { beforeTag, duringTag, afterTag } = splitStringForTag( + getLocale('braveWalletCryptoWalletsDescriptionTwo') + ) return ( + + {getLocale('braveWalletWelcomeTitle')} {getLocale('braveWalletWelcomeDescription')} + + {getLocale('braveWalletWelcomeRestoreButton')} + {isMetaMaskInitialized && <> @@ -56,6 +94,7 @@ function OnboardingWelcome (props: Props) { } + {isCryptoWalletsInitialized && {getLocale('braveWalletCryptoWalletsDetected')} diff --git a/components/brave_wallet_ui/components/extension/connected-header/index.tsx b/components/brave_wallet_ui/components/extension/connected-header/index.tsx index 6294b83ed55d..d8820ad7e057 100644 --- a/components/brave_wallet_ui/components/extension/connected-header/index.tsx +++ b/components/brave_wallet_ui/components/extension/connected-header/index.tsx @@ -1,5 +1,13 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' +// utils +import { getLocale } from '../../../../common/locale' + // Styled Components import { HeaderTitle, @@ -7,27 +15,28 @@ import { ActionIcon, ExpandIcon } from './style' + +// components import { WalletMorePopup } from '../../desktop' -import { getLocale } from '../../../../common/locale' export interface Props { onExpand: () => void onClickMore: () => void - onClickLock: () => void onClickSetting: () => void onClickViewOnBlockExplorer: () => void showMore: boolean } -const ConnectedHeader = (props: Props) => { +export const ConnectedHeader = (props: Props) => { const { - onClickLock, onClickMore, onClickSetting, onExpand, onClickViewOnBlockExplorer, showMore } = props + + // render return ( @@ -35,7 +44,6 @@ const ConnectedHeader = (props: Props) => { {showMore && diff --git a/components/brave_wallet_ui/components/extension/connected-panel/index.tsx b/components/brave_wallet_ui/components/extension/connected-panel/index.tsx index 64cec56c4c95..9dd97a531dd8 100644 --- a/components/brave_wallet_ui/components/extension/connected-panel/index.tsx +++ b/components/brave_wallet_ui/components/extension/connected-panel/index.tsx @@ -1,4 +1,29 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' +import { create, background } from 'ethereum-blockies' + +// Utils +import { getLocale } from '../../../../common/locale' +import { reduceAddress } from '../../../utils/reduce-address' +import { copyToClipboard } from '../../../utils/copy-to-clipboard' +import { reduceAccountDisplayName } from '../../../utils/reduce-account-name' +import Amount from '../../../utils/amount' + +// Hooks +import { useExplorer, usePricing } from '../../../common/hooks' + +// types +import { + WalletAccountType, + PanelTypes, + BraveWallet, + BuySupportedChains, + DefaultCurrencies +} from '../../../constants/types' // Components import { @@ -25,25 +50,6 @@ import { MoreAssetsButton } from './style' -// Utils -import { reduceAddress } from '../../../utils/reduce-address' -import { copyToClipboard } from '../../../utils/copy-to-clipboard' -import { reduceAccountDisplayName } from '../../../utils/reduce-account-name' -import Amount from '../../../utils/amount' - -// Hooks -import { useExplorer, usePricing } from '../../../common/hooks' - -import { - WalletAccountType, - PanelTypes, - BraveWallet, - BuySupportedChains, - DefaultCurrencies -} from '../../../constants/types' -import { create, background } from 'ethereum-blockies' -import { getLocale } from '../../../../common/locale' - export interface Props { spotPrices: BraveWallet.AssetPrice[] selectedAccount: WalletAccountType @@ -53,14 +59,12 @@ export interface Props { isSwapSupported: boolean defaultCurrencies: DefaultCurrencies navAction: (path: PanelTypes) => void - onLockWallet: () => void onOpenSettings: () => void } -const ConnectedPanel = (props: Props) => { +export const ConnectedPanel = (props: Props) => { const { spotPrices, - onLockWallet, onOpenSettings, isConnected, isSwapSupported, @@ -70,34 +74,42 @@ const ConnectedPanel = (props: Props) => { originInfo, defaultCurrencies } = props + + // state const [showMore, setShowMore] = React.useState(false) - const navigate = (path: PanelTypes) => () => { + // custom hooks + const { computeFiatAmount } = usePricing(spotPrices) + const onClickViewOnBlockExplorer = useExplorer(selectedNetwork) + + // methods + const navigate = React.useCallback((path: PanelTypes) => () => { navAction(path) - } + }, [navAction]) - const onExpand = () => { + const onExpand = React.useCallback(() => { navAction('expanded') - } + }, [navAction]) - const onShowSitePermissions = () => { + const onShowSitePermissions = React.useCallback(() => { navAction('sitePermissions') - } + }, [navAction]) - const onShowMore = () => { + const onShowMore = React.useCallback(() => { setShowMore(true) - } + }, []) - const onHideMore = () => { + const onHideMore = React.useCallback(() => { if (showMore) { setShowMore(false) } - } + }, [showMore]) - const onCopyToClipboard = async () => { + const onCopyToClipboard = React.useCallback(async () => { await copyToClipboard(selectedAccount.address) - } + }, [selectedAccount.address]) + // memos const bg = React.useMemo(() => { return background({ seed: selectedAccount.address.toLowerCase() }) }, [selectedAccount.address]) @@ -110,23 +122,20 @@ const ConnectedPanel = (props: Props) => { return !BuySupportedChains.includes(selectedNetwork.chainId) }, [BuySupportedChains, selectedNetwork]) - const formattedAssetBalance = new Amount(selectedAccount.nativeBalanceRegistry[selectedNetwork.chainId] ?? '') - .divideByDecimals(selectedNetwork.decimals) - .formatAsAsset(6, selectedNetwork.symbol) - - const { computeFiatAmount } = usePricing(spotPrices) - const selectedAccountFiatBalance = React.useMemo(() => computeFiatAmount( selectedAccount.nativeBalanceRegistry[selectedNetwork.chainId], selectedNetwork.symbol, selectedNetwork.decimals ), [computeFiatAmount, selectedNetwork, selectedAccount]) - const onClickViewOnBlockExplorer = useExplorer(selectedNetwork) + // computed + const formattedAssetBalance = new Amount(selectedAccount.nativeBalanceRegistry[selectedNetwork.chainId] ?? '') + .divideByDecimals(selectedNetwork.decimals) + .formatAsAsset(6, selectedNetwork.symbol) + // render return ( { + return ( + + requirement ? ( + children + ) : ( + + ) + } + /> + ) +} + +export default ProtectedRoute diff --git a/components/brave_wallet_ui/constants/types.ts b/components/brave_wallet_ui/constants/types.ts index b161e6c6566a..cd3716cab8fc 100644 --- a/components/brave_wallet_ui/constants/types.ts +++ b/components/brave_wallet_ui/constants/types.ts @@ -266,7 +266,6 @@ export interface PageState { showAddModal: boolean isCryptoWalletsInitialized: boolean isMetaMaskInitialized: boolean - } export interface WalletPageState { @@ -558,26 +557,22 @@ export type UpdateAccountNamePayloadType = { isDerived: boolean } -export enum WalletOnboardingSteps { - OnboardingWelcome = 0, - OnboardingCreatePassword = 1, - OnboardingBackupWallet = 2, - OnboardingImportMetaMask = 3, - OnboardingImportCryptoWallets = 4 -} - export enum WalletRoutes { - Unlock = '/crypto/unlock', - Onboarding = '/crypto/onboarding', - Restore = '/crypto/restore-wallet', - Portfolio = '/crypto/portfolio', - PortfolioSub = '/crypto/portfolio/:id?', Accounts = '/crypto/accounts', + AccountsSub = '/crypto/accounts/:id?', AddAccountModal = '/crypto/accounts/add-account', AddAssetModal = '/crypto/portfolio/add-asset', - AccountsSub = '/crypto/accounts/:id?', Backup = '/crypto/backup-wallet', - CryptoPage = '/crypto/:category/:id?' + CryptoPage = '/crypto/:category/:id?', + Onboarding = '/crypto/onboarding', + OnboardingCreatePassword = '/crypto/onboarding/create-password', + OnboardingBackupWallet = '/crypto/onboarding/backup-wallet', + OnboardingImportMetaMask = '/crypto/onboarding/import-metamask-wallet', + OnboardingImportCryptoWallets = '/crypto/onboarding/import-legacy-wallet', + Portfolio = '/crypto/portfolio', + PortfolioSub = '/crypto/portfolio/:id', + Restore = '/crypto/restore-wallet', + Unlock = '/crypto/unlock', } export const WalletOrigin = 'chrome://wallet' diff --git a/components/brave_wallet_ui/options/top-nav-options.ts b/components/brave_wallet_ui/options/top-nav-options.ts index 6c8b3c5087d5..df59a1b7f92c 100644 --- a/components/brave_wallet_ui/options/top-nav-options.ts +++ b/components/brave_wallet_ui/options/top-nav-options.ts @@ -1,3 +1,8 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import { TopTabNavObjectType } from '../constants/types' import { getLocale } from '../../common/locale' @@ -16,3 +21,5 @@ export const TopNavOptions = (): TopTabNavObjectType[] => [ // name: getLocale('braveWalletTopTabApps') // } ] + +export const TOP_NAV_OPTIONS = TopNavOptions() diff --git a/components/brave_wallet_ui/page/actions/wallet_page_actions.ts b/components/brave_wallet_ui/page/actions/wallet_page_actions.ts index 43c6508cd90a..ffa82c5ac011 100644 --- a/components/brave_wallet_ui/page/actions/wallet_page_actions.ts +++ b/components/brave_wallet_ui/page/actions/wallet_page_actions.ts @@ -9,7 +9,6 @@ import { WalletCreatedPayloadType, RecoveryWordsAvailablePayloadType, PrivateKeyAvailablePayloadType, - RestoreWalletPayloadType, SelectAssetPayloadType, UpdateSelectedAssetType, ImportAccountPayloadType, @@ -19,7 +18,8 @@ import { ImportAccountFromJsonPayloadType, ImportFromExternalWalletPayloadType, ImportWalletErrorPayloadType, - ImportFilecoinAccountPayloadType + ImportFilecoinAccountPayloadType, + RestoreWalletPayloadType } from '../constants/action_types' import { BraveWallet, diff --git a/components/brave_wallet_ui/page/async/wallet_page_async_handler.ts b/components/brave_wallet_ui/page/async/wallet_page_async_handler.ts index dcd3b0f10349..c72fc85a9fea 100644 --- a/components/brave_wallet_ui/page/async/wallet_page_async_handler.ts +++ b/components/brave_wallet_ui/page/async/wallet_page_async_handler.ts @@ -2,6 +2,7 @@ // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // you can obtain one at http://mozilla.org/MPL/2.0/. + import getWalletPageApiProxy from '../wallet_page_api_proxy' import AsyncActionHandler from '../../../common/AsyncActionHandler' import * as WalletPageActions from '../actions/wallet_page_actions' @@ -13,7 +14,6 @@ import { } from '../../constants/types' import { CreateWalletPayloadType, - RestoreWalletPayloadType, UpdateSelectedAssetType, ImportAccountPayloadType, RemoveImportedAccountPayloadType, @@ -21,7 +21,8 @@ import { ViewPrivateKeyPayloadType, ImportAccountFromJsonPayloadType, ImportFromExternalWalletPayloadType, - ImportFilecoinAccountPayloadType + ImportFilecoinAccountPayloadType, + RestoreWalletPayloadType } from '../constants/action_types' import { findHardwareAccountInfo, @@ -59,6 +60,7 @@ handler.on(WalletPageActions.restoreWallet.getType(), async (store: Store, paylo keyringService.notifyWalletBackupComplete() await refreshWalletInfo(store) store.dispatch(WalletPageActions.setShowIsRestoring(false)) + store.dispatch(WalletPageActions.walletSetupComplete()) }) handler.on(WalletPageActions.showRecoveryPhrase.getType(), async (store: Store, payload: boolean) => { diff --git a/components/brave_wallet_ui/page/container.tsx b/components/brave_wallet_ui/page/container.tsx index b662c6ca2929..47b549a8188e 100644 --- a/components/brave_wallet_ui/page/container.tsx +++ b/components/brave_wallet_ui/page/container.tsx @@ -4,55 +4,50 @@ // you can obtain one at http://mozilla.org/MPL/2.0/. import * as React from 'react' -import { connect } from 'react-redux' -import { bindActionCreators, Dispatch } from 'redux' -import { Route, Switch, useHistory, useLocation } from 'react-router-dom' +import { useDispatch, useSelector } from 'react-redux' +import { Redirect, Route, Switch, useHistory, useLocation } from 'react-router-dom' +// actions import * as WalletPageActions from './actions/wallet_page_actions' import * as WalletActions from '../common/actions/wallet_actions' -import store from './store' -import 'emptykit.css' -import '../../../ui/webui/resources/fonts/poppins.css' -import '../../../ui/webui/resources/fonts/muli.css' - -import { OnboardingWrapper, WalletWidgetStandIn } from '../stories/style' -import { CryptoView, LockScreen, OnboardingRestore, WalletPageLayout, WalletSubViewLayout } from '../components/desktop' +// types import { BraveWallet, BuySendSwapTypes, PageState, UpdateAccountNamePayloadType, WalletAccountType, - WalletPageState, WalletRoutes, WalletState } from '../constants/types' + +// style +import 'emptykit.css' +import '../../../ui/webui/resources/fonts/poppins.css' +import '../../../ui/webui/resources/fonts/muli.css' +import { OnboardingWrapper, WalletWidgetStandIn } from '../stories/style' + +// components +import { CryptoView, LockScreen, OnboardingRestore, WalletPageLayout, WalletSubViewLayout } from '../components/desktop' import BuySendSwap from '../stories/screens/buy-send-swap' import Onboarding from '../stories/screens/onboarding' import BackupWallet from '../stories/screens/backup-wallet' import { SweepstakesBanner } from '../components/desktop/sweepstakes-banner' - -import { - getBalance, - getBuyAssetUrl, - onConnectHardwareWallet -} from '../common/async/lib' +import { Skeleton } from '../components/shared/loading-skeleton/styles' // Hooks -import { useAssets } from '../common/hooks' - -type Props = { - wallet: WalletState - page: PageState - walletPageActions: typeof WalletPageActions - walletActions: typeof WalletActions -} +import { useAssets, useLib } from '../common/hooks' +import ProtectedRoute from '../components/shared/protected-routing/protected-route' -function Container (props: Props) { +export const Container = () => { + // routing let history = useHistory() const { pathname: walletLocation } = useLocation() - // Wallet Props + + // redux + const dispatch = useDispatch() + const { isFilecoinEnabled, isSolanaEnabled, @@ -61,40 +56,32 @@ function Container (props: Props) { isWalletBackedUp, hasIncorrectPassword, accounts, - networkList, - transactions, selectedNetwork, selectedAccount, hasInitialized, - transactionSpotPrices, defaultWallet, - isMetaMaskInstalled, - defaultCurrencies, - fullTokenList, - userVisibleTokensInfo - } = props.wallet + isMetaMaskInstalled + } = useSelector(({ wallet }: { wallet: WalletState }) => wallet) - // Page Props - const { - mnemonic, - selectedTimeline, - selectedAsset, - setupStillInProgress, - privateKey, - importAccountError, - showAddModal - } = props.page - - // const [view, setView] = React.useState('crypto') + const setupStillInProgress = useSelector(({ page }: { page: PageState }) => page.setupStillInProgress) + const importAccountError = useSelector(({ page }: { page: PageState }) => page.importAccountError) + + // state + const [sessionRoute, setSessionRoute] = React.useState(undefined) const [inputValue, setInputValue] = React.useState('') const [buyAmount, setBuyAmount] = React.useState('') const [selectedWidgetTab, setSelectedWidgetTab] = React.useState('buy') - const [showVisibleAssetsModal, setShowVisibleAssetsModal] = React.useState(false) - const [sessionRoute, setSessionRoute] = React.useState(undefined) const [selectedBuyOption, setSelectedBuyOption] = React.useState(BraveWallet.OnRampProvider.kRamp) + // custom hooks const { buyAssetOptions, wyreAssetOptions, rampAssetOptions } = useAssets() + const { + getBalance, + getBuyAssetUrl, + onConnectHardwareWallet + } = useLib() + // methods const onToggleShowRestore = React.useCallback(() => { if (walletLocation === WalletRoutes.Restore) { // If a user has not yet created a wallet and clicks Restore @@ -118,65 +105,44 @@ function Container (props: Props) { } const onSelectAccount = (account: WalletAccountType) => { - props.walletActions.selectAccount(account) - } - - const onBackupWallet = () => { - props.walletPageActions.walletBackupComplete() - history.goBack() + dispatch(WalletActions.selectAccount(account)) } - const unlockWallet = () => { - props.walletActions.unlockWallet({ password: inputValue }) + const unlockWallet = React.useCallback(() => { + dispatch(WalletActions.unlockWallet({ password: inputValue })) setInputValue('') - } - - const lockWallet = () => { - props.walletActions.lockWallet() - } - - const onShowBackup = () => { - props.walletPageActions.showRecoveryPhrase(true) - history.push(WalletRoutes.Backup) - } + if (sessionRoute) { + history.push(sessionRoute) + } else { + history.push(WalletRoutes.Portfolio) + } + }, [inputValue, sessionRoute]) - const onHideBackup = () => { - props.walletPageActions.showRecoveryPhrase(false) + const onHideBackup = React.useCallback(() => { + dispatch(WalletPageActions.showRecoveryPhrase(false)) history.goBack() - } + }, []) - const handlePasswordChanged = (value: string) => { + const handlePasswordChanged = React.useCallback((value: string) => { setInputValue(value) if (hasIncorrectPassword) { - props.walletActions.hasIncorrectPassword(false) + dispatch(WalletActions.hasIncorrectPassword(false)) } - } - - const recoveryPhrase = React.useMemo(() => { - return (mnemonic || '').split(' ') - }, [mnemonic]) - - const onSelectAsset = (asset: BraveWallet.BlockchainToken) => { - props.walletPageActions.selectAsset({ asset: asset, timeFrame: selectedTimeline }) - } - - const onShowAddModal = () => { - props.walletPageActions.setShowAddModal(true) - } + }, [hasIncorrectPassword]) - const onHideAddModal = () => { - props.walletPageActions.setShowAddModal(false) - } + const onHideAddModal = React.useCallback(() => { + dispatch(WalletPageActions.setShowAddModal(false)) + }, []) - const onCreateAccount = (name: string, coin: BraveWallet.CoinType) => { - const created = props.walletActions.addAccount({ accountName: name, coin: coin }) + const onCreateAccount = React.useCallback((name: string, coin: BraveWallet.CoinType) => { + const created = dispatch(WalletActions.addAccount({ accountName: name, coin: coin })) if (walletLocation.includes(WalletRoutes.Accounts)) { history.push(WalletRoutes.Accounts) } if (created) { onHideAddModal() } - } + }, [onHideAddModal]) const onSubmitBuy = (asset: BraveWallet.BlockchainToken) => { getBuyAssetUrl({ @@ -190,133 +156,63 @@ function Container (props: Props) { .catch(e => console.error(e)) } - const onAddHardwareAccounts = (selected: BraveWallet.HardwareWalletAccount[]) => { - props.walletPageActions.addHardwareAccounts(selected) - } + const onAddHardwareAccounts = React.useCallback((selected: BraveWallet.HardwareWalletAccount[]) => { + dispatch(WalletPageActions.addHardwareAccounts(selected)) + }, []) - const onImportAccount = (accountName: string, privateKey: string, coin: BraveWallet.CoinType) => { - props.walletPageActions.importAccount({ accountName, privateKey, coin }) - } + const onImportAccount = React.useCallback((accountName: string, privateKey: string, coin: BraveWallet.CoinType) => { + dispatch(WalletPageActions.importAccount({ accountName, privateKey, coin })) + }, []) - const onImportFilecoinAccount = (accountName: string, privateKey: string, network: string) => { - props.walletPageActions.importFilecoinAccount({ accountName, privateKey, network }) - } + const onImportFilecoinAccount = React.useCallback((accountName: string, privateKey: string, network: string) => { + dispatch(WalletPageActions.importFilecoinAccount({ accountName, privateKey, network })) + }, []) - const onImportAccountFromJson = (accountName: string, password: string, json: string) => { - props.walletPageActions.importAccountFromJson({ accountName, password, json }) - } + const onImportAccountFromJson = React.useCallback((accountName: string, password: string, json: string) => { + dispatch(WalletPageActions.importAccountFromJson({ accountName, password, json })) + }, []) - const onSetImportAccountError = (hasError: boolean) => { - props.walletPageActions.setImportAccountError(hasError) - } + const onSetImportAccountError = React.useCallback((hasError: boolean) => { + dispatch(WalletPageActions.setImportAccountError(hasError)) + }, []) - const onRemoveAccount = (address: string, hardware: boolean, coin: BraveWallet.CoinType) => { + const onRemoveAccount = React.useCallback((address: string, hardware: boolean, coin: BraveWallet.CoinType) => { if (hardware) { - props.walletPageActions.removeHardwareAccount({ address, coin }) + dispatch(WalletPageActions.removeHardwareAccount({ address, coin })) return } - props.walletPageActions.removeImportedAccount({ address, coin }) - } + dispatch(WalletPageActions.removeImportedAccount({ address, coin })) + }, []) - const onUpdateAccountName = (payload: UpdateAccountNamePayloadType): { success: boolean } => { - const result = props.walletPageActions.updateAccountName(payload) + const onUpdateAccountName = React.useCallback((payload: UpdateAccountNamePayloadType): { success: boolean } => { + const result = dispatch(WalletPageActions.updateAccountName(payload)) return result ? { success: true } : { success: false } - } - - const fetchFullTokenList = () => { - props.walletActions.getAllTokensList() - } - - const onViewPrivateKey = (address: string, isDefault: boolean, coin: BraveWallet.CoinType) => { - props.walletPageActions.viewPrivateKey({ address, isDefault, coin }) - } + }, []) - const onDoneViewingPrivateKey = () => { - props.walletPageActions.doneViewingPrivateKey() - } + const onViewPrivateKey = React.useCallback((address: string, isDefault: boolean, coin: BraveWallet.CoinType) => { + dispatch(WalletPageActions.viewPrivateKey({ address, isDefault, coin })) + }, []) - const checkWalletsToImport = () => { - props.walletPageActions.checkWalletsToImport() - } + const onDoneViewingPrivateKey = React.useCallback(() => { + dispatch(WalletPageActions.doneViewingPrivateKey()) + }, []) - const onOpenWalletSettings = () => { - props.walletPageActions.openWalletSettings() - } + const onOpenWalletSettings = React.useCallback(() => { + dispatch(WalletPageActions.openWalletSettings()) + }, []) - const onShowVisibleAssetsModal = (showModal: boolean) => { - if (showModal) { - if (fullTokenList.length === 0) { - fetchFullTokenList() - } - history.push(`${WalletRoutes.AddAssetModal}`) - } else { - history.push(`${WalletRoutes.Portfolio}`) - } - setShowVisibleAssetsModal(showModal) - } + // computed + const walletNotYetCreated = (!isWalletCreated || setupStillInProgress) && walletLocation !== WalletRoutes.Restore + const hideMainComponents = + (isWalletCreated && !setupStillInProgress) && + !isWalletLocked && + walletLocation !== WalletRoutes.Backup - React.useEffect(() => { - // Creates a list of Accepted Portfolio Routes - const acceptedPortfolioRoutes = userVisibleTokensInfo.map((token) => { - if (token.contractAddress === '') { - return `${WalletRoutes.Portfolio}/${token.symbol}` - } - return `${WalletRoutes.Portfolio}/${token.contractAddress}` - }) - // Creates a list of Accepted Account Routes - const acceptedAccountRoutes = accounts.map((account) => { - return `${WalletRoutes.Accounts}/${account.address}` - }) - - const allAcceptedRoutes = [ - WalletRoutes.Backup, - WalletRoutes.Accounts, - WalletRoutes.AddAccountModal, - WalletRoutes.AddAssetModal, - WalletRoutes.Portfolio, - ...acceptedPortfolioRoutes, - ...acceptedAccountRoutes - ] - - if (allAcceptedRoutes.includes(walletLocation)) { - setSessionRoute(walletLocation) - } - - if (!hasInitialized) { - return - } - // If wallet is not yet created will Route to Onboarding - if ((!isWalletCreated || setupStillInProgress) && walletLocation !== WalletRoutes.Restore) { - checkWalletsToImport() - history.push(WalletRoutes.Onboarding) - // If wallet is created will Route to login - } else if (isWalletLocked && walletLocation !== WalletRoutes.Restore) { - history.push(WalletRoutes.Unlock) - // Allowed Private Routes if a wallet is unlocked else will redirect back to Portfolio - } else if ( - !isWalletLocked && - hasInitialized && - !allAcceptedRoutes.includes(walletLocation) && - acceptedAccountRoutes.length !== 0 && - acceptedPortfolioRoutes.length !== 0 - ) { - if (sessionRoute) { - history.push(sessionRoute) - } else { - history.push(WalletRoutes.Portfolio) - } - } - }, [ - walletLocation, - isWalletCreated, - isWalletLocked, - hasInitialized, - setupStillInProgress, - selectedAsset, - userVisibleTokensInfo, - accounts - ]) + const walletUnlockNeeded = (isWalletCreated && !setupStillInProgress) && isWalletLocked && + walletLocation !== WalletRoutes.Unlock && + walletLocation !== WalletRoutes.Restore + // effects React.useEffect(() => { if (hasIncorrectPassword) { // Clear incorrect password @@ -324,7 +220,22 @@ function Container (props: Props) { } }, [hasIncorrectPassword]) - const hideMainComponents = (isWalletCreated && !setupStillInProgress) && !isWalletLocked && walletLocation !== WalletRoutes.Backup + React.useEffect(() => { + // store the last url before wallet lock + // so that we can return to that page after unlock + if ( + !walletLocation.includes(WalletRoutes.Onboarding) && + walletLocation !== WalletRoutes.Unlock && + walletLocation !== WalletRoutes.Restore // can be accessed from unlock screen + ) { + setSessionRoute(walletLocation) + } + }, [walletLocation]) + + // render + if (!hasInitialized) { + return + } return ( @@ -333,85 +244,90 @@ function Container (props: Props) { selectedButton={view} onSubmit={navigateTo} /> */} - - - - {isWalletLocked && - - - - } - - + + + + + + + + + + - - {isWalletLocked && - - - - } - + + + + + + + + {/* If wallet is not yet created will Route to Onboarding */} + {walletNotYetCreated && } + + {/* Redirect to unlock screen if needed */} + {walletUnlockNeeded && } + - - {hideMainComponents && - - } - - - + + {/* Default */} + + + + + + + {hideMainComponents && { - return { - page: state.page, - wallet: state.wallet - } -} - -function mapDispatchToProps (dispatch: Dispatch): Partial { - return { - walletPageActions: bindActionCreators(WalletPageActions, store.dispatch.bind(store)), - walletActions: bindActionCreators(WalletActions, store.dispatch.bind(store)) - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(Container) +export default Container diff --git a/components/brave_wallet_ui/panel/container.tsx b/components/brave_wallet_ui/panel/container.tsx index 6cdd4e515f7c..ee2f794f1698 100644 --- a/components/brave_wallet_ui/panel/container.tsx +++ b/components/brave_wallet_ui/panel/container.tsx @@ -270,21 +270,22 @@ function Container (props: Props) { props.walletActions.unlockWallet({ password: inputValue }) setInputValue('') } - const onLockWallet = () => { - props.walletActions.lockWallet() - } + const handlePasswordChanged = (value: string) => { setInputValue(value) if (hasIncorrectPassword) { props.walletActions.hasIncorrectPassword(false) } } + const onRestore = () => { props.walletPanelActions.expandRestoreWallet() } + const onSetup = () => { props.walletPanelActions.setupWallet() } + const addToFavorites = (app: BraveWallet.AppItem) => { props.walletActions.addFavoriteApp(app) } @@ -976,7 +977,6 @@ function Container (props: Props) { selectedNetwork={getNetworkInfo(selectedNetwork.chainId, networkList)} isConnected={isConnectedToSite} navAction={navigateTo} - onLockWallet={onLockWallet} onOpenSettings={onOpenSettings} originInfo={activeOrigin} isSwapSupported={isSwapSupported} diff --git a/components/brave_wallet_ui/stories/mock-data/user-accounts.ts b/components/brave_wallet_ui/stories/mock-data/user-accounts.ts index c8ba0a0c618c..734f03c5adf3 100644 --- a/components/brave_wallet_ui/stories/mock-data/user-accounts.ts +++ b/components/brave_wallet_ui/stories/mock-data/user-accounts.ts @@ -1,3 +1,8 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + import { UserAccountType } from '../../constants/types' export const mockUserAccounts: UserAccountType[] = [ @@ -35,3 +40,5 @@ export const mockRecoveryPhrase = [ 'glasses', 'sound' ] + +export const mockedMnemonic = mockRecoveryPhrase.join(' ') diff --git a/components/brave_wallet_ui/stories/screens/backup-wallet.tsx b/components/brave_wallet_ui/stories/screens/backup-wallet.tsx index 3e7f678f9bfd..5eafe101fbb1 100644 --- a/components/brave_wallet_ui/stories/screens/backup-wallet.tsx +++ b/components/brave_wallet_ui/stories/screens/backup-wallet.tsx @@ -1,60 +1,99 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useHistory } from 'react-router' + +// components import { OnboardingBackup, OnboardingRecovery, OnboardingVerify } from '../../components/desktop' - import { BackButton } from '../../components/shared' + +// utils import { copyToClipboard } from '../../utils/copy-to-clipboard' +// actions +import { WalletPageActions } from '../../page/actions' + +// types +import { PageState, WalletRoutes } from '../../constants/types' + export interface Props { - recoveryPhrase: string[] - onSubmit: () => void onCancel: () => void - onBack?: () => void isOnboarding: boolean } const recoverPhraseCopiedTimeout = 5000 // 5s -export function BackupWallet (props: Props) { - const { recoveryPhrase, isOnboarding, onSubmit, onCancel, onBack } = props +export const BackupWallet = (props: Props) => { + const { isOnboarding, onCancel } = props + + // routing + const history = useHistory() + + // redux + const dispatch = useDispatch() + const mnemonic = useSelector(({ page }: { page: PageState }) => (page?.mnemonic || '')) + const recoveryPhrase = React.useMemo(() => (mnemonic || '').split(' '), [mnemonic]) + + // state const [backupStep, setBackupStep] = React.useState(0) const [backupTerms, setBackupTerms] = React.useState(false) const [backedUp, setBackedUp] = React.useState(false) - const [isRecoverPhraseCopied, setIsRecoverPhraseCopied] = React.useState(false) - const nextStep = () => { + // methods + const onSubmit = React.useCallback(() => { + dispatch(WalletPageActions.walletBackupComplete()) + dispatch(WalletPageActions.walletSetupComplete()) + + if (isOnboarding) { + history.push(WalletRoutes.Portfolio) + } else { + history.goBack() + } + }, [isOnboarding]) + + const nextStep = React.useCallback(() => { if (backupStep === 2) { onSubmit() return } setBackupStep(backupStep + 1) - } + }, [backupStep, onSubmit]) - const onGoBack = () => { - if (onBack && isOnboarding && backupStep === 0) { - onBack() + const onGoBack = React.useCallback(() => { + if (isOnboarding && backupStep === 0) { + history.goBack() } else { setBackupStep(backupStep - 1) } - } + }, [isOnboarding, backupStep]) - const checkedBox = (key: string, selected: boolean) => { + const checkedBox = React.useCallback((key: string, selected: boolean) => { if (key === 'backupTerms') { setBackupTerms(selected) } if (key === 'backedUp') { setBackedUp(selected) } - } + }, []) - const onCopyToClipboard = async () => { + const onCopyToClipboard = React.useCallback(async () => { await copyToClipboard(recoveryPhrase.join(' ')) setIsRecoverPhraseCopied(true) - } + }, [recoveryPhrase]) + + // effects + React.useEffect(() => { + dispatch(WalletPageActions.showRecoveryPhrase(true)) + }, []) React.useEffect(() => { if (isRecoverPhraseCopied) { @@ -66,6 +105,7 @@ export function BackupWallet (props: Props) { return () => {} }, [isRecoverPhraseCopied]) + // render return ( <> {backupStep !== 0 && @@ -92,7 +132,7 @@ export function BackupWallet (props: Props) { /> } {backupStep === 2 && - + } ) diff --git a/components/brave_wallet_ui/stories/screens/crypto-story-view.tsx b/components/brave_wallet_ui/stories/screens/crypto-story-view.tsx index be6af1890c37..957deb4d8b7e 100644 --- a/components/brave_wallet_ui/stories/screens/crypto-story-view.tsx +++ b/components/brave_wallet_ui/stories/screens/crypto-story-view.tsx @@ -11,10 +11,7 @@ import { BraveWallet, AppsListType, UserAssetInfoType, - AccountTransactions, WalletAccountType, - UpdateAccountNamePayloadType, - DefaultCurrencies, AddAccountNavTypes } from '../../constants/types' import { TopNavOptions } from '../../options/top-nav-options' @@ -23,34 +20,25 @@ import { SearchBar, AppList } from '../../components/shared' import { getLocale } from '../../../common/locale' import { AppsList } from '../../options/apps-list-options' import { filterAppList } from '../../utils/filter-app-list' -import { PortfolioView, AccountsView } from '../../components/desktop/views' +import { Accounts, PortfolioOverview } from '../../components/desktop/views' import { HardwareWalletConnectOpts } from '../../components/desktop/popup-modals/add-account-modal/hardware-wallet-connect/types' +import WalletPageStory from '../wrappers/wallet-page-story-wrapper' export interface Props { - defaultCurrencies: DefaultCurrencies selectedNetwork: BraveWallet.NetworkInfo showAddModal: boolean userAssetList: UserAssetInfoType[] - transactions: AccountTransactions - networkList: BraveWallet.NetworkInfo[] accounts: WalletAccountType[] needsBackup: boolean - userVisibleTokensInfo: BraveWallet.BlockchainToken[] - privateKey: string - transactionSpotPrices: BraveWallet.AssetPrice[] hasImportError: boolean isFilecoinEnabled: boolean isSolanaEnabled: boolean - onLockWallet: () => void onSetImportError: (hasError: boolean) => void onImportAccountFromJson: (accountName: string, password: string, json: string) => void - onDoneViewingPrivateKey: () => void - onViewPrivateKey: (address: string, isDefault: boolean, coin: BraveWallet.CoinType) => void onRemoveAccount: (address: string, hardware: boolean, coin: BraveWallet.CoinType) => void onToggleAddModal: () => void - onUpdateAccountName: (payload: UpdateAccountNamePayloadType) => { success: boolean } getBalance: (address: string) => Promise onAddHardwareAccounts: (selected: BraveWallet.HardwareWalletAccount[]) => void onConnectHardwareWallet: (opts: HardwareWalletConnectOpts) => Promise @@ -58,28 +46,17 @@ export interface Props { onImportFilecoinAccount: (accountName: string, privateKey: string, network: string) => void onCreateAccount: (name: string) => void onShowBackup: () => void - onShowVisibleAssetsModal: (value: boolean) => void - showVisibleAssetsModal: boolean } const CryptoStoryView = (props: Props) => { const { - defaultCurrencies, hasImportError, - userVisibleTokensInfo, - transactionSpotPrices, - privateKey, selectedNetwork, needsBackup, accounts, - networkList, - transactions, showAddModal, - showVisibleAssetsModal, isFilecoinEnabled, isSolanaEnabled, - onShowVisibleAssetsModal, - onLockWallet, onShowBackup, onCreateAccount, onConnectHardwareWallet, @@ -87,17 +64,14 @@ const CryptoStoryView = (props: Props) => { getBalance, onImportAccount, onImportFilecoinAccount, - onUpdateAccountName, onToggleAddModal, onRemoveAccount, - onViewPrivateKey, - onDoneViewingPrivateKey, onImportAccountFromJson, onSetImportError } = props const [showBackupWarning, setShowBackupWarning] = React.useState(needsBackup) const [showDefaultWalletBanner, setShowDefaultWalletBanner] = React.useState(needsBackup) - const [selectedAccount, setSelectedAccount] = React.useState() + const [, setSelectedAccount] = React.useState() const [hideNav, setHideNav] = React.useState(false) const [filteredAppsList, setFilteredAppsList] = React.useState(AppsList()) const [favoriteApps, setFavoriteApps] = React.useState([ @@ -147,11 +121,6 @@ const CryptoStoryView = (props: Props) => { onToggleAddModal() } - const goBack = () => { - setSelectedAccount(undefined) - toggleNav() - } - const onSelectAccount = (account: WalletAccountType) => { setSelectedAccount(account) toggleNav() @@ -177,104 +146,87 @@ const CryptoStoryView = (props: Props) => { } return ( - - {!hideNav && - <> - - {showDefaultWalletBanner && - + + {!hideNav && + <> + - } - - {needsBackup && showBackupWarning && - + } + + {needsBackup && showBackupWarning && + + } + + } + {selectedTab === 'apps' && + <> + + - } - - } - {selectedTab === 'apps' && - <> - + } + {selectedTab === 'portfolio' && + + } + {selectedTab === 'accounts' && + - - - } - {selectedTab === 'portfolio' && - - } - {selectedTab === 'accounts' && - - } - {showAddModal && - - } - + } + + ) } diff --git a/components/brave_wallet_ui/stories/screens/onboarding.tsx b/components/brave_wallet_ui/stories/screens/onboarding.tsx index ae80f4d155aa..b35f318aff05 100644 --- a/components/brave_wallet_ui/stories/screens/onboarding.tsx +++ b/components/brave_wallet_ui/stories/screens/onboarding.tsx @@ -1,309 +1,82 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { useHistory, useLocation } from 'react-router' +import { useDispatch } from 'react-redux' +import { Redirect, Route, Switch, useHistory } from 'react-router' +// components import { OnboardingWelcome, OnboardingCreatePassword, OnboardingImportMetaMaskOrLegacy } from '../../components/desktop' -import { - WalletOnboardingSteps, - PageState, - WalletRoutes, - WalletState -} from '../../constants/types' import { BackButton } from '../../components/shared' import BackupWallet from './backup-wallet' + +// types +import { WalletRoutes } from '../../constants/types' + +// style import { OnboardingWrapper } from '../style' -import { useLib } from '../../common/hooks/useLib' +// actions import * as WalletPageActions from '../../page/actions/wallet_page_actions' export const Onboarding = () => { // routing let history = useHistory() - const { pathname: walletLocation } = useLocation() - - // custom hooks - const { isStrongPassword: checkIsStrongPassword } = useLib() // redux const dispatch = useDispatch() - const { - isWalletCreated, - isWalletLocked - } = useSelector(({ wallet }: { wallet: WalletState }) => wallet) - - const { - mnemonic, - isCryptoWalletsInitialized, - isMetaMaskInitialized, - importWalletError: importError - } = useSelector(({ page }: { page: PageState }) => page) - - // state - const [onboardingStep, setOnboardingStep] = React.useState(WalletOnboardingSteps.OnboardingWelcome) - const [isStrongPassword, setIsStrongPassword] = React.useState(false) - const [isStrongImportPassword, setIsStrongImportPassword] = React.useState(false) - const [importPassword, setImportPassword] = React.useState('') - const [password, setPassword] = React.useState('') - const [confirmedPassword, setConfirmedPassword] = React.useState('') - const [useSamePassword, setUseSamePassword] = React.useState(false) - const [needsNewPassword, setNeedsNewPassword] = React.useState(false) - const [useSamePasswordVerified, setUseSamePasswordVerified] = React.useState(false) // methods - const onImportMetaMask = React.useCallback((password: string, newPassword: string) => { - dispatch(WalletPageActions.importFromMetaMask({ password, newPassword })) - }, []) - - const onImportCryptoWallets = React.useCallback((password: string, newPassword: string) => { - dispatch(WalletPageActions.importFromCryptoWallets({ password, newPassword })) - }, []) - - const setImportWalletError = React.useCallback((hasError: boolean) => { - dispatch(WalletPageActions.setImportWalletError({ hasError })) - }, []) - - const onShowRestore = React.useCallback(() => { - if (walletLocation === WalletRoutes.Restore) { - // If a user has not yet created a wallet and clicks Restore - // from the panel, we need to route to onboarding if they click back. - if (!isWalletCreated) { - history.push(WalletRoutes.Onboarding) - return - } - // If a user has created a wallet and clicks Restore from the panel - // while the wallet is locked, we need to route to unlock if they click back. - if (isWalletCreated && isWalletLocked) { - history.push(WalletRoutes.Unlock) - } - } else { - history.push(WalletRoutes.Restore) - } - }, [walletLocation, isWalletCreated, isWalletLocked]) - - const onPasswordProvided = React.useCallback((password: string) => { - dispatch(WalletPageActions.createWallet({ password })) - }, []) - - const completeWalletSetup = React.useCallback((recoveryVerified: boolean) => { - if (recoveryVerified) { - dispatch(WalletPageActions.walletBackupComplete()) - } - dispatch(WalletPageActions.walletSetupComplete()) - }, []) - - const nextStep = React.useCallback(() => { - if (onboardingStep === WalletOnboardingSteps.OnboardingWelcome && isCryptoWalletsInitialized) { - setOnboardingStep(WalletOnboardingSteps.OnboardingImportCryptoWallets) - return - } - if (onboardingStep === WalletOnboardingSteps.OnboardingBackupWallet) { - completeWalletSetup(true) - return - } - if (onboardingStep === WalletOnboardingSteps.OnboardingCreatePassword) { - onPasswordProvided(password) - } - setOnboardingStep(onboardingStep + 1) - }, [onboardingStep, completeWalletSetup, onPasswordProvided, password, isCryptoWalletsInitialized]) - - const onBack = React.useCallback(() => { - if ( - onboardingStep === WalletOnboardingSteps.OnboardingImportCryptoWallets || - onboardingStep === WalletOnboardingSteps.OnboardingImportMetaMask - ) { - setOnboardingStep(WalletOnboardingSteps.OnboardingWelcome) - setPassword('') - setConfirmedPassword('') - setNeedsNewPassword(false) - setUseSamePassword(false) - return - } - setOnboardingStep(onboardingStep - 1) - }, [onboardingStep]) - const onSkipBackup = React.useCallback(() => { - completeWalletSetup(false) - }, [completeWalletSetup]) - - const handleImportPasswordChanged = React.useCallback(async (value: string) => { - if (importError.hasError) { - setImportWalletError(false) - } - if (needsNewPassword || useSamePasswordVerified) { - setNeedsNewPassword(false) - setUseSamePassword(false) - } - setImportPassword(value) - const isStrong = await checkIsStrongPassword(value) - setIsStrongImportPassword(isStrong) - }, [importError, needsNewPassword, useSamePasswordVerified, setImportWalletError]) - - const handlePasswordChanged = React.useCallback(async (value: string) => { - setPassword(value) - const isStrong = await checkIsStrongPassword(value) - setIsStrongPassword(isStrong) - }, []) - - const onClickImportMetaMask = React.useCallback(() => { - setOnboardingStep(WalletOnboardingSteps.OnboardingImportMetaMask) - }, []) - - const onImport = React.useCallback( - () => { - if (onboardingStep === WalletOnboardingSteps.OnboardingImportMetaMask) { - onImportMetaMask(importPassword, confirmedPassword) - } else { - onImportCryptoWallets(importPassword, confirmedPassword) - } - }, - [ - onboardingStep, - importPassword, - confirmedPassword, - onImportMetaMask, - onImportCryptoWallets - ] - ) - - const startNormalOnboarding = React.useCallback(() => { - setOnboardingStep(WalletOnboardingSteps.OnboardingCreatePassword) + dispatch(WalletPageActions.walletSetupComplete()) + history.push(WalletRoutes.Portfolio) }, []) - // memos - React.useMemo(() => { - if (importError.hasError) { - setPassword('') - setConfirmedPassword('') - setUseSamePassword(false) - setNeedsNewPassword(false) - setUseSamePasswordVerified(false) - } - }, [importError]) - - const recoveryPhrase = React.useMemo(() => { - return (mnemonic || '').split(' ') - }, [mnemonic]) - - const showBackButton = React.useMemo(() => { - if ( - onboardingStep === WalletOnboardingSteps.OnboardingCreatePassword || - onboardingStep === WalletOnboardingSteps.OnboardingImportMetaMask || - onboardingStep === WalletOnboardingSteps.OnboardingImportCryptoWallets - ) { - return true - } else { - return false - } - }, [onboardingStep]) - - const checkPassword = React.useMemo(() => { - if (password === '') { - return false - } - return !isStrongPassword - }, [password, isStrongPassword]) - - const checkConfirmedPassword = React.useMemo(() => { - if (confirmedPassword === '') { - return false - } else { - return confirmedPassword !== password - } - }, [confirmedPassword, password]) - // effects React.useEffect(() => { - if (useSamePassword) { - handlePasswordChanged(importPassword) - setConfirmedPassword(importPassword) - if (!isStrongImportPassword) { - setNeedsNewPassword(true) - setUseSamePasswordVerified(false) - } else { - setNeedsNewPassword(false) - setUseSamePasswordVerified(true) - } - } else { - setPassword('') - setConfirmedPassword('') - setNeedsNewPassword(false) - setUseSamePasswordVerified(false) - setIsStrongPassword(false) - } - }, [useSamePassword]) - - // computed - const isImporting = onboardingStep === WalletOnboardingSteps.OnboardingImportMetaMask || - onboardingStep === WalletOnboardingSteps.OnboardingImportCryptoWallets - - const isCreateWalletDisabled = checkConfirmedPassword || - checkPassword || - password === '' || - confirmedPassword === '' - - const isImportDisabled = isCreateWalletDisabled || importPassword === '' + dispatch(WalletPageActions.checkWalletsToImport()) + }, []) // render return ( - {showBackButton && - - } - <> - {onboardingStep === WalletOnboardingSteps.OnboardingWelcome && - - } - {onboardingStep === WalletOnboardingSteps.OnboardingCreatePassword && - - } - {onboardingStep === WalletOnboardingSteps.OnboardingBackupWallet && + + + + + + + + - } - {isImporting && - - } - + + + + + + + + + + + + + + + + + + + ) } diff --git a/components/brave_wallet_ui/stories/wallet-components.tsx b/components/brave_wallet_ui/stories/wallet-components.tsx index b82ce71b506b..21a22e8d040a 100644 --- a/components/brave_wallet_ui/stories/wallet-components.tsx +++ b/components/brave_wallet_ui/stories/wallet-components.tsx @@ -1,6 +1,4 @@ import * as React from 'react' -import { Provider } from 'react-redux' -import { BrowserRouter } from 'react-router-dom' import { DesktopComponentWrapper, DesktopComponentWrapperRow } from './style' import { SideNav, TopTabNav, ChartControlBar, WalletPageLayout, WalletSubViewLayout, OnboardingVerify } from '../components/desktop' @@ -10,25 +8,11 @@ import { TopNavOptions } from '../options/top-nav-options' import { ChartTimelineOptions } from '../options/chart-timeline-options' import Onboarding from './screens/onboarding' import './locale' -import { mockRecoveryPhrase } from './mock-data/user-accounts' import BackupWallet from './screens/backup-wallet' import { SweepstakesBanner } from '../components/desktop/sweepstakes-banner' import { LoadingSkeleton } from '../components/shared' -import { createStore, combineReducers } from 'redux' -import { createSendCryptoReducer } from '../common/reducers/send_crypto_reducer' -import { createWalletReducer } from '../common/reducers/wallet_reducer' -import { createPageReducer } from '../page/reducers/page_reducer' -import { mockPageState } from './mock-data/mock-page-state' -import { mockWalletState } from './mock-data/mock-wallet-state' -import { mockSendCryptoState } from './mock-data/send-crypto-state' -import * as Lib from '../common/async/__mocks__/lib' -import { LibContext } from '../common/context/lib.context' - -const store = createStore(combineReducers({ - wallet: createWalletReducer(mockWalletState), - page: createPageReducer(mockPageState), - sendCrypto: createSendCryptoReducer(mockSendCryptoState) -})) +import WalletPageStory from './wrappers/wallet-page-story-wrapper' +import { mockedMnemonic } from './mock-data/user-accounts' export default { title: 'Wallet/Desktop/Components', @@ -104,17 +88,13 @@ _LineChartControls.story = { export const _Onboarding = () => { return ( - - - - - - - - - - - + + + + + + + ) } @@ -127,12 +107,14 @@ export const _BackupWallet = () => { alert('Wallet Setup Complete!!!') } - return + return ( + + + + ) } _BackupWallet.story = { @@ -140,10 +122,13 @@ _BackupWallet.story = { } export const _OnboardingVerify = () => { - return console.log('done')} - /> + return ( + + console.log('done')} + /> + + ) } _OnboardingVerify.story = { diff --git a/components/brave_wallet_ui/stories/wallet-concept.tsx b/components/brave_wallet_ui/stories/wallet-concept.tsx index e9f74a04710c..962c67d19a95 100644 --- a/components/brave_wallet_ui/stories/wallet-concept.tsx +++ b/components/brave_wallet_ui/stories/wallet-concept.tsx @@ -1,5 +1,4 @@ import * as React from 'react' -import { combineReducers, createStore } from 'redux' import { WalletWidgetStandIn } from './style' import { WalletPageLayout, @@ -21,7 +20,7 @@ import BackupWallet from './screens/backup-wallet' import CryptoStoryView from './screens/crypto-story-view' import './locale' import BuySendSwap from './screens/buy-send-swap' -import { mockRecoveryPhrase, mockUserAccounts } from './mock-data/user-accounts' +import { mockUserAccounts } from './mock-data/user-accounts' import { mockRPCResponse } from './mock-data/rpc-response' import { mockUserWalletPreferences } from './mock-data/user-wallet-preferences' import { getLocale } from '../../common/locale' @@ -30,23 +29,9 @@ import { } from '../components/desktop/popup-modals/add-account-modal/hardware-wallet-connect/types' import { mockNetworks } from './mock-data/mock-networks' import { SweepstakesBanner } from '../components/desktop/sweepstakes-banner' -import { Provider } from 'react-redux' -import { createWalletReducer } from '../common/reducers/wallet_reducer' -import { mockWalletState } from './mock-data/mock-wallet-state' -import { mockPageState } from './mock-data/mock-page-state' -import { createPageReducer } from '../page/reducers/page_reducer' -import * as Lib from '../common/async/__mocks__/lib' -import { LibContext } from '../common/context/lib.context' import { mockAccountAssetOptions, mockNewAssetOptions } from './mock-data/mock-asset-options' -import { createSendCryptoReducer } from '../common/reducers/send_crypto_reducer' -import { mockSendCryptoState } from './mock-data/send-crypto-state' import { mockOriginInfo } from './mock-data/mock-origin-info' - -const store = createStore(combineReducers({ - wallet: createWalletReducer(mockWalletState), - page: createPageReducer(mockPageState), - sendCrypto: createSendCryptoReducer(mockSendCryptoState) -})) +import WalletPageStory from './wrappers/wallet-page-story-wrapper' export default { title: 'Wallet/Desktop', @@ -297,7 +282,7 @@ export const _DesktopWalletConcept = (args: { onboarding: boolean, locked: boole const [isSolanaEnabled] = React.useState(true) const [needsOnboarding] = React.useState(onboarding) const [walletLocked, setWalletLocked] = React.useState(locked) - const [needsBackup, setNeedsBackup] = React.useState(true) + const [needsBackup] = React.useState(true) const [showBackup, setShowBackup] = React.useState(false) const [inputValue, setInputValue] = React.useState('') const [hasPasswordError, setHasPasswordError] = React.useState(false) @@ -309,7 +294,6 @@ export const _DesktopWalletConcept = (args: { onboarding: boolean, locked: boole const [isRestoring, setIsRestoring] = React.useState(false) const [importAccountError, setImportAccountError] = React.useState(false) const [selectedWidgetTab, setSelectedWidgetTab] = React.useState('buy') - const [showVisibleAssetsModal, setShowVisibleAssetsModal] = React.useState(false) const [selectedBuyOption, setSelectedBuyOption] = React.useState(BraveWallet.OnRampProvider.kRamp) const onToggleRestore = () => { @@ -322,10 +306,6 @@ export const _DesktopWalletConcept = (args: { onboarding: boolean, locked: boole // setView(path) // } - const onWalletBackedUp = () => { - setNeedsBackup(false) - } - const unlockWallet = () => { if (inputValue !== 'password') { setHasPasswordError(true) @@ -334,19 +314,11 @@ export const _DesktopWalletConcept = (args: { onboarding: boolean, locked: boole } } - const lockWallet = () => { - setWalletLocked(true) - } - const handlePasswordChanged = (value: string) => { setHasPasswordError(false) setInputValue(value) } - const onUpdateAccountName = (): { success: boolean } => { - return { success: true } - } - const onShowBackup = () => { setShowBackup(true) } @@ -423,14 +395,14 @@ export const _DesktopWalletConcept = (args: { onboarding: boolean, locked: boole alert(name) } - const onImportAccount = (name: string, key: string, coin: BraveWallet.CoinType) => { + const onImportAccount = () => { // doesnt do anything in storybook } - const onImportFilecoinAccount = (accountName: string, privateKey: string, network: string) => { + const onImportFilecoinAccount = () => { // doesnt do anything in storybook } - const onImportAccountFromJson = (name: string, password: string, json: string) => { + const onImportAccountFromJson = () => { // doesnt do anything in storybook } @@ -469,7 +441,7 @@ export const _DesktopWalletConcept = (args: { onboarding: boolean, locked: boole }) } - const getBalance = (address: string): Promise => { + const getBalance = (): Promise => { return new Promise(async (resolve) => { resolve('0') }) @@ -479,133 +451,114 @@ export const _DesktopWalletConcept = (args: { onboarding: boolean, locked: boole console.log(accounts) } - const onViewPrivateKey = () => { - // Doesnt do anything in storybook - } - - const onDoneViewingPrivateKey = () => { - // Doesnt do anything in storybook - } - const onSetImportAccountError = (hasError: boolean) => setImportAccountError(hasError) - const defaultCurrencies = { - fiat: 'USD', - crypto: 'BTC' - } - - const onShowVisibleAssetsModal = (value: boolean) => { - setShowVisibleAssetsModal(value) - } - return ( - - - - {/* */} - - {isRestoring ? ( - - ) : ( - <> - {needsOnboarding - ? ( - - ) : ( - <> - {view === 'crypto' ? ( - <> - {walletLocked ? ( - - ) : ( - <> - {showBackup ? ( - - ) : ( - - )} - - )} - - ) : ( -
-

{view} view

-
- )} - - )} - - )} - -
- {!needsOnboarding && !walletLocked && - - - - - } -
-
-
+ + + {/* */} + + {isRestoring ? ( + + ) : ( + <> + {needsOnboarding + ? ( + + ) : ( + <> + {view === 'crypto' ? ( + <> + {walletLocked ? ( + + ) : ( + <> + {showBackup ? ( + + ) : ( + + )} + + )} + + ) : ( +
+

{view} view

+
+ )} + + )} + + )} + +
+ {!needsOnboarding && !walletLocked && + + + + + } +
+
) } diff --git a/components/brave_wallet_ui/stories/wallet-extension-panels.tsx b/components/brave_wallet_ui/stories/wallet-extension-panels.tsx index 6d8f2691be9d..c9a7f1a5c368 100644 --- a/components/brave_wallet_ui/stories/wallet-extension-panels.tsx +++ b/components/brave_wallet_ui/stories/wallet-extension-panels.tsx @@ -1,3 +1,8 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' import { Provider } from 'react-redux' import { combineReducers, createStore } from 'redux' @@ -450,10 +455,6 @@ export const _ConnectedPanel = (args: { locked: boolean }) => { } } - const onLockWallet = () => { - setWalletLocked(true) - } - const handlePasswordChanged = (value: string) => { setHasPasswordError(false) setInputValue(value) @@ -531,7 +532,6 @@ export const _ConnectedPanel = (args: { locked: boolean }) => { selectedAccount={selectedAccount} isConnected={true} navAction={navigateTo} - onLockWallet={onLockWallet} onOpenSettings={onOpenSettings} originInfo={originInfo} isSwapSupported={true} diff --git a/components/brave_wallet_ui/stories/wrappers/wallet-page-story-wrapper.tsx b/components/brave_wallet_ui/stories/wrappers/wallet-page-story-wrapper.tsx new file mode 100644 index 000000000000..1c34cd4c3145 --- /dev/null +++ b/components/brave_wallet_ui/stories/wrappers/wallet-page-story-wrapper.tsx @@ -0,0 +1,58 @@ +import * as React from 'react' +import { createStore, combineReducers } from 'redux' +import { Provider } from 'react-redux' +import { MemoryRouter } from 'react-router-dom' + +// utils +import { createSendCryptoReducer } from '../../common/reducers/send_crypto_reducer' +import { createWalletReducer } from '../../common/reducers/wallet_reducer' +import { createPageReducer } from '../../page/reducers/page_reducer' + +// types +import { PageState, WalletState } from '../../constants/types' + +// components +import { LibContext } from '../../common/context/lib.context' + +// Mocks +import * as Lib from '../../common/async/__mocks__/lib' +import { mockPageState } from '../mock-data/mock-page-state' +import { mockWalletState } from '../mock-data/mock-wallet-state' +import { mockSendCryptoState } from '../mock-data/send-crypto-state' + +export interface WalletPageStoryProps { + walletStateOverride?: Partial + pageStateOverride?: Partial +} + +export const WalletPageStory: React.FC> = ({ + children, + pageStateOverride, + walletStateOverride +}) => { + // redux + const store = createStore(combineReducers({ + wallet: createWalletReducer({ + ...mockWalletState, + ...(walletStateOverride || {}) + }), + page: createPageReducer({ + ...mockPageState, + ...(pageStateOverride || {}) + }), + sendCrypto: createSendCryptoReducer(mockSendCryptoState) + })) + + // render + return ( + + + + {children} + + + + ) +} + +export default WalletPageStory diff --git a/components/brave_wallet_ui/utils/account-utils.ts b/components/brave_wallet_ui/utils/account-utils.ts new file mode 100644 index 000000000000..767cee2cc873 --- /dev/null +++ b/components/brave_wallet_ui/utils/account-utils.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import { WalletAccountType } from '../constants/types' + +export const sortAccountsByName = (accounts: WalletAccountType[]) => { + return [...accounts].sort(function (a: WalletAccountType, b: WalletAccountType) { + if (a.name < b.name) { + return -1 + } + + if (a.name > b.name) { + return 1 + } + + return 0 + }) +} + +export const groupAccountsById = (accounts: WalletAccountType[], key: string) => { + return accounts.reduce((result, obj) => { + (result[obj[key]] = result[obj[key]] || []).push(obj) + return result + }, {}) +} From d36b098ff885d5eb1efe282a8dbd146cc43d2962 Mon Sep 17 00:00:00 2001 From: Josh Leonard Date: Tue, 26 Apr 2022 20:11:46 -0600 Subject: [PATCH 2/6] feat(wallet): add routes account modal tabs --- .../common/reducers/wallet_reducer.ts | 2 +- .../components/desktop/index.ts | 7 +- .../add-account-modal/add-account-modal.tsx | 37 ++ .../add-hardware-account-modal.tsx | 91 +++++ .../add-imported-account-modal.tsx | 303 ++++++++++++++ .../create-account-modal.tsx | 147 +++++++ .../hardware-wallet-connect/index.tsx | 144 ++++--- .../popup-modals/add-account-modal/index.tsx | 372 +----------------- .../select-account-type/index.ts | 6 + .../select-account-type.style.ts | 34 ++ .../select-account-type.tsx | 64 +++ .../popup-modals/add-account-modal/style.ts | 33 +- .../desktop/views/accounts/account.tsx | 13 +- .../desktop/views/accounts/accounts.tsx | 48 ++- .../components/desktop/views/crypto/index.tsx | 136 ++----- .../views/portfolio/portfolio-asset.tsx | 14 +- components/brave_wallet_ui/constants/types.ts | 37 +- components/brave_wallet_ui/page/container.tsx | 74 +--- .../stories/screens/crypto-story-view.tsx | 87 +--- 19 files changed, 911 insertions(+), 738 deletions(-) create mode 100644 components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-account-modal.tsx create mode 100644 components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-hardware-account-modal.tsx create mode 100644 components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-imported-account-modal.tsx create mode 100644 components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/create-account-modal.tsx create mode 100644 components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/select-account-type/index.ts create mode 100644 components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/select-account-type/select-account-type.style.ts create mode 100644 components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/select-account-type/select-account-type.tsx diff --git a/components/brave_wallet_ui/common/reducers/wallet_reducer.ts b/components/brave_wallet_ui/common/reducers/wallet_reducer.ts index 879c5ce0a96e..0e147c062406 100644 --- a/components/brave_wallet_ui/common/reducers/wallet_reducer.ts +++ b/components/brave_wallet_ui/common/reducers/wallet_reducer.ts @@ -213,7 +213,7 @@ export const createWalletReducer = (initialState: WalletState) => { let accounts: WalletAccountType[] = [...state.accounts] accounts.forEach((account, accountIndex) => { - payload.balances[accountIndex].forEach((info, tokenIndex) => { + payload.balances[accountIndex]?.forEach((info, tokenIndex) => { if (info.error === BraveWallet.ProviderError.kSuccess) { const contractAddress = visibleTokens[tokenIndex].contractAddress.toLowerCase() accounts[accountIndex].tokenBalanceRegistry[contractAddress] = Amount.normalize(info.balance) diff --git a/components/brave_wallet_ui/components/desktop/index.ts b/components/brave_wallet_ui/components/desktop/index.ts index 1c28b22bf610..4eac3f355e0c 100644 --- a/components/brave_wallet_ui/components/desktop/index.ts +++ b/components/brave_wallet_ui/components/desktop/index.ts @@ -1,3 +1,8 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import SideNavButton from './side-nav-button' import SideNav from './side-nav' import TopTabNavButton from './top-tab-nav-button' @@ -15,7 +20,7 @@ import LockScreen from './lock-screen' import WalletMorePopup from './wallet-more-popup' import WalletBanner from './wallet-banner' import PopupModal from './popup-modals' -import AddAccountModal from './popup-modals/add-account-modal' +import { AddAccountModal } from './popup-modals/add-account-modal' import AccountSettingsModal from './popup-modals/account-settings-modal' import EditVisibleAssetsModal from './popup-modals/edit-visible-assets-modal' import AssetWatchlistItem from './asset-watchlist-item' diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-account-modal.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-account-modal.tsx new file mode 100644 index 000000000000..620ca6fea8f0 --- /dev/null +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-account-modal.tsx @@ -0,0 +1,37 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import { Redirect, Route, Switch } from 'react-router' + +// types +import { WalletRoutes } from '../../../../constants/types' + +// components +import { AddHardwareAccountModal } from './add-hardware-account-modal' +import { ImportAccountModal } from './add-imported-account-modal' +import { CreateAccountModal } from './create-account-modal' + +export const AddAccountModal = () => { + return ( + + + + + + + + + + + + + + + + + + ) +} diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-hardware-account-modal.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-hardware-account-modal.tsx new file mode 100644 index 000000000000..879e2e2ec34a --- /dev/null +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-hardware-account-modal.tsx @@ -0,0 +1,91 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useHistory, useParams } from 'react-router' + +// utils +import { getLocale } from '$web-common/locale' + +// options +import { CreateAccountOptions } from '../../../../options/create-account-options' + +// types +import { CreateAccountOptionsType, WalletRoutes, WalletState } from '../../../../constants/types' + +// actions +import { WalletPageActions } from '../../../../page/actions' + +// components +import { DividerLine } from '../../../extension' +import PopupModal from '..' + +// style +import { + StyledWrapper +} from './style' +import HardwareWalletConnect from './hardware-wallet-connect' +import { SelectAccountType } from './select-account-type/select-account-type' + +interface Params { + accountTypeName: string +} + +export const AddHardwareAccountModal = () => { + // routing + const history = useHistory() + const { accountTypeName } = useParams() + + // redux + const dispatch = useDispatch() + const isFilecoinEnabled = useSelector(({ wallet }: { wallet: WalletState }) => wallet.isFilecoinEnabled) + const isSolanaEnabled = useSelector(({ wallet }: { wallet: WalletState }) => wallet.isSolanaEnabled) + + // memos + const createAccountOptions = React.useMemo( + () => CreateAccountOptions(isFilecoinEnabled, isSolanaEnabled), + [isFilecoinEnabled, isSolanaEnabled] + ) + + const selectedAccountType: CreateAccountOptionsType | undefined = React.useMemo(() => { + return createAccountOptions.find((option) => { + return option.name.toLowerCase() === accountTypeName?.toLowerCase() + }) + }, [createAccountOptions, accountTypeName]) + + // methods + const setImportError = React.useCallback((hasError: boolean) => { + dispatch(WalletPageActions.setImportAccountError(hasError)) + }, []) + + const onClickClose = React.useCallback(() => { + setImportError(false) + history.push(WalletRoutes.Accounts) + }, []) + + const onSelectAccountType = React.useCallback((accountType: CreateAccountOptionsType) => () => { + history.push(WalletRoutes.AddHardwareAccountModal.replace(':accountTypeName?', accountType.name.toLowerCase())) + }, []) + + // render + return ( + + + + {selectedAccountType && + + + + } + + {!selectedAccountType && + + } + + ) +} + +export default AddHardwareAccountModal diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-imported-account-modal.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-imported-account-modal.tsx new file mode 100644 index 000000000000..7b410d79031a --- /dev/null +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-imported-account-modal.tsx @@ -0,0 +1,303 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useHistory, useParams } from 'react-router' + +// utils +import { getLocale } from '$web-common/locale' +import { copyToClipboard } from '../../../../utils/copy-to-clipboard' + +// options +import { CreateAccountOptions } from '../../../../options/create-account-options' + +// types +import { BraveWallet, CreateAccountOptionsType, PageState, WalletRoutes, WalletState } from '../../../../constants/types' +import { FilecoinNetworkTypes, FilecoinNetworkLocaleMapping, FilecoinNetwork } from '../../../../common/hardware/types' + +// actions +import { WalletPageActions } from '../../../../page/actions' + +// components +import { Select } from 'brave-ui/components' +import { DividerLine, NavButton } from '../../../extension' +import PopupModal from '..' +import { SelectAccountType } from './select-account-type/select-account-type' + +// style +import { + DisclaimerText, + ErrorText, + ImportButton, + ImportDisclaimer, + ImportRow, + Input, + SelectWrapper, + StyledWrapper +} from './style' + +interface Params { + accountTypeName: string +} + +const reduceFileName = (address: string) => { + const firstHalf = address.slice(0, 4) + const secondHalf = address.slice(-4) + const reduced = firstHalf.concat('......', secondHalf) + return reduced +} + +export const ImportAccountModal = () => { + // refs + const passwordInputRef = React.useRef(null) + + // routing + const history = useHistory() + // const { pathname: walletLocation } = useLocation() + const { accountTypeName } = useParams() + + // redux + const isSolanaEnabled = useSelector(({ wallet }: { wallet: WalletState }) => wallet.isSolanaEnabled) + const isFilecoinEnabled = useSelector(({ wallet }: { wallet: WalletState }) => wallet.isFilecoinEnabled) + + // memos + const selectedAccountType: CreateAccountOptionsType | undefined = React.useMemo(() => { + if (!accountTypeName) { + return undefined + } + return CreateAccountOptions(isFilecoinEnabled, isSolanaEnabled).find(option => { + return option.name.toLowerCase() === accountTypeName.toLowerCase() + }) + }, [accountTypeName, isFilecoinEnabled, isSolanaEnabled]) + + // state + const [accountName, setAccountName] = React.useState('') + const [filecoinNetwork, setFilecoinNetwork] = React.useState('f') + const [importOption, setImportOption] = React.useState('key') + const [privateKey, setPrivateKey] = React.useState('') + const [file, setFile] = React.useState() + const [password, setPassword] = React.useState('') + + // redux + const dispatch = useDispatch() + const hasImportError = useSelector(({ page }: { page: PageState }) => page.importAccountError) + + // methods + const setImportError = React.useCallback((hasError: boolean) => { + dispatch(WalletPageActions.setImportAccountError(hasError)) + }, []) + + const onClickClose = React.useCallback(() => { + setImportError(false) + history.push(WalletRoutes.Accounts) + }, []) + + const importAccount = React.useCallback((accountName: string, privateKey: string, coin: BraveWallet.CoinType) => { + dispatch(WalletPageActions.importAccount({ accountName, privateKey, coin })) + }, []) + + const importFilecoinAccount = React.useCallback((accountName: string, privateKey: string, network: string) => { + dispatch(WalletPageActions.importFilecoinAccount({ accountName, privateKey, network })) + }, []) + + const importAccountFromJson = React.useCallback((accountName: string, password: string, json: string) => { + dispatch(WalletPageActions.importAccountFromJson({ accountName, password, json })) + }, []) + + const handleAccountNameChanged = React.useCallback((event: React.ChangeEvent) => { + setAccountName(event.target.value) + setImportError(false) + }, [setImportError]) + + const onChangeFilecoinNetwork = React.useCallback((network: FilecoinNetwork) => { + setFilecoinNetwork(network) + }, []) + + const handlePrivateKeyChanged = React.useCallback((event: React.ChangeEvent) => { + setPrivateKey(event.target.value) + setImportError(false) + }, [setImportError]) + + const onClearClipboard = React.useCallback(() => { + copyToClipboard('') + }, []) + + const onFileUpload = React.useCallback((file: React.ChangeEvent) => { + if (file.target.files) { + setFile(file.target.files) + setImportError(false) + passwordInputRef.current?.focus() + } + }, [setImportError]) + + const handlePasswordChanged = React.useCallback((event: React.ChangeEvent) => { + setPassword(event.target.value) + setImportError(false) + }, [setImportError]) + + const onClickCreateAccount = React.useCallback(() => { + if (importOption === 'key') { + if (selectedAccountType?.coin === BraveWallet.CoinType.FIL) { + importFilecoinAccount(accountName, privateKey, filecoinNetwork) + } else { + importAccount(accountName, privateKey, selectedAccountType?.coin || BraveWallet.CoinType.ETH) + } + + history.push(WalletRoutes.Accounts) + + return + } + + if (file) { + const index = file[0] + const reader = new FileReader() + reader.onload = function () { + if (reader.result) { + importAccountFromJson(accountName, password, (reader.result.toString().trim())) + } + } + + reader.readAsText(index) + + history.push(WalletRoutes.Accounts) + } + }, [ + importOption, + selectedAccountType, + accountName, + privateKey, + file, + password + ]) + + const handleKeyDown = React.useCallback((event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + onClickCreateAccount() + } + }, [onClickCreateAccount]) + + const onSelectAccountType = React.useCallback((accountType: CreateAccountOptionsType) => () => { + history.push(WalletRoutes.ImportAccountModal.replace(':accountTypeName?', accountType.name.toLowerCase())) + }, []) + + // computed + const isDisabled = accountName === '' + const modalTitle = selectedAccountType + ? getLocale('braveWalletCreateAccountImportAccount').replace('$1', selectedAccountType.name) + : getLocale('braveWalletAddAccountImport') + + // render + return ( + + + + + {!selectedAccountType && + + } + + {selectedAccountType && + + + {getLocale('braveWalletImportAccountDisclaimer')} + + + {selectedAccountType?.coin === BraveWallet.CoinType.FIL && + <> + + + + + } + + {selectedAccountType?.coin === BraveWallet.CoinType.ETH && + + + + } + + {hasImportError && + {getLocale('braveWalletImportAccountError')} + } + + {importOption === 'key' && ( + + )} + + {importOption !== 'key' && ( + <> + + {getLocale('braveWalletImportAccountUploadButton')} + {file ? reduceFileName(file[0].name) : getLocale('braveWalletImportAccountUploadPlaceholder')} + + + + + )} + + + + + + + } + + + ) +} + +export default ImportAccountModal diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/create-account-modal.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/create-account-modal.tsx new file mode 100644 index 000000000000..bdd37b8b8267 --- /dev/null +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/create-account-modal.tsx @@ -0,0 +1,147 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useHistory, useLocation, useParams } from 'react-router' + +// utils +import { getLocale } from '$web-common/locale' + +// options +import { CreateAccountOptions } from '../../../../options/create-account-options' + +// types +import { BraveWallet, CreateAccountOptionsType, WalletRoutes, WalletState } from '../../../../constants/types' + +// actions +import { WalletPageActions } from '../../../../page/actions' +import { WalletActions } from '../../../../common/actions' + +// components +import { DividerLine, NavButton } from '../../../../components/extension' +import PopupModal from '..' + +// style +import { + Input, + StyledWrapper +} from './style' +import { SelectAccountType } from './select-account-type' + +interface Params { + accountTypeName: string +} + +export const CreateAccountModal = () => { + // routing + const history = useHistory() + const { pathname: walletLocation } = useLocation() + const { accountTypeName } = useParams() + + // redux + const dispatch = useDispatch() + const isFilecoinEnabled = useSelector(({ wallet }: { wallet: WalletState }) => wallet.isFilecoinEnabled) + const isSolanaEnabled = useSelector(({ wallet }: { wallet: WalletState }) => wallet.isSolanaEnabled) + const accounts = useSelector(({ wallet }: { wallet: WalletState }) => wallet.accounts) + + // state + const [accountName, setAccountName] = React.useState('') + + // memos + const selectedAccountType: CreateAccountOptionsType | undefined = React.useMemo(() => { + if (!accountTypeName) { + return undefined + } + return CreateAccountOptions(isFilecoinEnabled, isSolanaEnabled).find(option => { + return option.name.toLowerCase() === accountTypeName.toLowerCase() + }) + }, [accountTypeName]) + + const suggestedAccountName = React.useMemo(() => { + const accountTypeLength = accounts.filter((account) => account.coin === selectedAccountType?.coin).length + 1 + return `${selectedAccountType?.name} ${getLocale('braveWalletAccount')} ${accountTypeLength}` + }, [accounts, selectedAccountType]) + + // methods + const setImportAccountError = React.useCallback((hasError: boolean) => { + dispatch(WalletPageActions.setImportAccountError(hasError)) + }, []) + + const onClickClose = React.useCallback(() => { + setImportAccountError(false) + history.push(WalletRoutes.Accounts) + }, [setImportAccountError]) + + const handleAccountNameChanged = React.useCallback((event: React.ChangeEvent) => { + setAccountName(event.target.value) + setImportAccountError(false) + }, [setImportAccountError]) + + const onClickCreateAccount = React.useCallback(() => { + const created = dispatch(WalletActions.addAccount({ accountName, coin: selectedAccountType?.coin || BraveWallet.CoinType.ETH })) + if (created) { + if (walletLocation.includes(WalletRoutes.Accounts)) { + history.push(WalletRoutes.Accounts) + } + } + }, [accountName, selectedAccountType]) + + const handleKeyDown = React.useCallback((event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + onClickCreateAccount() + } + }, [onClickCreateAccount]) + + const pickNewAccountType = React.useCallback((option: CreateAccountOptionsType) => () => { + history.push(WalletRoutes.CreateAccountModal + .replace(':accountTypeName?', option.name.toLowerCase()) + ) + }, []) + + // effects + React.useEffect(() => { + setAccountName(suggestedAccountName) + }, [suggestedAccountName]) + + // computed + const isDisabled = accountName === '' + const modalTitle = selectedAccountType + ? getLocale('braveWalletCreateAccount').replace('$1', selectedAccountType.name) + : getLocale('braveWalletCreateAccountButton') + + // render + return ( + + + {selectedAccountType && + + + + + } + + {!selectedAccountType && + + } + + ) +} + +export default CreateAccountModal diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/hardware-wallet-connect/index.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/hardware-wallet-connect/index.tsx index 2c3ff73ec95e..0b0b93867831 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/hardware-wallet-connect/index.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/hardware-wallet-connect/index.tsx @@ -1,8 +1,19 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' +// utils import { getLocale } from '../../../../../../common/locale' +import { getBalance } from '../../../../../common/async/lib' + +// components +import HardwareWalletAccountsList from './accounts-list' import { NavButton } from '../../../../extension' -import { BraveWallet, WalletAccountType, CreateAccountOptionsType } from '../../../../../constants/types' + // Styled Components import { DisclaimerText, InfoIcon } from '../style' import { @@ -18,29 +29,49 @@ import { ErrorText, LoadIcon } from './style' + // Custom types -import { HardwareWalletConnectOpts, ErrorMessage, HardwareWalletDerivationPathsMapping } from './types' -import HardwareWalletAccountsList from './accounts-list' +import { ErrorMessage, HardwareWalletDerivationPathsMapping } from './types' import { HardwareDerivationScheme, LedgerDerivationPaths, FilecoinNetwork, DerivationBatchSize } from '../../../../../common/hardware/types' import { HardwareVendor } from '../../../../../common/api/hardware_keyrings' +import { WalletPageActions } from '../../../../../page/actions' +import { BraveWallet, CreateAccountOptionsType, WalletState } from '../../../../../constants/types' + +// hooks +import { useLib } from '../../../../../common/hooks' export interface Props { - onConnectHardwareWallet: (opts: HardwareWalletConnectOpts) => Promise - onAddHardwareAccounts: (selected: BraveWallet.HardwareWalletAccount[]) => void - getBalance: (address: string, coin: BraveWallet.CoinType) => Promise - onChangeFilecoinNetwork: (network: FilecoinNetwork) => void - preAddedHardwareWalletAccounts: WalletAccountType[] selectedAccountType: CreateAccountOptionsType - selectedNetwork: BraveWallet.NetworkInfo - filecoinNetwork: FilecoinNetwork } -export default function (props: Props) { - const { - selectedAccountType, - selectedNetwork, - filecoinNetwork - } = props +const getErrorMessage = (error: any, accountTypeName: string) => { + if (error.statusCode && error.statusCode === 27404) { // Unknown Error + return { error: getLocale('braveWalletConnectHardwareInfo2').replace('$1', accountTypeName), userHint: '' } + } + + if (error.statusCode && (error.statusCode === 27904 || error.statusCode === 26368)) { // INCORRECT_LENGTH or INS_NOT_SUPPORTED + return { error: error.message, userHint: getLocale('braveWalletConnectHardwareWrongApplicationUserHint') } + } + + if (!error || !error.message) { + return { error: getLocale('braveWalletUnknownInternalError'), userHint: '' } + } + + return { error: error.message, userHint: '' } +} + +export const HardwareWalletConnect = (props: Props) => { + const { selectedAccountType } = props + + // lib + const { onConnectHardwareWallet } = useLib() + + // redux + const dispatch = useDispatch() + const selectedNetwork = useSelector(({ wallet }: { wallet: WalletState }) => wallet.selectedNetwork) + const savedAccounts = useSelector(({ wallet }: { wallet: WalletState }) => wallet.accounts) + + // state const [selectedHardwareWallet, setSelectedHardwareWallet] = React.useState(BraveWallet.LEDGER_HARDWARE_VENDOR) const [isConnecting, setIsConnecting] = React.useState(false) const [accounts, setAccounts] = React.useState([]) @@ -50,24 +81,12 @@ export default function (props: Props) { LedgerDerivationPaths.LedgerLive ) const [showAccountsList, setShowAccountsList] = React.useState(false) - const getErrorMessage = (error: any, accountTypeName: string) => { - if (error.statusCode && error.statusCode === 27404) { // Unknown Error - return { error: getLocale('braveWalletConnectHardwareInfo2').replace('$1', accountTypeName), userHint: '' } - } + const [filecoinNetwork, setFilecoinNetwork] = React.useState('f') - if (error.statusCode && (error.statusCode === 27904 || error.statusCode === 26368)) { // INCORRECT_LENGTH or INS_NOT_SUPPORTED - return { error: error.message, userHint: getLocale('braveWalletConnectHardwareWrongApplicationUserHint') } - } - - if (!error || !error.message) { - return { error: getLocale('braveWalletUnknownInternalError'), userHint: '' } - } - - return { error: error.message, userHint: '' } - } - const onFilecoinNetworkChanged = (network: FilecoinNetwork) => { - props.onChangeFilecoinNetwork(network) - props.onConnectHardwareWallet({ + // methods + const onFilecoinNetworkChanged = React.useCallback((network: FilecoinNetwork) => { + setFilecoinNetwork(network) + onConnectHardwareWallet({ hardware: BraveWallet.LEDGER_HARDWARE_VENDOR, startIndex: 0, stopIndex: DerivationBatchSize, @@ -81,11 +100,16 @@ export default function (props: Props) { }).finally( () => setIsConnecting(false) ) - } - const onChangeDerivationScheme = (scheme: HardwareDerivationScheme) => { + }, [onConnectHardwareWallet, selectedAccountType]) + + const onAddHardwareAccounts = React.useCallback((selected: BraveWallet.HardwareWalletAccount[]) => { + dispatch(WalletPageActions.addHardwareAccounts(selected)) + }, []) + + const onChangeDerivationScheme = React.useCallback((scheme: HardwareDerivationScheme) => { setSelectedDerivationScheme(scheme) setAccounts([]) - props.onConnectHardwareWallet({ + onConnectHardwareWallet({ hardware: selectedHardwareWallet, startIndex: 0, stopIndex: DerivationBatchSize, @@ -100,9 +124,9 @@ export default function (props: Props) { }).finally( () => setIsConnecting(false) ) - } + }, [onConnectHardwareWallet, selectedHardwareWallet, selectedAccountType, filecoinNetwork]) - const getDefaultAccountName = (account: BraveWallet.HardwareWalletAccount) => { + const getDefaultAccountName = React.useCallback((account: BraveWallet.HardwareWalletAccount) => { const index = accounts.findIndex(e => e.address === account.address) let schemeString switch (selectedDerivationScheme) { @@ -116,45 +140,41 @@ export default function (props: Props) { return index === 0 ? `${account.hardwareVendor}${schemeString}` : `${account.hardwareVendor} ${index}${schemeString}` - } + }, [accounts, selectedDerivationScheme]) - const onAddAccounts = () => { - const selectedAccounts = accounts.filter(o => selectedDerivationPaths.includes(o.derivationPath)) + const onAddAccounts = React.useCallback(() => { + const selectedAccounts = accounts.filter(account => selectedDerivationPaths.includes(account.derivationPath)) const renamedSelectedAccounts = selectedAccounts .map(account => ({ ...account, name: getDefaultAccountName(account) })) - props.onAddHardwareAccounts(renamedSelectedAccounts) - } + onAddHardwareAccounts(renamedSelectedAccounts) + }, [accounts, selectedDerivationPaths, getDefaultAccountName, onAddHardwareAccounts]) - const getBalance = (address: string, coin: BraveWallet.CoinType) => { - return props.getBalance(address, coin) - } - - const selectVendor = (vendor: HardwareVendor) => { + const selectVendor = React.useCallback((vendor: HardwareVendor) => { const derivationPathsEnum = HardwareWalletDerivationPathsMapping[vendor] setSelectedDerivationScheme(Object.values(derivationPathsEnum)[0] as HardwareDerivationScheme) setSelectedHardwareWallet(vendor) - } + }, []) - const onSelectLedger = () => { + const onSelectLedger = React.useCallback(() => { if (selectedHardwareWallet !== BraveWallet.LEDGER_HARDWARE_VENDOR) { setConnectionError(undefined) } selectVendor(BraveWallet.LEDGER_HARDWARE_VENDOR) - } + }, [selectedHardwareWallet, selectVendor]) - const onSelectTrezor = () => { + const onSelectTrezor = React.useCallback(() => { if (selectedHardwareWallet !== BraveWallet.TREZOR_HARDWARE_VENDOR) { setConnectionError(undefined) } selectVendor(BraveWallet.TREZOR_HARDWARE_VENDOR) - } + }, [selectedHardwareWallet, selectVendor]) - const onSubmit = () => { + const onSubmit = React.useCallback(() => { setConnectionError(undefined) setIsConnecting(true) - props.onConnectHardwareWallet({ + onConnectHardwareWallet({ hardware: selectedHardwareWallet, startIndex: accounts.length, stopIndex: accounts.length + DerivationBatchSize, @@ -170,14 +190,22 @@ export default function (props: Props) { }).finally( () => setIsConnecting(false) ) - } + }, [onConnectHardwareWallet, selectedHardwareWallet, accounts, selectedDerivationScheme, selectedAccountType, filecoinNetwork]) + + // memos + const preAddedHardwareWalletAccounts = React.useMemo(() => { + return savedAccounts.filter(account => + ['Ledger', 'Trezor'].includes(account.accountType) + ) + }, [savedAccounts]) + // render if (showAccountsList) { return ( ) } + +export default HardwareWalletConnect diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/index.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/index.tsx index 04b17d4bbc42..aa946e939679 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/index.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/index.tsx @@ -1,368 +1,6 @@ -import * as React from 'react' +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. -import { - BraveWallet, - AddAccountNavTypes, - WalletAccountType, - CreateAccountOptionsType -} from '../../../../constants/types' -import { Select } from 'brave-ui/components' -import { PopupModal } from '../..' -import { NavButton, DividerLine } from '../../../extension' -import { getLocale } from '../../../../../common/locale' -import AccountTypeItem from './account-type-item' -import { CreateAccountOptions } from '../../../../options/create-account-options' -import { copyToClipboard } from '../../../../utils/copy-to-clipboard' - -// Styled Components -import { - Input, - StyledWrapper, - DisclaimerText, - ImportDisclaimer, - SelectWrapper, - ImportButton, - ImportRow, - ErrorText, - SelectAccountTypeWrapper, - SelectAccountTitle, - SelectAccountItemWrapper -} from './style' - -import { HardwareWalletConnectOpts } from './hardware-wallet-connect/types' -import HardwareWalletConnect from './hardware-wallet-connect' -import { - FilecoinNetwork, - FilecoinNetworkTypes, - FilecoinNetworkLocaleMapping -} from '../../../../common/hardware/types' - -export interface Props { - onClose: () => void - onCreateAccount: (name: string, coin: BraveWallet.CoinType) => void - onImportAccount: (accountName: string, privateKey: string, coin: BraveWallet.CoinType) => void - isFilecoinEnabled: boolean - isSolanaEnabled: boolean - onImportFilecoinAccount: (accountName: string, key: string, network: FilecoinNetwork) => void - onImportAccountFromJson: (accountName: string, password: string, json: string) => void - onConnectHardwareWallet: (opts: HardwareWalletConnectOpts) => Promise - onAddHardwareAccounts: (selected: BraveWallet.HardwareWalletAccount[]) => void - getBalance: (address: string, coin: BraveWallet.CoinType) => Promise - onSetImportError: (hasError: boolean) => void - hasImportError: boolean - accounts: WalletAccountType[] - tab: AddAccountNavTypes - selectedNetwork: BraveWallet.NetworkInfo -} - -const AddAccountModal = (props: Props) => { - const { - accounts, - selectedNetwork, - isFilecoinEnabled, - isSolanaEnabled, - hasImportError, - tab, - onClose, - onCreateAccount, - onImportAccount, - onImportFilecoinAccount, - onConnectHardwareWallet, - onAddHardwareAccounts, - getBalance, - onImportAccountFromJson, - onSetImportError - } = props - - const [importOption, setImportOption] = React.useState('key') - const [file, setFile] = React.useState() - const [accountName, setAccountName] = React.useState('') - const [privateKey, setPrivateKey] = React.useState('') - const [password, setPassword] = React.useState('') - const [selectedAccountType, setSelectedAccountType] = React.useState(undefined) - const passwordInputRef = React.useRef(null) - const [filecoinNetwork, setFilecoinNetwork] = React.useState('f') - - const suggestedAccountName = React.useMemo(() => { - const accountTypeLength = accounts.filter((account) => account.coin === selectedAccountType?.coin).length + 1 - return `${selectedAccountType?.name} ${getLocale('braveWalletAccount')} ${accountTypeLength}` - }, [accounts, selectedAccountType]) - - const onChangeFilecoinNetwork = (network: FilecoinNetwork) => { - setFilecoinNetwork(network) - } - - const importError = React.useMemo(() => { - return hasImportError - }, [hasImportError]) - - const onClickClose = () => { - setPassword('') - setPrivateKey('') - setFile(undefined) - onSetImportError(false) - onClose() - } - - const handleAccountNameChanged = (event: React.ChangeEvent) => { - setAccountName(event.target.value) - onSetImportError(false) - } - - const handlePrivateKeyChanged = (event: React.ChangeEvent) => { - setPrivateKey(event.target.value) - onSetImportError(false) - } - - const handlePasswordChanged = (event: React.ChangeEvent) => { - setPassword(event.target.value) - onSetImportError(false) - } - - const onClickCreateAccount = () => { - if (tab === 'create') { - onCreateAccount(accountName, selectedAccountType?.coin || BraveWallet.CoinType.ETH) - return - } - if (tab === 'import') { - if (importOption === 'key') { - if (selectedAccountType?.coin === BraveWallet.CoinType.FIL) { - onImportFilecoinAccount(accountName, privateKey, filecoinNetwork) - } else { - onImportAccount(accountName, privateKey, selectedAccountType?.coin || BraveWallet.CoinType.ETH) - } - } else { - if (file) { - const index = file[0] - const reader = new FileReader() - reader.onload = function () { - if (reader.result) { - onImportAccountFromJson(accountName, password, (reader.result.toString().trim())) - } - } - reader.readAsText(index) - } - } - } - } - - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === 'Enter') { - onClickCreateAccount() - } - } - - const onImportOptionChange = (value: string) => { - setImportOption(value) - } - - const reduceFileName = (address: string) => { - const firstHalf = address.slice(0, 4) - const secondHalf = address.slice(-4) - const reduced = firstHalf.concat('......', secondHalf) - return reduced - } - - const onFileUpload = (file: React.ChangeEvent) => { - if (file.target.files) { - setFile(file.target.files) - onSetImportError(false) - passwordInputRef.current?.focus() - } - } - - const isDisabled = React.useMemo(() => { - if (tab === 'create') { - return accountName === '' - } - if (tab === 'import') { - if (importOption === 'key') { - return accountName === '' || privateKey === '' - } - return accountName === '' || file === undefined - } - return false - }, [tab, importOption, accountName, privateKey, file]) - - const modalTitle = React.useMemo((): string => { - switch (tab) { - case 'create': - setAccountName(suggestedAccountName) - return selectedAccountType - ? getLocale('braveWalletCreateAccount').replace('$1', selectedAccountType.name) - : getLocale('braveWalletCreateAccountButton') - case 'import': - setAccountName('') - return selectedAccountType - ? getLocale('braveWalletCreateAccountImportAccount').replace('$1', selectedAccountType.name) - : getLocale('braveWalletAddAccountImport') - case 'hardware': - setAccountName('') - return getLocale('braveWalletAddAccountImportHardware') - default: - return '' - } - }, [tab, selectedAccountType]) - - const onSelectAccountType = (accountType: CreateAccountOptionsType) => () => { - setSelectedAccountType(accountType) - } - - const buttonText = React.useMemo((): string => { - switch (tab) { - case 'create': - return getLocale('braveWalletAddAccountCreate') - case 'import': - return getLocale('braveWalletAddAccountImport') - case 'hardware': - return getLocale('braveWalletAddAccountConnect') - default: - return '' - } - }, [tab]) - - const onClearClipboard = () => { - copyToClipboard('') - } - - return ( - - - {selectedAccountType ? ( - - {tab === 'import' && - <> - - {getLocale('braveWalletImportAccountDisclaimer')} - - {selectedAccountType?.coin === BraveWallet.CoinType.FIL ? ( - <> - - - - - ) : ( - <> - {selectedAccountType?.coin === BraveWallet.CoinType.ETH && - - - - } - - ) - } - {importError && - {getLocale('braveWalletImportAccountError')} - } - {importOption === 'key' ? ( - - ) : ( - <> - - {getLocale('braveWalletImportAccountUploadButton')} - {file ? reduceFileName(file[0].name) : getLocale('braveWalletImportAccountUploadPlaceholder')} - - - - - )} - - } - {tab !== 'hardware' && - <> - - - - } - {tab === 'hardware' && - ['Ledger', 'Trezor'].includes(account.accountType)) - } - selectedNetwork={selectedNetwork} - filecoinNetwork={filecoinNetwork} - onChangeFilecoinNetwork={onChangeFilecoinNetwork} - /> - } - - ) : ( - - {getLocale('braveWalletCreateAccountTitle')} - - {CreateAccountOptions(isFilecoinEnabled, isSolanaEnabled).map((network) => - - - {network.coin !== BraveWallet.CoinType.FIL && - - } - - )} - - )} - - ) -} - -export default AddAccountModal +export { AddAccountModal } from './add-account-modal' diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/select-account-type/index.ts b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/select-account-type/index.ts new file mode 100644 index 000000000000..179db7b58545 --- /dev/null +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/select-account-type/index.ts @@ -0,0 +1,6 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +export { SelectAccountType } from './select-account-type' diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/select-account-type/select-account-type.style.ts b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/select-account-type/select-account-type.style.ts new file mode 100644 index 000000000000..1de9b696eadb --- /dev/null +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/select-account-type/select-account-type.style.ts @@ -0,0 +1,34 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import styled from 'styled-components' + +export const SelectAccountTypeWrapper = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + width: 100%; + padding: 24px; + min-height: 320px; +` + +export const SelectAccountItemWrapper = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; +` + +export const SelectAccountTitle = styled.span` + font-family: Poppins; + font-size: 12px; + line-height: 18px; + font-weight: 600; + letter-spacing: 0.01em; + color: ${(p) => p.theme.color.text02}; + margin-bottom: 12px; +` diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/select-account-type/select-account-type.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/select-account-type/select-account-type.tsx new file mode 100644 index 000000000000..d460d9ab1c6a --- /dev/null +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/select-account-type/select-account-type.tsx @@ -0,0 +1,64 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import { useSelector } from 'react-redux' + +// utils +import { getLocale } from '$web-common/locale' + +// options +import { CreateAccountOptions } from '../../../../../options/create-account-options' + +// types +import { BraveWallet, CreateAccountOptionsType, WalletState } from '../../../../../constants/types' + +// components +import { DividerLine } from '../../../../extension' +import AccountTypeItem from '../account-type-item' + +// style +import { + SelectAccountItemWrapper, + SelectAccountTitle, + SelectAccountTypeWrapper +} from './select-account-type.style' + +interface Props { + onSelectAccountType: (accountType: CreateAccountOptionsType) => () => void + buttonText: string +} + +export const SelectAccountType = ({ buttonText, onSelectAccountType }: Props) => { + // redux + const { isSolanaEnabled, isFilecoinEnabled } = useSelector(({ wallet }: { wallet: WalletState }) => wallet) + + // render + return ( + + + {getLocale('braveWalletCreateAccountTitle')} + + + + {CreateAccountOptions(isFilecoinEnabled, isSolanaEnabled).map((network) => ( + + + + + {network.coin !== BraveWallet.CoinType.FIL && } + + + ))} + + + ) +} diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/style.ts b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/style.ts index be841a6af0bc..5273933d7d18 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/style.ts +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/style.ts @@ -1,3 +1,8 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + import styled from 'styled-components' import InfoLogo from '../../../../assets/svg-icons/info-icon.svg' @@ -120,34 +125,6 @@ export const ErrorText = styled.span` margin-bottom: 10px; ` -export const SelectAccountTypeWrapper = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: flex-start; - width: 100%; - padding: 24px; - min-height: 320px; -` - -export const SelectAccountItemWrapper = styled.div` - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 100%; -` - -export const SelectAccountTitle = styled.span` - font-family: Poppins; - font-size: 12px; - line-height: 18px; - font-weight: 600; - letter-spacing: 0.01em; - color: ${(p) => p.theme.color.text02}; - margin-bottom: 12px; -` - export const ImportDisclaimer = styled.div` display: flex; flex-direction: column; diff --git a/components/brave_wallet_ui/components/desktop/views/accounts/account.tsx b/components/brave_wallet_ui/components/desktop/views/accounts/account.tsx index acd57ad3118f..e75af52d961e 100644 --- a/components/brave_wallet_ui/components/desktop/views/accounts/account.tsx +++ b/components/brave_wallet_ui/components/desktop/views/accounts/account.tsx @@ -55,13 +55,13 @@ import { useBalance } from '../../../../common/hooks' // Actions import { WalletActions } from '../../../../common/actions' +import { WalletPageActions } from '../../../../page/actions' export interface Props { onViewPrivateKey: (address: string, isDefault: boolean, coin: BraveWallet.CoinType) => void onDoneViewingPrivateKey: () => void toggleNav: () => void onUpdateAccountName: (payload: UpdateAccountNamePayloadType) => { success: boolean } - onRemoveAccount: (address: string, hardware: boolean, coin: BraveWallet.CoinType) => void goBack: () => void } @@ -71,8 +71,7 @@ export const Account = (props: Props) => { onViewPrivateKey, onDoneViewingPrivateKey, toggleNav, - onUpdateAccountName, - onRemoveAccount + onUpdateAccountName } = props const { id: accountId } = useParams<{ id: string }>() @@ -164,6 +163,14 @@ export const Account = (props: Props) => { setEditTab('details') }, []) + const onRemoveAccount = React.useCallback((address: string, hardware: boolean, coin: BraveWallet.CoinType) => { + if (hardware) { + dispatch(WalletPageActions.removeHardwareAccount({ address, coin })) + return + } + dispatch(WalletPageActions.removeImportedAccount({ address, coin })) + }, []) + // effects React.useEffect(() => { if (selectedAccount) { diff --git a/components/brave_wallet_ui/components/desktop/views/accounts/accounts.tsx b/components/brave_wallet_ui/components/desktop/views/accounts/accounts.tsx index 041ff8591514..c98c642ffaf8 100644 --- a/components/brave_wallet_ui/components/desktop/views/accounts/accounts.tsx +++ b/components/brave_wallet_ui/components/desktop/views/accounts/accounts.tsx @@ -4,19 +4,24 @@ // you can obtain one at http://mozilla.org/MPL/2.0/. import * as React from 'react' -import { useSelector } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' +import { useHistory } from 'react-router' import { WalletAccountType, BraveWallet, AddAccountNavTypes, - WalletState + WalletState, + WalletRoutes } from '../../../../constants/types' // utils import { getLocale } from '../../../../../common/locale' import { groupAccountsById, sortAccountsByName } from '../../../../utils/account-utils' +// actions +import { WalletPageActions } from '../../../../page/actions' + // Styled Components import { StyledWrapper, @@ -38,20 +43,39 @@ import { AddButton } from '../..' -export interface Props { - onClickAddAccount: (tabId: AddAccountNavTypes) => () => void - onRemoveAccount: (address: string, hardware: boolean, coin: BraveWallet.CoinType) => void - onSelectAccount: (account: WalletAccountType) => void -} +export const Accounts = () => { + // routing + const history = useHistory() -export const Accounts = ({ - onSelectAccount, - onClickAddAccount, - onRemoveAccount -}: Props) => { // redux + const dispatch = useDispatch() const accounts = useSelector(({ wallet }: { wallet: WalletState }) => wallet.accounts) + // methods + const onSelectAccount = React.useCallback((account: WalletAccountType | undefined) => { + if (account) { + history.push(`${WalletRoutes.Accounts}/${account.address}`) + } + }, []) + + const onClickAddAccount = React.useCallback((tabId: AddAccountNavTypes) => () => { + switch (tabId) { + case 'create': return history.push(WalletRoutes.CreateAccountModalStart) + case 'hardware': return history.push(WalletRoutes.AddHardwareAccountModalStart) + case 'import': return history.push(WalletRoutes.ImportAccountModalStart) + default: return history.push(WalletRoutes.AddAccountModal) + } + // dispatch(WalletPageActions.setShowAddModal(true)) + }, []) + + const onRemoveAccount = React.useCallback((address: string, hardware: boolean, coin: BraveWallet.CoinType) => { + if (hardware) { + dispatch(WalletPageActions.removeHardwareAccount({ address, coin })) + return + } + dispatch(WalletPageActions.removeImportedAccount({ address, coin })) + }, []) + // memos const primaryAccounts = React.useMemo(() => { return accounts.filter((account) => account.accountType === 'Primary') diff --git a/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx b/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx index 781870957eaa..38980e819022 100644 --- a/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx +++ b/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx @@ -4,12 +4,8 @@ // you can obtain one at http://mozilla.org/MPL/2.0/. import * as React from 'react' -import { useDispatch } from 'react-redux' import { Route, useHistory, useParams, Switch } from 'react-router-dom' -// actions -import { WalletPageActions } from '../../../../page/actions' - // utils import { getLocale } from '../../../../../common/locale' @@ -17,23 +13,21 @@ import { getLocale } from '../../../../../common/locale' import { BraveWallet, TopTabNavTypes, - WalletAccountType, UpdateAccountNamePayloadType, - WalletRoutes, - AddAccountNavTypes + WalletRoutes } from '../../../../constants/types' import { TOP_NAV_OPTIONS } from '../../../../options/top-nav-options' -import { HardwareWalletConnectOpts } from '../../popup-modals/add-account-modal/hardware-wallet-connect/types' // style import { StyledWrapper } from './style' // components -import { TopTabNav, WalletBanner, AddAccountModal, EditVisibleAssetsModal } from '../../' +import { TopTabNav, WalletBanner, EditVisibleAssetsModal } from '../../' import { PortfolioOverview } from '../portfolio/portfolio-overview' import { PortfolioAsset } from '../portfolio/portfolio-asset' import { Accounts } from '../accounts/accounts' import { Account } from '../accounts/account' +import { AddAccountModal } from '../../popup-modals/add-account-modal/add-account-modal' interface ParamsType { category?: TopTabNavTypes @@ -41,51 +35,23 @@ interface ParamsType { } export interface Props { - onCreateAccount: (name: string, coin: BraveWallet.CoinType) => void - onImportAccount: (accountName: string, privateKey: string, coin: BraveWallet.CoinType) => void - onImportFilecoinAccount: (accountName: string, key: string, network: string) => void - onConnectHardwareWallet: (opts: HardwareWalletConnectOpts) => Promise - onAddHardwareAccounts: (selected: BraveWallet.HardwareWalletAccount[]) => void - getBalance: (address: string, coin: BraveWallet.CoinType) => Promise onUpdateAccountName: (payload: UpdateAccountNamePayloadType) => { success: boolean } - onRemoveAccount: (address: string, hardware: boolean, coin: BraveWallet.CoinType) => void onViewPrivateKey: (address: string, isDefault: boolean, coin: BraveWallet.CoinType) => void onDoneViewingPrivateKey: () => void - onImportAccountFromJson: (accountName: string, password: string, json: string) => void - onSetImportError: (error: boolean) => void onOpenWalletSettings: () => void - hasImportError: boolean needsBackup: boolean - accounts: WalletAccountType[] - isFilecoinEnabled: boolean - isSolanaEnabled: boolean - selectedNetwork: BraveWallet.NetworkInfo defaultWallet: BraveWallet.DefaultWallet isMetaMaskInstalled: boolean } const CryptoView = (props: Props) => { const { - onCreateAccount, - onConnectHardwareWallet, - onAddHardwareAccounts, - getBalance, - onImportAccount, - onImportFilecoinAccount, onUpdateAccountName, - onRemoveAccount, onViewPrivateKey, onDoneViewingPrivateKey, - onImportAccountFromJson, - onSetImportError, onOpenWalletSettings, defaultWallet, - hasImportError, - selectedNetwork, needsBackup, - accounts, - isFilecoinEnabled, - isSolanaEnabled, isMetaMaskInstalled } = props @@ -94,15 +60,11 @@ const CryptoView = (props: Props) => { const [showBackupWarning, setShowBackupWarning] = React.useState(needsBackup) const [showDefaultWalletBanner, setShowDefaultWalletBanner] = React.useState(needsBackup) const [showMore, setShowMore] = React.useState(false) - const [addAccountModalTab, setAddAccountModalTab] = React.useState('create') // routing const history = useHistory() const { category } = useParams() - // redux - const dispatch = useDispatch() - // methods const onShowBackup = React.useCallback(() => { history.push(WalletRoutes.Backup) @@ -132,28 +94,11 @@ const CryptoView = (props: Props) => { setShowDefaultWalletBanner(false) }, []) - const onCloseAddModal = React.useCallback(() => { - history.push(WalletRoutes.Accounts) - dispatch(WalletPageActions.setShowAddModal(false)) - }, []) - - const onClickAddAccount = React.useCallback((tabId: AddAccountNavTypes) => () => { - setAddAccountModalTab(tabId) - dispatch(WalletPageActions.setShowAddModal(true)) - history.push(WalletRoutes.AddAccountModal) - }, []) - const goBack = React.useCallback(() => { history.push(WalletRoutes.Accounts) setHideNav(false) }, []) - const onSelectAccount = React.useCallback((account: WalletAccountType | undefined) => { - if (account) { - history.push(`${WalletRoutes.Accounts}/${account.address}`) - } - }, []) - const onClickSettings = React.useCallback(() => { chrome.tabs.create({ url: 'chrome://settings/wallet' }, () => { if (chrome.runtime.lastError) { @@ -172,7 +117,10 @@ const CryptoView = (props: Props) => { } }, [showMore]) - const hideVisibleAssetsModal = React.useCallback(() => onShowVisibleAssetsModal(false), []) + const hideVisibleAssetsModal = React.useCallback( + () => onShowVisibleAssetsModal(false), + [onShowVisibleAssetsModal] + ) // memos const nav = React.useMemo(() => ( @@ -230,70 +178,54 @@ const CryptoView = (props: Props) => { {/* Portfolio */} - - {nav} - - - - - + {/* Show portfolio overview in background */} {nav} - - + + - {/* Accounts */} - + {nav} - - + - + {/* Accounts */} + {/* Show accounts overview in background */} {nav} - + - + + + {nav} + + + + + + {/* modals */} + + + + + + + + ) diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx index e2b2600989ad..a36deab58e62 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx @@ -65,17 +65,9 @@ import { } from './style' import { Skeleton } from '../../../shared/loading-skeleton/styles' -interface Props { - onClickAddAccount: (tabId: AddAccountNavTypes) => () => void -} - const AssetIconWithPlaceholder = withPlaceholderIcon(AssetIcon, { size: 'big', marginLeft: 0, marginRight: 12 }) -export const PortfolioAsset = (props: Props) => { - const { - onClickAddAccount - } = props - +export const PortfolioAsset = () => { // routing const history = useHistory() const { id: assetId } = useParams<{ id?: string }>() @@ -268,6 +260,10 @@ export const PortfolioAsset = (props: Props) => { const isNftAsset = selectedAssetFromParams?.isErc721 // methods + const onClickAddAccount = React.useCallback((tabId: AddAccountNavTypes) => () => { + history.push(WalletRoutes.AddAccountModal) + }, []) + const onChangeTimeline = React.useCallback((timeline: BraveWallet.AssetPriceTimeframe) => { dispatch(WalletPageActions.selectAsset({ asset: selectedAsset, diff --git a/components/brave_wallet_ui/constants/types.ts b/components/brave_wallet_ui/constants/types.ts index cd3716cab8fc..f5321868855b 100644 --- a/components/brave_wallet_ui/constants/types.ts +++ b/components/brave_wallet_ui/constants/types.ts @@ -558,21 +558,44 @@ export type UpdateAccountNamePayloadType = { } export enum WalletRoutes { - Accounts = '/crypto/accounts', - AccountsSub = '/crypto/accounts/:id?', - AddAccountModal = '/crypto/accounts/add-account', - AddAssetModal = '/crypto/portfolio/add-asset', - Backup = '/crypto/backup-wallet', + // index CryptoPage = '/crypto/:category/:id?', + + // onboarding Onboarding = '/crypto/onboarding', OnboardingCreatePassword = '/crypto/onboarding/create-password', OnboardingBackupWallet = '/crypto/onboarding/backup-wallet', OnboardingImportMetaMask = '/crypto/onboarding/import-metamask-wallet', OnboardingImportCryptoWallets = '/crypto/onboarding/import-legacy-wallet', - Portfolio = '/crypto/portfolio', - PortfolioSub = '/crypto/portfolio/:id', + + // accounts + Accounts = '/crypto/accounts', + Account = '/crypto/accounts/:id', + + // add account modals + AddAccountModal = '/crypto/accounts/add-account', + CreateAccountModalStart = '/crypto/accounts/add-account/create/', + CreateAccountModal = '/crypto/accounts/add-account/create/:accountTypeName?', + + // import account modals + ImportAccountModalStart = '/crypto/accounts/add-account/import/', + ImportAccountModal = '/crypto/accounts/add-account/import/:accountTypeName?', + + // hardware wallet import modals + AddHardwareAccountModalStart = '/crypto/accounts/add-account/hardware', + AddHardwareAccountModal = '/crypto/accounts/add-account/hardware/:accountTypeName?', + + // wallet mangement + Backup = '/crypto/backup-wallet', Restore = '/crypto/restore-wallet', Unlock = '/crypto/unlock', + + // portfolio + Portfolio = '/crypto/portfolio', + PortfolioAsset = '/crypto/portfolio/:id', + + // portfolio asset modals + AddAssetModal = '/crypto/portfolio/add-asset', } export const WalletOrigin = 'chrome://wallet' diff --git a/components/brave_wallet_ui/page/container.tsx b/components/brave_wallet_ui/page/container.tsx index 47b549a8188e..269c54e81abd 100644 --- a/components/brave_wallet_ui/page/container.tsx +++ b/components/brave_wallet_ui/page/container.tsx @@ -49,13 +49,10 @@ export const Container = () => { const dispatch = useDispatch() const { - isFilecoinEnabled, - isSolanaEnabled, isWalletCreated, isWalletLocked, isWalletBackedUp, hasIncorrectPassword, - accounts, selectedNetwork, selectedAccount, hasInitialized, @@ -64,7 +61,6 @@ export const Container = () => { } = useSelector(({ wallet }: { wallet: WalletState }) => wallet) const setupStillInProgress = useSelector(({ page }: { page: PageState }) => page.setupStillInProgress) - const importAccountError = useSelector(({ page }: { page: PageState }) => page.importAccountError) // state const [sessionRoute, setSessionRoute] = React.useState(undefined) @@ -75,11 +71,7 @@ export const Container = () => { // custom hooks const { buyAssetOptions, wyreAssetOptions, rampAssetOptions } = useAssets() - const { - getBalance, - getBuyAssetUrl, - onConnectHardwareWallet - } = useLib() + const { getBuyAssetUrl } = useLib() // methods const onToggleShowRestore = React.useCallback(() => { @@ -130,21 +122,7 @@ export const Container = () => { } }, [hasIncorrectPassword]) - const onHideAddModal = React.useCallback(() => { - dispatch(WalletPageActions.setShowAddModal(false)) - }, []) - - const onCreateAccount = React.useCallback((name: string, coin: BraveWallet.CoinType) => { - const created = dispatch(WalletActions.addAccount({ accountName: name, coin: coin })) - if (walletLocation.includes(WalletRoutes.Accounts)) { - history.push(WalletRoutes.Accounts) - } - if (created) { - onHideAddModal() - } - }, [onHideAddModal]) - - const onSubmitBuy = (asset: BraveWallet.BlockchainToken) => { + const onSubmitBuy = React.useCallback((asset: BraveWallet.BlockchainToken) => { getBuyAssetUrl({ asset, onRampProvider: selectedBuyOption, @@ -152,37 +130,9 @@ export const Container = () => { address: selectedAccount.address, amount: buyAmount }) - .then(url => window.open(url, '_blank')) - .catch(e => console.error(e)) - } - - const onAddHardwareAccounts = React.useCallback((selected: BraveWallet.HardwareWalletAccount[]) => { - dispatch(WalletPageActions.addHardwareAccounts(selected)) - }, []) - - const onImportAccount = React.useCallback((accountName: string, privateKey: string, coin: BraveWallet.CoinType) => { - dispatch(WalletPageActions.importAccount({ accountName, privateKey, coin })) - }, []) - - const onImportFilecoinAccount = React.useCallback((accountName: string, privateKey: string, network: string) => { - dispatch(WalletPageActions.importFilecoinAccount({ accountName, privateKey, network })) - }, []) - - const onImportAccountFromJson = React.useCallback((accountName: string, password: string, json: string) => { - dispatch(WalletPageActions.importAccountFromJson({ accountName, password, json })) - }, []) - - const onSetImportAccountError = React.useCallback((hasError: boolean) => { - dispatch(WalletPageActions.setImportAccountError(hasError)) - }, []) - - const onRemoveAccount = React.useCallback((address: string, hardware: boolean, coin: BraveWallet.CoinType) => { - if (hardware) { - dispatch(WalletPageActions.removeHardwareAccount({ address, coin })) - return - } - dispatch(WalletPageActions.removeImportedAccount({ address, coin })) - }, []) + .then(url => window.open(url, '_blank')) + .catch(e => console.error(e)) + }, [getBuyAssetUrl, selectedBuyOption, selectedNetwork, selectedAccount, buyAmount]) const onUpdateAccountName = React.useCallback((payload: UpdateAccountNamePayloadType): { success: boolean } => { const result = dispatch(WalletPageActions.updateAccountName(payload)) @@ -302,23 +252,9 @@ export const Container = () => { > void - onImportAccountFromJson: (accountName: string, password: string, json: string) => void - onRemoveAccount: (address: string, hardware: boolean, coin: BraveWallet.CoinType) => void - onToggleAddModal: () => void - getBalance: (address: string) => Promise - onAddHardwareAccounts: (selected: BraveWallet.HardwareWalletAccount[]) => void - onConnectHardwareWallet: (opts: HardwareWalletConnectOpts) => Promise - onImportAccount: (accountName: string, privateKey: string, coin: BraveWallet.CoinType) => void - onImportFilecoinAccount: (accountName: string, privateKey: string, network: string) => void - onCreateAccount: (name: string) => void onShowBackup: () => void } const CryptoStoryView = (props: Props) => { const { - hasImportError, - selectedNetwork, needsBackup, - accounts, showAddModal, - isFilecoinEnabled, - isSolanaEnabled, - onShowBackup, - onCreateAccount, - onConnectHardwareWallet, - onAddHardwareAccounts, - getBalance, - onImportAccount, - onImportFilecoinAccount, - onToggleAddModal, - onRemoveAccount, - onImportAccountFromJson, - onSetImportError + onShowBackup } = props const [showBackupWarning, setShowBackupWarning] = React.useState(needsBackup) const [showDefaultWalletBanner, setShowDefaultWalletBanner] = React.useState(needsBackup) - const [, setSelectedAccount] = React.useState() - const [hideNav, setHideNav] = React.useState(false) + const [hideNav] = React.useState(false) const [filteredAppsList, setFilteredAppsList] = React.useState(AppsList()) const [favoriteApps, setFavoriteApps] = React.useState([ AppsList()[0].appList[0] ]) const [selectedTab, setSelectedTab] = React.useState('portfolio') - const [addAccountModalTab, setAddAccountModalTab] = React.useState('create') const [showMore, setShowMore] = React.useState(false) const browseMore = () => { @@ -104,28 +65,10 @@ const CryptoStoryView = (props: Props) => { filterAppList(event, AppsList(), setFilteredAppsList) } - const toggleNav = () => { - setHideNav(!hideNav) - } - const onDismissBackupWarning = () => { setShowBackupWarning(false) } - const onClickAddAccount = (tabId: AddAccountNavTypes) => () => { - setAddAccountModalTab(tabId) - onToggleAddModal() - } - - const onCloseAddModal = () => { - onToggleAddModal() - } - - const onSelectAccount = (account: WalletAccountType) => { - setSelectedAccount(account) - toggleNav() - } - const onDismissDefaultWalletBanner = () => { setShowDefaultWalletBanner(false) } @@ -200,30 +143,10 @@ const CryptoStoryView = (props: Props) => { } {selectedTab === 'accounts' && - + } {showAddModal && - + }
From 2356c8a74570438f0a4b264bd6327878922f16ec Mon Sep 17 00:00:00 2001 From: Josh Leonard Date: Tue, 26 Apr 2022 21:07:24 -0600 Subject: [PATCH 3/6] chore: update wallet concept story --- .../common/async/__mocks__/lib.ts | 22 + .../stories/wallet-concept.tsx | 567 +----------------- .../stories/wallet-extension-panels.tsx | 250 +++++++- .../wrappers/wallet-page-story-wrapper.tsx | 31 +- 4 files changed, 299 insertions(+), 571 deletions(-) diff --git a/components/brave_wallet_ui/common/async/__mocks__/lib.ts b/components/brave_wallet_ui/common/async/__mocks__/lib.ts index f3ff20b213b1..9dedc89c41ea 100644 --- a/components/brave_wallet_ui/common/async/__mocks__/lib.ts +++ b/components/brave_wallet_ui/common/async/__mocks__/lib.ts @@ -5,6 +5,7 @@ import { mockBasicAttentionToken, mockEthToken } from '../../../stories/mock-data/mock-asset-options' import { BraveWallet, GetChecksumEthAddressReturnInfo, GetEthAddrReturnInfo } from '../../../constants/types' +import { HardwareWalletConnectOpts } from '../../../components/desktop/popup-modals/add-account-modal/hardware-wallet-connect/types' let mockedAllowance = '1000000000000000000' // 1 unit let mockedIsSwapSupported = true @@ -88,3 +89,24 @@ export const isStrongPassword = (value: string) => { /\d/.test(value) // contains a number ) } + +export const onConnectHardwareWallet = (opts: HardwareWalletConnectOpts): Promise => { + const makeDerivationPath = (index: number): string => `m/44'/60'/${index}'/0/0` + + return new Promise((resolve) => { + resolve(Array.from({ length: opts.stopIndex - opts.startIndex }, (_, i) => ({ + address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + derivationPath: makeDerivationPath(i + opts.startIndex), + name: 'Ledger 1', + hardwareVendor: 'Ledger', + deviceId: 'device1', + coin: BraveWallet.CoinType.ETH + }))) + }) +} + +export const getBalance = (): Promise => { + return new Promise(async (resolve) => { + resolve('0') + }) +} diff --git a/components/brave_wallet_ui/stories/wallet-concept.tsx b/components/brave_wallet_ui/stories/wallet-concept.tsx index 962c67d19a95..7699a70f5ae1 100644 --- a/components/brave_wallet_ui/stories/wallet-concept.tsx +++ b/components/brave_wallet_ui/stories/wallet-concept.tsx @@ -1,37 +1,8 @@ import * as React from 'react' -import { WalletWidgetStandIn } from './style' -import { - WalletPageLayout, - WalletSubViewLayout, - LockScreen, - OnboardingRestore -} from '../components/desktop' -import { - NavTypes, - BraveWallet, - RPCResponseType, - UserAccountType, - AccountTransactions, - BuySendSwapTypes, - WalletAccountType -} from '../constants/types' -import Onboarding from './screens/onboarding' -import BackupWallet from './screens/backup-wallet' -import CryptoStoryView from './screens/crypto-story-view' + import './locale' -import BuySendSwap from './screens/buy-send-swap' -import { mockUserAccounts } from './mock-data/user-accounts' -import { mockRPCResponse } from './mock-data/rpc-response' -import { mockUserWalletPreferences } from './mock-data/user-wallet-preferences' -import { getLocale } from '../../common/locale' -import { - HardwareWalletConnectOpts -} from '../components/desktop/popup-modals/add-account-modal/hardware-wallet-connect/types' -import { mockNetworks } from './mock-data/mock-networks' -import { SweepstakesBanner } from '../components/desktop/sweepstakes-banner' -import { mockAccountAssetOptions, mockNewAssetOptions } from './mock-data/mock-asset-options' -import { mockOriginInfo } from './mock-data/mock-origin-info' import WalletPageStory from './wrappers/wallet-page-story-wrapper' +import Container from '../page/container' export default { title: 'Wallet/Desktop', @@ -41,532 +12,24 @@ export default { } } -export const transactionDummyData: AccountTransactions = { - [mockUserAccounts[0].id]: [ - { - fromAddress: 'ETHEREUM ACCOUNT 1', - id: '13cf4882-d3c0-44cd-a8c2-aca1fcf85c4a', - txDataUnion: { - ethTxData1559: { - baseData: { - data: Array.from(new Uint8Array(24)), - gasLimit: '0xfde8', - gasPrice: '0x20000000000', - nonce: '0x1', - to: 'ETHEREUM ACCOUNT 2', - value: '0xb1a2bc2ec50000' - }, - chainId: '', - maxFeePerGas: '', - maxPriorityFeePerGas: '', - gasEstimation: undefined - }, - ethTxData: undefined, - solanaTxData: undefined, - filTxData: undefined - }, - txHash: '0x55732e30af74a450cd438be2a02c765ea62cb4ec8dda5cb12ed8dc5d21ac15d3', - txStatus: 3, - txArgs: [], - txParams: [], - txType: 0, - createdTime: { microseconds: BigInt((Date.now() * 1000) - 1000 * 60 * 5 * 1000) }, - submittedTime: { microseconds: BigInt((Date.now() * 1000) - 1000 * 60 * 5) }, - confirmedTime: { microseconds: BigInt((Date.now() * 1000) - 1000 * 60 * 5) }, - originInfo: mockOriginInfo - }, - { - fromAddress: '0x7d66c9ddAED3115d93Bd1790332f3Cd06Cf52B14', - id: '13cf4882-d3c0-44cd-a8c2-aca1fcf85c4a', - txDataUnion: { - ethTxData1559: { - baseData: { - data: Array.from(new Uint8Array(24)), - gasLimit: '0xfde8', - gasPrice: '0x20000000000', - nonce: '0x1', - to: '0xcd3a3f8e0e4bdc174c9e2e63b4c22e15a7f7f92a', - value: '0xb1a2bc2ec50000' - }, - chainId: '', - maxFeePerGas: '', - maxPriorityFeePerGas: '', - gasEstimation: undefined - }, - ethTxData: undefined, - solanaTxData: undefined, - filTxData: undefined - }, - txHash: '0x55732e30af74a450cd438be2a02c765ea62cb4ec8dda5cb12ed8dc5d21ac15d3', - txStatus: 3, - txArgs: [], - txParams: [], - txType: 0, - createdTime: { microseconds: BigInt(0) }, - submittedTime: { microseconds: BigInt(0) }, - confirmedTime: { microseconds: BigInt(0) }, - originInfo: mockOriginInfo - }, - { - fromAddress: '0x7843981e0b96135073b26043ea24c950d4ec385b', - id: '13cf4882-d3c0-44cd-a8c2-aca1fcf85c4a', - txDataUnion: { - ethTxData1559: { - baseData: { - data: Array.from(new Uint8Array(24)), - gasLimit: '0xfde8', - gasPrice: '0x20000000000', - nonce: '0x1', - to: '0x7d66c9ddAED3115d93Bd1790332f3Cd06Cf52B14', - value: '0xb1a2bc2ec90000' - }, - chainId: '', - maxFeePerGas: '', - maxPriorityFeePerGas: '', - gasEstimation: undefined - }, - ethTxData: undefined, - solanaTxData: undefined, - filTxData: undefined - }, - txHash: '0x55732e30af74a450cd438be2a02c765ea62cb4ec8dda5cb12ed8dc5d21ac15d3', - txStatus: 4, - txArgs: [], - txParams: [], - txType: 0, - createdTime: { microseconds: BigInt(0) }, - submittedTime: { microseconds: BigInt(0) }, - confirmedTime: { microseconds: BigInt(0) }, - originInfo: mockOriginInfo - }, - { - fromAddress: '0x7d66c9ddAED3115d93Bd1790332f3Cd06Cf52B14', - id: '13cf4882-d3c0-44cd-a8c2-aca1fcf85c4a', - txDataUnion: { - ethTxData1559: { - baseData: { - data: Array.from(new Uint8Array(24)), - gasLimit: '0xfde8', - gasPrice: '0x20000000000', - nonce: '0x1', - to: '0xcd3a3f8e0e4bdc174c9e2e63b4c22e15a7f7f92a', - value: '0xb1a2bc2ec90000' - }, - chainId: '', - maxFeePerGas: '', - maxPriorityFeePerGas: '', - gasEstimation: undefined - }, - ethTxData: undefined, - solanaTxData: undefined, - filTxData: undefined - }, - txHash: '0x55732e30af74a450cd438be2a02c765ea62cb4ec8dda5cb12ed8dc5d21ac15d3', - txStatus: 2, - txArgs: [], - txParams: [], - txType: 0, - createdTime: { microseconds: BigInt(0) }, - submittedTime: { microseconds: BigInt(0) }, - confirmedTime: { microseconds: BigInt(0) }, - originInfo: mockOriginInfo - }, - { - fromAddress: '0x7d66c9ddAED3115d93Bd1790332f3Cd06Cf52B14', - id: '13cf4882-d3c0-44cd-a8c2-aca1fcf85c4a', - txDataUnion: { - ethTxData1559: { - baseData: { - data: Array.from(new Uint8Array(24)), - gasLimit: '0xfde8', - gasPrice: '0x20000000000', - nonce: '0x1', - to: '0xcd3a3f8e0e4bdc174c9e2e63b4c22e15a7f7f92a', - value: '0xb1a2bc2ec90000' - }, - chainId: '', - maxFeePerGas: '', - maxPriorityFeePerGas: '', - gasEstimation: undefined - }, - ethTxData: undefined, - solanaTxData: undefined, - filTxData: undefined - }, - txHash: '0x55732e30af74a450cd438be2a02c765ea62cb4ec8dda5cb12ed8dc5d21ac15d3', - txStatus: 1, - txArgs: [], - txParams: [], - txType: 0, - createdTime: { microseconds: BigInt(0) }, - submittedTime: { microseconds: BigInt(0) }, - confirmedTime: { microseconds: BigInt(0) }, - originInfo: mockOriginInfo - } - ], - [mockUserAccounts[1].id]: [ - { - fromAddress: '0x73A29A1da97149722eB09c526E4eAd698895bDCf', - id: '13cf4882-d3c0-44cd-a8c2-aca1fcf85c4a', - txDataUnion: { - ethTxData1559: { - baseData: { - data: Array.from(new Uint8Array(24)), - gasLimit: '0xfde8', - gasPrice: '0x20000000000', - nonce: '0x1', - to: '0xcd3a3f8e0e4bdc174c9e2e63b4c22e15a7f7f92a', - value: '0xb1a2bc2ec90000' - }, - chainId: '', - maxFeePerGas: '', - maxPriorityFeePerGas: '', - gasEstimation: undefined - }, - ethTxData: undefined, - solanaTxData: undefined, - filTxData: undefined - }, - txHash: '0x55732e30af74a450cd438be2a02c765ea62cb4ec8dda5cb12ed8dc5d21ac15d3', - txStatus: 0, - txArgs: [], - txParams: [], - txType: 0, - createdTime: { microseconds: BigInt(0) }, - submittedTime: { microseconds: BigInt(0) }, - confirmedTime: { microseconds: BigInt(0) }, - originInfo: mockOriginInfo - }, - { - fromAddress: '0x73A29A1da97149722eB09c526E4eAd698895bDCf', - id: '13cf4882-d3c0-44cd-a8c2-aca1fcf85c4a', - txDataUnion: { - ethTxData1559: { - baseData: { - data: Array.from(new Uint8Array(24)), - gasLimit: '0xfde8', - gasPrice: '0x20000000000', - nonce: '0x1', - to: '0xcd3a3f8e0e4bdc174c9e2e63b4c22e15a7f7f92a', - value: '0xb1a2bc2ec90000' - }, - chainId: '', - maxFeePerGas: '', - maxPriorityFeePerGas: '', - gasEstimation: undefined - }, - ethTxData: undefined, - solanaTxData: undefined, - filTxData: undefined - }, - txHash: '0x55732e30af74a450cd438be2a02c765ea62cb4ec8dda5cb12ed8dc5d21ac15d3', - txStatus: 5, - txArgs: [], - txParams: [], - txType: 0, - createdTime: { microseconds: BigInt(0) }, - submittedTime: { microseconds: BigInt(0) }, - confirmedTime: { microseconds: BigInt(0) }, - originInfo: mockOriginInfo - } - ] -} - -export const _DesktopWalletConcept = (args: { onboarding: boolean, locked: boolean }) => { - const { - onboarding, - locked - } = args - const [view] = React.useState('crypto') - const [isFilecoinEnabled] = React.useState(true) - const [isSolanaEnabled] = React.useState(true) - const [needsOnboarding] = React.useState(onboarding) - const [walletLocked, setWalletLocked] = React.useState(locked) - const [needsBackup] = React.useState(true) - const [showBackup, setShowBackup] = React.useState(false) - const [inputValue, setInputValue] = React.useState('') - const [hasPasswordError, setHasPasswordError] = React.useState(false) - const [selectedAsset] = React.useState() - const [selectedNetwork] = React.useState(mockNetworks[0]) - const [, setSelectedAccount] = React.useState(mockUserAccounts[0]) - const [showAddModal, setShowAddModal] = React.useState(false) - const [buyAmount, setBuyAmount] = React.useState('') - const [isRestoring, setIsRestoring] = React.useState(false) - const [importAccountError, setImportAccountError] = React.useState(false) - const [selectedWidgetTab, setSelectedWidgetTab] = React.useState('buy') - const [selectedBuyOption, setSelectedBuyOption] = React.useState(BraveWallet.OnRampProvider.kRamp) - - const onToggleRestore = () => { - setIsRestoring(!isRestoring) - } - - // In the future these will be actual paths - // for example wallet/rewards - // const navigateTo = (path: NavTypes) => { - // setView(path) - // } - - const unlockWallet = () => { - if (inputValue !== 'password') { - setHasPasswordError(true) - } else { - setWalletLocked(false) - } - } - - const handlePasswordChanged = (value: string) => { - setHasPasswordError(false) - setInputValue(value) - } - - const onShowBackup = () => { - setShowBackup(true) - } - - const onHideBackup = () => { - setShowBackup(false) - } - - // This returns info about a single asset - const assetInfo = (account: RPCResponseType) => { - return account.assets.find((a) => a.symbol === selectedAsset?.symbol) - } - - // This returns the balance of a single accounts asset - const singleAccountBalance = (account: RPCResponseType) => { - const balance = assetInfo(account)?.balance - return balance ? balance.toString() : '' - } - - const accountInfo = (asset: RPCResponseType) => { - const foundAccount = mockUserAccounts.find((account) => account.address === asset.address) - return foundAccount - } - - // This returns a list of accounts with a balance of the selected asset - const accounts = React.useMemo(() => { - const id = selectedAsset ? selectedAsset.symbol : '' - const list = selectedAsset ? mockRPCResponse.filter((account) => account.assets.map((assetID) => assetID.symbol).includes(id)) : mockRPCResponse - const newList = list.map((wallet) => { - const walletInfo = accountInfo(wallet) - const id = walletInfo ? walletInfo.id : '' - const name = walletInfo ? walletInfo.name : getLocale('braveWalletAccount') - return { - id: id, - name: name, - address: wallet.address, - nativeBalanceRegistry: { - '0x1': singleAccountBalance(wallet) - }, - asset: selectedAsset ? selectedAsset.symbol : '', - accountType: 'Primary', - tokenBalanceRegistry: {}, - coin: BraveWallet.CoinType.ETH - } as WalletAccountType - }) - return newList - }, [selectedAsset, mockRPCResponse]) - - // This will scrape all of the user's accounts and combine the balances for a single asset - const scrapedFullAssetBalance = (asset: BraveWallet.BlockchainToken) => { - const response = mockRPCResponse - const amounts = response.map((account) => { - const balance = account.assets.find((item) => item.id === asset.contractAddress)?.balance - return balance || 0 - }) - const grandTotal = amounts.reduce(function (a, b) { - return a + b - }, 0) - return grandTotal - } - - const userAssetList = React.useMemo(() => { - const userList = mockUserWalletPreferences.viewableAssets - const newList = mockNewAssetOptions.filter((asset) => userList.includes(asset.contractAddress)) - return newList.map((asset) => { - return { - asset: asset, - assetBalance: scrapedFullAssetBalance(asset).toString() - } - }) - }, [mockUserWalletPreferences.viewableAssets]) - - const onCreateAccount = (name: string) => { - alert(name) - } - - const onImportAccount = () => { - // doesnt do anything in storybook - } - const onImportFilecoinAccount = () => { - // doesnt do anything in storybook - } - - const onImportAccountFromJson = () => { - // doesnt do anything in storybook - } - - const onToggleAddModal = () => { - setShowAddModal(!showAddModal) - } - - const onSubmitBuy = (asset: BraveWallet.BlockchainToken) => { - alert(`Buy ${asset.symbol} asset`) - } - - const onSelectAccount = (account: UserAccountType) => { - setSelectedAccount(account) - } - - const onSetBuyAmount = (value: string) => { - setBuyAmount(value) - } - - const onRemoveAccount = () => { - alert('Will Remove Account') - } - - const onConnectHardwareWallet = (opts: HardwareWalletConnectOpts): Promise => { - const makeDerivationPath = (index: number): string => `m/44'/60'/${index}'/0/0` - - return new Promise((resolve) => { - resolve(Array.from({ length: opts.stopIndex - opts.startIndex }, (_, i) => ({ - address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - derivationPath: makeDerivationPath(i + opts.startIndex), - name: 'Ledger 1', - hardwareVendor: 'Ledger', - deviceId: 'device1', - coin: BraveWallet.CoinType.ETH - }))) - }) - } - - const getBalance = (): Promise => { - return new Promise(async (resolve) => { - resolve('0') - }) - } - - const onAddHardwareAccounts = (accounts: BraveWallet.HardwareWalletAccount[]) => { - console.log(accounts) - } - - const onSetImportAccountError = (hasError: boolean) => setImportAccountError(hasError) - +export const _DesktopWalletConcept = () => { return ( - - - {/* */} - - {isRestoring ? ( - - ) : ( - <> - {needsOnboarding - ? ( - - ) : ( - <> - {view === 'crypto' ? ( - <> - {walletLocked ? ( - - ) : ( - <> - {showBackup ? ( - - ) : ( - - )} - - )} - - ) : ( -
-

{view} view

-
- )} - - )} - - )} - -
- {!needsOnboarding && !walletLocked && - - - - - } -
+ + ) } -_DesktopWalletConcept.args = { - onboarding: false, - locked: false -} - _DesktopWalletConcept.story = { name: 'Concept' } diff --git a/components/brave_wallet_ui/stories/wallet-extension-panels.tsx b/components/brave_wallet_ui/stories/wallet-extension-panels.tsx index c9a7f1a5c368..a5fa94b20b2c 100644 --- a/components/brave_wallet_ui/stories/wallet-extension-panels.tsx +++ b/components/brave_wallet_ui/stories/wallet-extension-panels.tsx @@ -40,7 +40,8 @@ import { AppsListType, BuySendSwapViewTypes, Origin, - WalletState + WalletState, + AccountTransactions } from '../constants/types' import { AppsList } from '../options/apps-list-options' import { filterAppList } from '../utils/filter-app-list' @@ -56,7 +57,13 @@ import { import { mockNetworks } from './mock-data/mock-networks' import { PanelTitles } from '../options/panel-titles' import './locale' -import { transactionDummyData } from './wallet-concept' +import { LibContext } from '../common/context/lib.context' +import { createSendCryptoReducer } from '../common/reducers/send_crypto_reducer' +import { createWalletReducer } from '../common/reducers/wallet_reducer' +import { createPageReducer } from '../page/reducers/page_reducer' + +// mocks +import * as MockedLib from '../common/async/__mocks__/lib' import { mockedErc20ApprovalTransaction, mockTransactionInfo } from './mock-data/mock-transaction-info' import { mockDefaultCurrencies } from './mock-data/mock-default-currencies' import { mockTransactionSpotPrices } from './mock-data/current-price-data' @@ -64,15 +71,11 @@ import { mockAccounts, mockedTransactionAccounts } from './mock-data/mock-wallet import { mockEncryptionKeyRequest, mockDecryptRequest } from './mock-data/mock-encryption-key-payload' import { mockOriginInfo } from './mock-data/mock-origin-info' import { mockAccountAssetOptions, mockBasicAttentionToken, mockEthToken, mockNewAssetOptions } from './mock-data/mock-asset-options' -import { LibContext } from '../common/context/lib.context' -import * as MockedLib from '../common/async/__mocks__/lib' - -import { createSendCryptoReducer } from '../common/reducers/send_crypto_reducer' -import { createWalletReducer } from '../common/reducers/wallet_reducer' -import { createPageReducer } from '../page/reducers/page_reducer' import { mockPageState } from './mock-data/mock-page-state' import { mockWalletState } from './mock-data/mock-wallet-state' import { mockSendCryptoState } from './mock-data/send-crypto-state' +import { mockUserAccounts } from './mock-data/user-accounts' + export default { title: 'Wallet/Extension/Panels', parameters: { @@ -83,6 +86,237 @@ export default { } } +const transactionDummyData: AccountTransactions = { + [mockUserAccounts[0].id]: [ + { + fromAddress: 'ETHEREUM ACCOUNT 1', + id: '13cf4882-d3c0-44cd-a8c2-aca1fcf85c4a', + txDataUnion: { + ethTxData1559: { + baseData: { + data: Array.from(new Uint8Array(24)), + gasLimit: '0xfde8', + gasPrice: '0x20000000000', + nonce: '0x1', + to: 'ETHEREUM ACCOUNT 2', + value: '0xb1a2bc2ec50000' + }, + chainId: '', + maxFeePerGas: '', + maxPriorityFeePerGas: '', + gasEstimation: undefined + }, + ethTxData: undefined, + solanaTxData: undefined, + filTxData: undefined + }, + txHash: '0x55732e30af74a450cd438be2a02c765ea62cb4ec8dda5cb12ed8dc5d21ac15d3', + txStatus: 3, + txArgs: [], + txParams: [], + txType: 0, + createdTime: { microseconds: BigInt((Date.now() * 1000) - 1000 * 60 * 5 * 1000) }, + submittedTime: { microseconds: BigInt((Date.now() * 1000) - 1000 * 60 * 5) }, + confirmedTime: { microseconds: BigInt((Date.now() * 1000) - 1000 * 60 * 5) }, + originInfo: mockOriginInfo + }, + { + fromAddress: '0x7d66c9ddAED3115d93Bd1790332f3Cd06Cf52B14', + id: '13cf4882-d3c0-44cd-a8c2-aca1fcf85c4a', + txDataUnion: { + ethTxData1559: { + baseData: { + data: Array.from(new Uint8Array(24)), + gasLimit: '0xfde8', + gasPrice: '0x20000000000', + nonce: '0x1', + to: '0xcd3a3f8e0e4bdc174c9e2e63b4c22e15a7f7f92a', + value: '0xb1a2bc2ec50000' + }, + chainId: '', + maxFeePerGas: '', + maxPriorityFeePerGas: '', + gasEstimation: undefined + }, + ethTxData: undefined, + solanaTxData: undefined, + filTxData: undefined + }, + txHash: '0x55732e30af74a450cd438be2a02c765ea62cb4ec8dda5cb12ed8dc5d21ac15d3', + txStatus: 3, + txArgs: [], + txParams: [], + txType: 0, + createdTime: { microseconds: BigInt(0) }, + submittedTime: { microseconds: BigInt(0) }, + confirmedTime: { microseconds: BigInt(0) }, + originInfo: mockOriginInfo + }, + { + fromAddress: '0x7843981e0b96135073b26043ea24c950d4ec385b', + id: '13cf4882-d3c0-44cd-a8c2-aca1fcf85c4a', + txDataUnion: { + ethTxData1559: { + baseData: { + data: Array.from(new Uint8Array(24)), + gasLimit: '0xfde8', + gasPrice: '0x20000000000', + nonce: '0x1', + to: '0x7d66c9ddAED3115d93Bd1790332f3Cd06Cf52B14', + value: '0xb1a2bc2ec90000' + }, + chainId: '', + maxFeePerGas: '', + maxPriorityFeePerGas: '', + gasEstimation: undefined + }, + ethTxData: undefined, + solanaTxData: undefined, + filTxData: undefined + }, + txHash: '0x55732e30af74a450cd438be2a02c765ea62cb4ec8dda5cb12ed8dc5d21ac15d3', + txStatus: 4, + txArgs: [], + txParams: [], + txType: 0, + createdTime: { microseconds: BigInt(0) }, + submittedTime: { microseconds: BigInt(0) }, + confirmedTime: { microseconds: BigInt(0) }, + originInfo: mockOriginInfo + }, + { + fromAddress: '0x7d66c9ddAED3115d93Bd1790332f3Cd06Cf52B14', + id: '13cf4882-d3c0-44cd-a8c2-aca1fcf85c4a', + txDataUnion: { + ethTxData1559: { + baseData: { + data: Array.from(new Uint8Array(24)), + gasLimit: '0xfde8', + gasPrice: '0x20000000000', + nonce: '0x1', + to: '0xcd3a3f8e0e4bdc174c9e2e63b4c22e15a7f7f92a', + value: '0xb1a2bc2ec90000' + }, + chainId: '', + maxFeePerGas: '', + maxPriorityFeePerGas: '', + gasEstimation: undefined + }, + ethTxData: undefined, + solanaTxData: undefined, + filTxData: undefined + }, + txHash: '0x55732e30af74a450cd438be2a02c765ea62cb4ec8dda5cb12ed8dc5d21ac15d3', + txStatus: 2, + txArgs: [], + txParams: [], + txType: 0, + createdTime: { microseconds: BigInt(0) }, + submittedTime: { microseconds: BigInt(0) }, + confirmedTime: { microseconds: BigInt(0) }, + originInfo: mockOriginInfo + }, + { + fromAddress: '0x7d66c9ddAED3115d93Bd1790332f3Cd06Cf52B14', + id: '13cf4882-d3c0-44cd-a8c2-aca1fcf85c4a', + txDataUnion: { + ethTxData1559: { + baseData: { + data: Array.from(new Uint8Array(24)), + gasLimit: '0xfde8', + gasPrice: '0x20000000000', + nonce: '0x1', + to: '0xcd3a3f8e0e4bdc174c9e2e63b4c22e15a7f7f92a', + value: '0xb1a2bc2ec90000' + }, + chainId: '', + maxFeePerGas: '', + maxPriorityFeePerGas: '', + gasEstimation: undefined + }, + ethTxData: undefined, + solanaTxData: undefined, + filTxData: undefined + }, + txHash: '0x55732e30af74a450cd438be2a02c765ea62cb4ec8dda5cb12ed8dc5d21ac15d3', + txStatus: 1, + txArgs: [], + txParams: [], + txType: 0, + createdTime: { microseconds: BigInt(0) }, + submittedTime: { microseconds: BigInt(0) }, + confirmedTime: { microseconds: BigInt(0) }, + originInfo: mockOriginInfo + } + ], + [mockUserAccounts[1].id]: [ + { + fromAddress: '0x73A29A1da97149722eB09c526E4eAd698895bDCf', + id: '13cf4882-d3c0-44cd-a8c2-aca1fcf85c4a', + txDataUnion: { + ethTxData1559: { + baseData: { + data: Array.from(new Uint8Array(24)), + gasLimit: '0xfde8', + gasPrice: '0x20000000000', + nonce: '0x1', + to: '0xcd3a3f8e0e4bdc174c9e2e63b4c22e15a7f7f92a', + value: '0xb1a2bc2ec90000' + }, + chainId: '', + maxFeePerGas: '', + maxPriorityFeePerGas: '', + gasEstimation: undefined + }, + ethTxData: undefined, + solanaTxData: undefined, + filTxData: undefined + }, + txHash: '0x55732e30af74a450cd438be2a02c765ea62cb4ec8dda5cb12ed8dc5d21ac15d3', + txStatus: 0, + txArgs: [], + txParams: [], + txType: 0, + createdTime: { microseconds: BigInt(0) }, + submittedTime: { microseconds: BigInt(0) }, + confirmedTime: { microseconds: BigInt(0) }, + originInfo: mockOriginInfo + }, + { + fromAddress: '0x73A29A1da97149722eB09c526E4eAd698895bDCf', + id: '13cf4882-d3c0-44cd-a8c2-aca1fcf85c4a', + txDataUnion: { + ethTxData1559: { + baseData: { + data: Array.from(new Uint8Array(24)), + gasLimit: '0xfde8', + gasPrice: '0x20000000000', + nonce: '0x1', + to: '0xcd3a3f8e0e4bdc174c9e2e63b4c22e15a7f7f92a', + value: '0xb1a2bc2ec90000' + }, + chainId: '', + maxFeePerGas: '', + maxPriorityFeePerGas: '', + gasEstimation: undefined + }, + ethTxData: undefined, + solanaTxData: undefined, + filTxData: undefined + }, + txHash: '0x55732e30af74a450cd438be2a02c765ea62cb4ec8dda5cb12ed8dc5d21ac15d3', + txStatus: 5, + txArgs: [], + txParams: [], + txType: 0, + createdTime: { microseconds: BigInt(0) }, + submittedTime: { microseconds: BigInt(0) }, + confirmedTime: { microseconds: BigInt(0) }, + originInfo: mockOriginInfo + } + ] +} + const originInfo = mockOriginInfo const store = createStore(combineReducers({ diff --git a/components/brave_wallet_ui/stories/wrappers/wallet-page-story-wrapper.tsx b/components/brave_wallet_ui/stories/wrappers/wallet-page-story-wrapper.tsx index 1c34cd4c3145..d0031b314058 100644 --- a/components/brave_wallet_ui/stories/wrappers/wallet-page-story-wrapper.tsx +++ b/components/brave_wallet_ui/stories/wrappers/wallet-page-story-wrapper.tsx @@ -8,6 +8,9 @@ import { createSendCryptoReducer } from '../../common/reducers/send_crypto_reduc import { createWalletReducer } from '../../common/reducers/wallet_reducer' import { createPageReducer } from '../../page/reducers/page_reducer' +// actions +import { WalletActions } from '../../common/actions' + // types import { PageState, WalletState } from '../../constants/types' @@ -31,17 +34,23 @@ export const WalletPageStory: React.FC { // redux - const store = createStore(combineReducers({ - wallet: createWalletReducer({ - ...mockWalletState, - ...(walletStateOverride || {}) - }), - page: createPageReducer({ - ...mockPageState, - ...(pageStateOverride || {}) - }), - sendCrypto: createSendCryptoReducer(mockSendCryptoState) - })) + const store = React.useMemo(() => { + return createStore(combineReducers({ + wallet: createWalletReducer({ + ...mockWalletState, + ...(walletStateOverride || {}) + }), + page: createPageReducer({ + ...mockPageState, + ...(pageStateOverride || {}) + }), + sendCrypto: createSendCryptoReducer(mockSendCryptoState) + })) + }, [walletStateOverride, pageStateOverride]) + + React.useEffect(() => { + store && store.dispatch(WalletActions.initialize()) + }, [store]) // render return ( From 3342460d0bc650f4bbd74538f4a0aacfce4e8d52 Mon Sep 17 00:00:00 2001 From: Josh Leonard Date: Wed, 27 Apr 2022 16:02:08 -0600 Subject: [PATCH 4/6] fix(wallet): close add hardware modal after import --- .../add-hardware-account-modal.tsx | 22 ++++++++++++++----- .../create-account-modal.tsx | 2 +- .../hardware-wallet-connect/index.tsx | 8 +++---- .../desktop/views/accounts/account.tsx | 1 + .../desktop/views/accounts/accounts.tsx | 1 - .../views/portfolio/portfolio-asset.tsx | 2 +- 6 files changed, 23 insertions(+), 13 deletions(-) diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-hardware-account-modal.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-hardware-account-modal.tsx index 879e2e2ec34a..e90558943bab 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-hardware-account-modal.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-hardware-account-modal.tsx @@ -22,13 +22,13 @@ import { WalletPageActions } from '../../../../page/actions' // components import { DividerLine } from '../../../extension' import PopupModal from '..' +import { HardwareWalletConnect } from './hardware-wallet-connect' +import { SelectAccountType } from './select-account-type/select-account-type' // style import { StyledWrapper } from './style' -import HardwareWalletConnect from './hardware-wallet-connect' -import { SelectAccountType } from './select-account-type/select-account-type' interface Params { accountTypeName: string @@ -61,7 +61,7 @@ export const AddHardwareAccountModal = () => { dispatch(WalletPageActions.setImportAccountError(hasError)) }, []) - const onClickClose = React.useCallback(() => { + const closeModal = React.useCallback(() => { setImportError(false) history.push(WalletRoutes.Accounts) }, []) @@ -72,17 +72,27 @@ export const AddHardwareAccountModal = () => { // render return ( - + + {selectedAccountType && - + } {!selectedAccountType && - + } ) diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/create-account-modal.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/create-account-modal.tsx index bdd37b8b8267..a6f36583ae85 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/create-account-modal.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/create-account-modal.tsx @@ -23,13 +23,13 @@ import { WalletActions } from '../../../../common/actions' // components import { DividerLine, NavButton } from '../../../../components/extension' import PopupModal from '..' +import { SelectAccountType } from './select-account-type' // style import { Input, StyledWrapper } from './style' -import { SelectAccountType } from './select-account-type' interface Params { accountTypeName: string diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/hardware-wallet-connect/index.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/hardware-wallet-connect/index.tsx index 0b0b93867831..ddf13199ec2c 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/hardware-wallet-connect/index.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/hardware-wallet-connect/index.tsx @@ -42,6 +42,7 @@ import { useLib } from '../../../../../common/hooks' export interface Props { selectedAccountType: CreateAccountOptionsType + onSuccess: () => void } const getErrorMessage = (error: any, accountTypeName: string) => { @@ -60,9 +61,7 @@ const getErrorMessage = (error: any, accountTypeName: string) => { return { error: error.message, userHint: '' } } -export const HardwareWalletConnect = (props: Props) => { - const { selectedAccountType } = props - +export const HardwareWalletConnect = ({ onSuccess, selectedAccountType }: Props) => { // lib const { onConnectHardwareWallet } = useLib() @@ -104,7 +103,8 @@ export const HardwareWalletConnect = (props: Props) => { const onAddHardwareAccounts = React.useCallback((selected: BraveWallet.HardwareWalletAccount[]) => { dispatch(WalletPageActions.addHardwareAccounts(selected)) - }, []) + onSuccess() + }, [onSuccess]) const onChangeDerivationScheme = React.useCallback((scheme: HardwareDerivationScheme) => { setSelectedDerivationScheme(scheme) diff --git a/components/brave_wallet_ui/components/desktop/views/accounts/account.tsx b/components/brave_wallet_ui/components/desktop/views/accounts/account.tsx index e75af52d961e..1965a90c0981 100644 --- a/components/brave_wallet_ui/components/desktop/views/accounts/account.tsx +++ b/components/brave_wallet_ui/components/desktop/views/accounts/account.tsx @@ -74,6 +74,7 @@ export const Account = (props: Props) => { onUpdateAccountName } = props + // routing const { id: accountId } = useParams<{ id: string }>() // redux diff --git a/components/brave_wallet_ui/components/desktop/views/accounts/accounts.tsx b/components/brave_wallet_ui/components/desktop/views/accounts/accounts.tsx index c98c642ffaf8..d157b951860c 100644 --- a/components/brave_wallet_ui/components/desktop/views/accounts/accounts.tsx +++ b/components/brave_wallet_ui/components/desktop/views/accounts/accounts.tsx @@ -65,7 +65,6 @@ export const Accounts = () => { case 'import': return history.push(WalletRoutes.ImportAccountModalStart) default: return history.push(WalletRoutes.AddAccountModal) } - // dispatch(WalletPageActions.setShowAddModal(true)) }, []) const onRemoveAccount = React.useCallback((address: string, hardware: boolean, coin: BraveWallet.CoinType) => { diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx index a36deab58e62..1b28282c8b86 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx @@ -291,7 +291,7 @@ export const PortfolioAsset = () => { setHideBalances(prevHideBalances => !prevHideBalances) }, []) - // effect + // effects React.useEffect(() => { setfilteredAssetList(userAssetList) }, [userAssetList]) From 41e5ed10e38c79c8977b6de0b9eab560ac8b80ab Mon Sep 17 00:00:00 2001 From: Josh Leonard Date: Thu, 28 Apr 2022 17:56:04 -0600 Subject: [PATCH 5/6] fix: redirect unknown unlocked routes to Portfolio --- .../popup-modals/add-account-modal/add-account-modal.tsx | 6 +++--- .../components/desktop/views/crypto/index.tsx | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-account-modal.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-account-modal.tsx index 620ca6fea8f0..2c7f8a0b99ca 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-account-modal.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/add-account-modal/add-account-modal.tsx @@ -18,15 +18,15 @@ export const AddAccountModal = () => { return ( - + - + - + diff --git a/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx b/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx index 38980e819022..1741109313b3 100644 --- a/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx +++ b/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx @@ -4,7 +4,7 @@ // you can obtain one at http://mozilla.org/MPL/2.0/. import * as React from 'react' -import { Route, useHistory, useParams, Switch } from 'react-router-dom' +import { Route, useHistory, useParams, Switch, Redirect } from 'react-router-dom' // utils import { getLocale } from '../../../../../common/locale' @@ -213,6 +213,8 @@ const CryptoView = (props: Props) => { + + {/* modals */} From 99d445a17e6ec927bbac8536944a17348e589aa0 Mon Sep 17 00:00:00 2001 From: Josh Leonard Date: Mon, 16 May 2022 21:42:47 -0600 Subject: [PATCH 6/6] fix: only allow restore route access when unlocked --- .../brave_wallet_ui/common/async/handlers.ts | 35 ------------------- .../views/portfolio/portfolio-asset.tsx | 8 ++--- components/brave_wallet_ui/page/container.tsx | 9 +++-- 3 files changed, 11 insertions(+), 41 deletions(-) diff --git a/components/brave_wallet_ui/common/async/handlers.ts b/components/brave_wallet_ui/common/async/handlers.ts index 7c2943f97ed2..d38a8fdc5b4b 100644 --- a/components/brave_wallet_ui/common/async/handlers.ts +++ b/components/brave_wallet_ui/common/async/handlers.ts @@ -232,41 +232,6 @@ handler.on(WalletActions.removeFavoriteApp.getType(), async (store: Store, appIt await refreshWalletInfo(store) }) -handler.on(WalletActions.chainChangedEvent.getType(), async (store: Store, payload: ChainChangedEventPayloadType) => { - const keyringService = getAPIProxy().keyringService - const state = getWalletState(store) - const { accounts } = state - const selectedAccountAddress = await keyringService.getSelectedAccount(payload.coin) - const selectedAccount = accounts.find((account) => account.address === selectedAccountAddress.address) ?? accounts[0] - store.dispatch(WalletActions.setSelectedAccount(selectedAccount)) - store.dispatch(WalletActions.setSelectedCoin(payload.coin)) -}) - -handler.on(WalletActions.selectNetwork.getType(), async (store: Store, payload: BraveWallet.NetworkInfo) => { - if (payload) { - const jsonRpcService = getAPIProxy().jsonRpcService - await jsonRpcService.setNetwork(payload.chainId, payload.coin) - store.dispatch(WalletActions.setNetwork(payload)) - } -}) - -handler.on(WalletActions.selectAccount.getType(), async (store: Store, payload: WalletAccountType) => { - const { keyringService } = getAPIProxy() - const state = getWalletState(store) - const { defaultNetworks } = state - - if (!(defaultNetworks.length > 0)) { - return - } - - const defaultCoinTypesNetwork = defaultNetworks.find((network) => network.coin === payload.coin) ?? defaultNetworks[0] - await keyringService.setSelectedAccount(payload.address, payload.coin) - store.dispatch(WalletActions.setNetwork(defaultCoinTypesNetwork)) - store.dispatch(WalletActions.setSelectedAccount(payload)) - store.dispatch(WalletActions.setSelectedCoin(payload.coin)) - await store.dispatch(refreshTransactionHistory(payload.address)) -}) - handler.on(WalletActions.initialized.getType(), async (store: Store, payload: WalletInfo) => { const keyringService = getAPIProxy().keyringService const state = getWalletState(store) diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx index 1b28282c8b86..55e170f00d4d 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx @@ -272,9 +272,6 @@ export const PortfolioAsset = () => { }, [selectedAsset]) const goBack = React.useCallback(() => { - // if (nftMetadata) { - // dispatch(WalletPageActions.updateNFTMetadata(undefined)) - // } dispatch(WalletPageActions.selectAsset({ asset: undefined, timeFrame: selectedTimeline })) history.push(WalletRoutes.Portfolio) setfilteredAssetList(userAssetList) @@ -337,6 +334,7 @@ export const PortfolioAsset = () => { {!isNftAsset && + @@ -344,7 +342,7 @@ export const PortfolioAsset = () => { {selectedAssetFromParams.symbol} on {selectedAssetsNetwork?.chainName ?? ''} - {/* {selectedAsset.name} {getLocale('braveWalletPrice')} ({selectedAsset.symbol}) */} + {CurrencySymbols[defaultCurrencies.fiat]}{hoverPrice || (selectedAssetFiatPrice ? new Amount(selectedAssetFiatPrice.price).formatAsFiat() : 0.00)} @@ -352,6 +350,7 @@ export const PortfolioAsset = () => { {selectedAssetFiatPrice ? Number(selectedAssetFiatPrice.assetTimeframeChange).toFixed(2) : 0.00}% + { selectedAssetCryptoPrice @@ -360,6 +359,7 @@ export const PortfolioAsset = () => { : '' } + } diff --git a/components/brave_wallet_ui/page/container.tsx b/components/brave_wallet_ui/page/container.tsx index 269c54e81abd..ffef21af03f9 100644 --- a/components/brave_wallet_ui/page/container.tsx +++ b/components/brave_wallet_ui/page/container.tsx @@ -205,11 +205,16 @@ export const Container = () => { - + - +