Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wallet): Recently Interacted Address History #27116

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions components/brave_wallet/browser/brave_wallet_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ inline constexpr webui::LocalizedString kLocalizedStrings[] = {
IDS_BRAVE_WALLET_USER_UNDERSTANDS_LABEL},
{"braveWalletChooseRecipient", IDS_BRAVE_WALLET_CHOOSE_RECIPIENT},
{"braveWalletMyAddresses", IDS_BRAVE_WALLET_MY_ADDRESSES},
{"braveWalletRecentAddresses", IDS_BRAVE_WALLET_RECENT_ADDRESSES},
{"braveWalletAddressOrDomainPlaceholder",
IDS_BRAVE_WALLET_ADDRESS_OR_DOMAIN_PLACEHOLDER},
{"braveWalletSendTo", IDS_BRAVE_WALLET_SEND_TO},
Expand Down Expand Up @@ -1496,6 +1497,12 @@ inline constexpr webui::LocalizedString kLocalizedStrings[] = {
{"braveWalletFromToken", IDS_BRAVE_WALLET_FROM_TOKEN},
{"braveWalletToToken", IDS_BRAVE_WALLET_TO_TOKEN},
{"braveWalletNewQuoteIn", IDS_BRAVE_WALLET_NEW_QUOTE_IN},
{"braveWalletAddressHistoryEnabledDisclaimer",
IDS_BRAVE_WALLET_ADDRESS_HISTORY_ENABLED_DISCLAIMER},
{"braveWalletAddressHistoryDisabledDisclaimer",
IDS_BRAVE_WALLET_ADDRESS_HISTORY_DISABLED_DISCLAIMER},
{"braveWalletDisableAddressHistory",
IDS_BRAVE_WALLET_DISABLE_ADDRESS_HISTORY},
{"braveSwapBalance", IDS_BRAVE_SWAP_BALANCE},
{"braveSwapSelectAToken", IDS_BRAVE_SWAP_SELECT_A_TOKEN},
{"braveSwapShowTokensWithZeroBalances",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ export const LOCAL_STORAGE_KEYS = {
NFT_COLLECTION_NAMES_REGISTRY: 'NFT_COLLECTION_NAMES_REGISTRY2',
FILTERED_OUT_DAPP_NETWORK_KEYS: 'BRAVE_WALLET_FILTERED_OUT_DAPP_NETWORK_KEYS',
FILTERED_OUT_DAPP_CATEGORIES: 'BRAVE_WALLET_FILTERED_OUT_DAPP_CATEGORIES',
HAS_ACCEPTED_PARTNER_TERMS: 'BRAVE_WALLET_HAS_ACCEPTED_PARTNER_TERMS'
HAS_ACCEPTED_PARTNER_TERMS: 'BRAVE_WALLET_HAS_ACCEPTED_PARTNER_TERMS',
RECENTLY_INTERACTED_WITH_ADDRESS_HISTORY:
'BRAVE_WALLET_RECENTLY_INTERACTED_WITH_ADDRESS_HISTORY',
RECENTLY_INTERACTED_WITH_ADDRESS_HISTORY_IS_DISABLED:
'BRAVE_WALLET_RECENTLY_INTERACTED_WITH_ADDRESS_HISTORY_IS_DISABLED'
} as const

const LOCAL_STORAGE_KEYS_DEPRECATED = {
Expand Down
2 changes: 2 additions & 0 deletions components/brave_wallet_ui/constants/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1121,3 +1121,5 @@ export type zcashAddressOptionType = {
addressType: zcashAddressTypes
label: string
}

export type AddressHistory = Record<string, string[]>
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ export const WalletIcon = styled(Icon).attrs({
margin-right: 8px;
`

export const TrashIcon = styled(Icon).attrs({
name: 'trash'
})`
--leo-icon-size: 20px;
color: ${leo.color.icon.default};
`

export const DomainLoadIcon = styled(LoaderIcon)<{ position: number }>`
color: ${leo.color.icon.default};
height: 20px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,28 @@ import * as React from 'react'
import { skipToken } from '@reduxjs/toolkit/query/react'
import Icon from '@brave/leo/react/icon'
import ProgressRing from '@brave/leo/react/progressRing'
import Button from '@brave/leo/react/button'

// Constants
import {
LOCAL_STORAGE_KEYS //
} from '../../../../../common/constants/local-storage-keys'

// Types
import {
BraveWallet,
AddressMessageInfo,
AddressMessageInfoIds,
CoinTypesMap
CoinTypesMap,
AddressHistory
} from '../../../../../constants/types'

// Selectors
import {
useSafeUISelector //
} from '../../../../../common/hooks/use-safe-selector'
import { UISelectors } from '../../../../../common/selectors'

// Queries
import {
useAccountsQuery,
Expand All @@ -32,12 +45,20 @@ import {
useValidateUnifiedAddressQuery
} from '../../../../../common/slices/api.slice'

// Hooks
import {
useLocalStorage //
} from '../../../../../common/hooks/use_local_storage'

// Utils
import {
isValidBtcAddress,
isValidEVMAddress,
isValidFilAddress
} from '../../../../../utils/address-utils'
import {
getAddressHistoryIdentifier //
} from '../../../../../utils/local-storage-utils'

// Messages
import {
Expand Down Expand Up @@ -91,7 +112,8 @@ import {
WalletIcon,
AddressButtonText,
DomainLoadIcon,
SearchBoxContainer
SearchBoxContainer,
TrashIcon
} from './select_address_modal.style'

interface Props {
Expand All @@ -116,6 +138,20 @@ export const SelectAddressModal = React.forwardRef<HTMLDivElement, Props>(
setResolvedDomainAddress
} = props

// UI Selectors (safe)
const isPanel = useSafeUISelector(UISelectors.isPanel)

// Local Storage
const [addressHistory, setAddressHistory] = useLocalStorage<AddressHistory>(
LOCAL_STORAGE_KEYS.RECENTLY_INTERACTED_WITH_ADDRESS_HISTORY,
{}
)
const [isAddressHistoryDisabled, setIsAddressHistoryDisabled] =
useLocalStorage<boolean>(
LOCAL_STORAGE_KEYS.RECENTLY_INTERACTED_WITH_ADDRESS_HISTORY_IS_DISABLED,
false
)

// Mutations
const [enableEnsOffchainLookup] = useEnableEnsOffchainLookupMutation()
const [generateReceiveAddress, { isLoading: isGeneratingAddress }] =
Expand Down Expand Up @@ -212,6 +248,22 @@ export const SelectAddressModal = React.forwardRef<HTMLDivElement, Props>(
)
}, [accountsByNetwork, searchValue])

const addressHistoryIdentifier =
getAddressHistoryIdentifier(selectedNetwork)

const addressHistoryByNetworkOrCoin = React.useMemo(() => {
if (!addressHistoryIdentifier) {
return []
}
return addressHistory[addressHistoryIdentifier] ?? []
}, [addressHistoryIdentifier, addressHistory])

const filteredAddressHistory = React.useMemo(() => {
return addressHistoryByNetworkOrCoin.filter((address) =>
address.toLocaleLowerCase().startsWith(searchValue.toLocaleLowerCase())
)
}, [addressHistoryByNetworkOrCoin, searchValue])

const evmAddressesforFVMTranslation = React.useMemo(
() =>
accountsByNetwork
Expand Down Expand Up @@ -323,7 +375,8 @@ export const SelectAddressModal = React.forwardRef<HTMLDivElement, Props>(
filteredAccounts.length !== 0) ||
(!isSearchingForDomain &&
addressMessageInformation &&
filteredAccounts.length === 0)
filteredAccounts.length === 0 &&
filteredAddressHistory.length === 0)

// Methods
const onSelectAccount = React.useCallback(
Expand Down Expand Up @@ -369,6 +422,35 @@ export const SelectAddressModal = React.forwardRef<HTMLDivElement, Props>(
dismissOffchainEnsWarning(true)
}, [enableEnsOffchainLookup])

const onClickDeleteAddress = React.useCallback(
(address: string) => {
if (addressHistoryIdentifier) {
const newAddressHistoryForNetwork =
addressHistoryByNetworkOrCoin.filter(
(addr: string) => addr !== address
)
let history = addressHistory
history[addressHistoryIdentifier] = newAddressHistoryForNetwork
setAddressHistory(history)
}
},
[
addressHistory,
addressHistoryByNetworkOrCoin,
addressHistoryIdentifier,
setAddressHistory
]
)

const onClickDisableAddressHistory = React.useCallback(() => {
setAddressHistory({})
setIsAddressHistoryDisabled(true)
}, [setAddressHistory, setIsAddressHistoryDisabled])

const onClickEnableAddressHistory = React.useCallback(() => {
setIsAddressHistoryDisabled(false)
}, [setIsAddressHistoryDisabled])

if (showChecksumInfo) {
return (
<PopupModal
Expand Down Expand Up @@ -439,13 +521,62 @@ export const SelectAddressModal = React.forwardRef<HTMLDivElement, Props>(
fullWidth={true}
justifyContent='flex-start'
>
{!isAddressHistoryDisabled &&
filteredAddressHistory.length !== 0 && (
<>
<Row
width='100%'
justifyContent='flex-start'
padding='0px 8px'
margin='4px 0px'
>
<LabelText
textSize='12px'
isBold={true}
>
{getLocale('braveWalletRecentAddresses')}
</LabelText>
</Row>
{filteredAddressHistory.map((address) => (
<Row
key={address}
gap='12px'
>
<AddressButton onClick={() => onSelectAddress(address)}>
<WalletIcon />
<Column alignItems='flext-start'>
<AddressButtonText
textSize='14px'
isBold={true}
textColor='primary'
textAlign='left'
>
{address}
</AddressButtonText>
</Column>
</AddressButton>
<Button
kind='plain-faint'
size='small'
onClick={() => onClickDeleteAddress(address)}
>
<TrashIcon />
</Button>
</Row>
))}
</>
)}
{filteredAccounts.length !== 0 && (
<>
<Row
width='100%'
justifyContent='flex-start'
padding='0px 8px'
marginBottom={4}
margin={
filteredAddressHistory.length !== 0
? '8px 0px 4px 0px'
: '0px 0px 4px 0px'
}
>
<LabelText
textSize='12px'
Expand Down Expand Up @@ -482,6 +613,7 @@ export const SelectAddressModal = React.forwardRef<HTMLDivElement, Props>(
</Row>
)}
{filteredAccounts.length === 0 &&
filteredAddressHistory.length === 0 &&
!isSearchingForDomain &&
!showEnsOffchainWarning &&
addressMessageInformation?.type !== 'error' && (
Expand Down Expand Up @@ -533,6 +665,36 @@ export const SelectAddressModal = React.forwardRef<HTMLDivElement, Props>(
)}
</ScrollContainer>
)}
<Row
gap='8px'
padding={isPanel ? '16px' : '16px 16px 0px 16px'}
>
<Text
textSize='12px'
isBold={false}
textColor='tertiary'
textAlign='left'
>
{isAddressHistoryDisabled
? getLocale('braveWalletAddressHistoryDisabledDisclaimer')
: getLocale('braveWalletAddressHistoryEnabledDisclaimer')}
</Text>
<div>
<Button
kind='plain'
size='tiny'
onClick={
isAddressHistoryDisabled
? onClickEnableAddressHistory
: onClickDisableAddressHistory
}
>
{isAddressHistoryDisabled
? getLocale('braveWalletButtonEnable')
: getLocale('braveWalletDisableAddressHistory')}
</Button>
</div>
</Row>
</Wrapper>
</PopupModal>
)
Expand Down
Loading
Loading