Skip to content

Commit 9227b4c

Browse files
authored
feat: add search text box to tokens page (#986)
* feat: add search text box to tokens page * feat: add empty search component * refactor: simplify asset portfolio component * test: fix asset portfolio test * feat: add search by ticker
1 parent 0bd48b5 commit 9227b4c

File tree

10 files changed

+156
-26
lines changed

10 files changed

+156
-26
lines changed

apps/browser-extension-wallet/src/lib/translations/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,8 @@
449449
"browserView.assets.startYourWeb3Journey": "Start your Web3 journey. Just add some digital assets to get started.",
450450
"browserView.assets.totalWalletBalance": "Total wallet balance",
451451
"browserView.assets.portfolioBalanceToolTip": "Approximate value of all tokens with available fiat pair",
452+
"browserView.assets.emptySearch": "No results matching your search",
453+
"browserView.assets.searchPlaceholder": "Search by name, policy ID or fingerprint",
452454
"browserView.assetDetails.title": "Token detail",
453455
"browserView.assetDetails.price": "Price",
454456
"browserView.assetDetails.assetPrice": "Token price",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@import '../../../../../../../packages/common/src/ui/styles/theme.scss';
2+
3+
.container {
4+
align-items: center;
5+
display: flex;
6+
flex-direction: column;
7+
justify-content: center;
8+
margin: size_unit(5) 0;
9+
width: 100%;
10+
position: relative;
11+
}
12+
13+
.text {
14+
color: var(--text-color-secondary);
15+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React, { ReactElement } from 'react';
2+
import Empty from '@assets/icons/empty.svg';
3+
import { Image } from 'antd';
4+
import styles from './EmptySearch.module.scss';
5+
6+
export const EmptySearch = ({ text }: { text: string }): ReactElement => (
7+
<div className={styles.container}>
8+
<Image preview={false} src={Empty} />
9+
<div className={styles.text}>{text}</div>
10+
</div>
11+
);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { EmptySearch } from './EmptySearch';

apps/browser-extension-wallet/src/views/browser-view/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export * from './SideMenu';
88
export * from './WarningModal';
99
export * from './SocialNetworks';
1010
export * from './WalletUsedAddressesDrawer';
11+
export * from './EmptySearch';

apps/browser-extension-wallet/src/views/browser-view/features/assets/components/Assets.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ export const Assets = ({ topSection }: AssetsProps): React.ReactElement => {
281281
totalAssets={fullAssetList?.length ?? 0}
282282
/>
283283
);
284+
284285
const drawers = (
285286
<>
286287
<AssetActivityDetails
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
2+
import { SearchBox } from '@lace/ui';
3+
import { EmptySearch, FundWalletBanner } from '@views/browser/components';
4+
import { Skeleton } from 'antd';
5+
import { AssetTable, IRow } from '@lace/core';
6+
import { CONTENT_LAYOUT_ID } from '@components/Layout';
7+
import { LACE_APP_ID } from '@utils/constants';
8+
import { IAssetDetails } from '@views/browser/features/assets/types';
9+
import { useTranslation } from 'react-i18next';
10+
import { useWalletStore } from '@stores';
11+
import styles from './AssetsPortfolio.module.scss';
12+
13+
const MIN_ASSETS_COUNT_FOR_SEARCH = 10;
14+
15+
const searchTokens = (data: IAssetDetails[], searchValue: string) => {
16+
const fields = ['name', 'policyId', 'fingerprint', 'ticker'] as const;
17+
const lowerSearchValue = searchValue.toLowerCase();
18+
19+
return data.filter((item) =>
20+
fields.some((field) => field in item && item[field] && item[field].toLowerCase().includes(lowerSearchValue))
21+
);
22+
};
23+
24+
interface AssetPortfolioContentProps {
25+
assetList: IRow[];
26+
totalAssets: number;
27+
isPortfolioBalanceLoading: boolean;
28+
onRowClick: (id: string) => void;
29+
onTableScroll: () => void;
30+
isPopupView: boolean;
31+
}
32+
33+
export const AssetPortfolioContent = ({
34+
assetList,
35+
totalAssets,
36+
isPortfolioBalanceLoading,
37+
onRowClick,
38+
onTableScroll,
39+
isPopupView
40+
}: AssetPortfolioContentProps): ReactElement => {
41+
const { t } = useTranslation();
42+
const [searchValue, setSearchValue] = useState<string>('');
43+
const [currentAssets, setCurrentAssets] = useState<{
44+
data: IAssetDetails[];
45+
total: number;
46+
}>({
47+
data: assetList,
48+
total: totalAssets
49+
});
50+
const { walletInfo } = useWalletStore();
51+
52+
useEffect(() => {
53+
setCurrentAssets({
54+
data: searchValue ? currentAssets.data : assetList,
55+
total: searchValue ? currentAssets.total : totalAssets
56+
});
57+
}, [assetList, currentAssets.data, currentAssets.total, searchValue, totalAssets]);
58+
59+
const handleSearch = useCallback(
60+
(value: string) => {
61+
const filteredAssets = searchTokens(assetList, value);
62+
setSearchValue(value);
63+
setCurrentAssets({ data: filteredAssets, total: filteredAssets.length });
64+
},
65+
[assetList]
66+
);
67+
68+
return (
69+
<>
70+
{assetList?.length > MIN_ASSETS_COUNT_FOR_SEARCH && (
71+
<div className={styles.searchBoxContainer}>
72+
<SearchBox
73+
placeholder={t('browserView.assets.searchPlaceholder')}
74+
onChange={handleSearch}
75+
data-testid="assets-search-input"
76+
value={searchValue}
77+
onClear={() => setSearchValue('')}
78+
/>
79+
</div>
80+
)}
81+
{searchValue && currentAssets.total === 0 && <EmptySearch text={t('browserView.assets.emptySearch')} />}
82+
{!searchValue && currentAssets.total === 0 && (
83+
<FundWalletBanner
84+
title={t('browserView.assets.welcome')}
85+
subtitle={t('browserView.assets.startYourWeb3Journey')}
86+
prompt={t('browserView.fundWalletBanner.prompt')}
87+
walletAddress={walletInfo.addresses[0].address.toString()}
88+
/>
89+
)}
90+
{
91+
<Skeleton loading={isPortfolioBalanceLoading || !currentAssets.data}>
92+
{currentAssets.total > 0 && (
93+
<AssetTable
94+
rows={currentAssets.data}
95+
onRowClick={onRowClick}
96+
totalItems={currentAssets.total}
97+
scrollableTargetId={isPopupView ? CONTENT_LAYOUT_ID : LACE_APP_ID}
98+
onLoad={onTableScroll}
99+
popupView={isPopupView}
100+
/>
101+
)}
102+
</Skeleton>
103+
}
104+
</>
105+
);
106+
};

apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetsPortfolio/AssetsPortfolio.module.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,9 @@
1313
width: 1000px;
1414
}
1515
}
16+
17+
.searchBoxContainer {
18+
@media (max-width: $breakpoint-popup) {
19+
margin-top: size_unit(3);
20+
}
21+
}

apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetsPortfolio/AssetsPortfolio.tsx

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ import { Skeleton } from 'antd';
22
import dayjs from 'dayjs';
33
import React, { useMemo } from 'react';
44
import { useTranslation } from 'react-i18next';
5-
import { AssetTable, IRow, SendReceive } from '@lace/core';
6-
import { CONTENT_LAYOUT_ID } from '@components/Layout/ContentLayout';
5+
import { IRow, SendReceive } from '@lace/core';
76
import { SectionTitle } from '@components/Layout/SectionTitle';
8-
import { APP_MODE_POPUP, AppMode, LACE_APP_ID } from '@src/utils/constants';
7+
import { APP_MODE_POPUP, AppMode } from '@src/utils/constants';
98
import { compactNumberWithUnit } from '@src/utils/format-number';
10-
import { FundWalletBanner, PortfolioBalance } from '@src/views/browser-view/components';
9+
import { PortfolioBalance } from '@src/views/browser-view/components';
1110
import { useCurrencyStore } from '@providers/currency';
1211
import { useWalletStore } from '@src/stores';
1312
import { useFetchCoinPrice } from '@hooks/useFetchCoinPrice';
@@ -18,6 +17,7 @@ import { PostHogAction } from '@providers/AnalyticsProvider/analyticsTracker';
1817
import styles from './AssetsPortfolio.module.scss';
1918
import BigNumber from 'bignumber.js';
2019
import { SendFlowTriggerPoints } from '../../../send-transaction';
20+
import { AssetPortfolioContent } from './AssetPortfolioContent';
2121

2222
const MINUTES_UNTIL_WARNING_BANNER = 3;
2323

@@ -49,7 +49,6 @@ export const AssetsPortfolio = ({
4949
const analytics = useAnalyticsContext();
5050
const { t } = useTranslation();
5151
const {
52-
walletInfo,
5352
walletUI: { canManageBalancesVisibility, areBalancesVisible }
5453
} = useWalletStore();
5554
const { fiatCurrency } = useCurrencyStore();
@@ -105,7 +104,7 @@ export const AssetsPortfolio = ({
105104
isBalanceVisible={areBalancesVisible || portfolioBalanceAsBigNumber.eq(0)}
106105
/>
107106
</div>
108-
{isPopupView && totalAssets > 0 && (
107+
{isPopupView && portfolioBalanceAsBigNumber.gt(0) && (
109108
<SendReceive
110109
leftButtonOnClick={openSend}
111110
rightButtonOnClick={handleRedirectToReceive}
@@ -118,25 +117,14 @@ export const AssetsPortfolio = ({
118117
}}
119118
/>
120119
)}
121-
<Skeleton loading={isPortfolioBalanceLoading || !assetList}>
122-
{portfolioBalanceAsBigNumber.gt(0) ? (
123-
<AssetTable
124-
rows={assetList}
125-
onRowClick={onRowClick}
126-
totalItems={totalAssets}
127-
scrollableTargetId={isPopupView ? CONTENT_LAYOUT_ID : LACE_APP_ID}
128-
onLoad={onTableScroll}
129-
popupView={isPopupView}
130-
/>
131-
) : (
132-
<FundWalletBanner
133-
title={t('browserView.assets.welcome')}
134-
subtitle={t('browserView.assets.startYourWeb3Journey')}
135-
prompt={t('browserView.fundWalletBanner.prompt')}
136-
walletAddress={walletInfo.addresses[0].address.toString()}
137-
/>
138-
)}
139-
</Skeleton>
120+
<AssetPortfolioContent
121+
totalAssets={totalAssets}
122+
assetList={assetList}
123+
isPortfolioBalanceLoading={isPortfolioBalanceLoading}
124+
onRowClick={onRowClick}
125+
onTableScroll={onTableScroll}
126+
isPopupView={isPopupView}
127+
/>
140128
</Skeleton>
141129
);
142130
};

apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetsPortfolio/__tests__/AssetsPortfolio.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@ describe('AssetsPortfolio', () => {
148148
/>
149149
);
150150
expect(queryByTestId('asset-table')).not.toBeInTheDocument();
151-
expect(queryByTestId('fund-wallet-banner-mock')).not.toBeInTheDocument();
152151
expect(queryByTestId('section-title-counter')).toHaveTextContent('(0)');
153152
});
154153

0 commit comments

Comments
 (0)