1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 09:23:21 +01:00

UX Multichain: Added balance-overview component (#20528)

* added balance-overview component

* updated balance overview component to use Currency utility props

* added MULTICHAIN feature flag

* lint fix

* lint fix

* lint fix

* updated ternary operators
This commit is contained in:
Nidhi Kumari 2023-08-29 22:07:23 +05:30 committed by GitHub
parent 6254fbb98d
commit b8b94c2c1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 525 additions and 17 deletions

View File

@ -24,6 +24,7 @@ import {
DetectedTokensBanner,
TokenListItem,
ImportTokenLink,
BalanceOverview,
} from '../../multichain';
const AssetList = ({ onClickAsset }) => {
@ -67,6 +68,7 @@ const AssetList = ({ onClickAsset }) => {
return (
<>
{process.env.MULTICHAIN ? <BalanceOverview /> : null}
<TokenListItem
onClick={() => onClickAsset(nativeCurrency)}
title={nativeCurrency}

View File

@ -72,4 +72,8 @@ UserPreferencedCurrencyDisplay.propTypes = {
]),
showFiat: PropTypes.bool,
showCurrencySuffix: PropTypes.bool,
/**
* UserPreferencedCurrencyDisplay component should also accept all the props from Currency component
*/
...CurrencyDisplay.propTypes,
};

View File

@ -0,0 +1,294 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Balance Overview and Portfolio for Tokens should match snapshot 1`] = `
<div>
<div
class="mm-box token-balance-overview mm-box--padding-4 mm-box--display-flex mm-box--justify-content-space-between mm-box--align-items-center"
>
<div
class="mm-box token-balance-overview__balance"
>
<div
class="mm-box token-balance-overview__primary-container"
>
<div
class="spinner loading-overlay__spinner"
>
<svg
class="lds-spinner"
height="100%"
preserveAspectRatio="xMidYMid"
style="background: none;"
viewBox="0 0 100 100"
width="100%"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<g
transform="rotate(0 50 50)"
>
<rect
fill="var(--color-secondary-default)"
height="30"
rx="0"
ry="0"
width="10"
x="45"
y="0"
>
<animate
attributeName="opacity"
begin="-0.9166666666666666s"
dur="1s"
repeatCount="indefinite"
values="1;0"
/>
</rect>
</g>
<g
transform="rotate(30 50 50)"
>
<rect
fill="var(--color-secondary-default)"
height="30"
rx="0"
ry="0"
width="10"
x="45"
y="0"
>
<animate
attributeName="opacity"
begin="-0.8333333333333334s"
dur="1s"
repeatCount="indefinite"
values="1;0"
/>
</rect>
</g>
<g
transform="rotate(60 50 50)"
>
<rect
fill="var(--color-secondary-default)"
height="30"
rx="0"
ry="0"
width="10"
x="45"
y="0"
>
<animate
attributeName="opacity"
begin="-0.75s"
dur="1s"
repeatCount="indefinite"
values="1;0"
/>
</rect>
</g>
<g
transform="rotate(90 50 50)"
>
<rect
fill="var(--color-secondary-default)"
height="30"
rx="0"
ry="0"
width="10"
x="45"
y="0"
>
<animate
attributeName="opacity"
begin="-0.6666666666666666s"
dur="1s"
repeatCount="indefinite"
values="1;0"
/>
</rect>
</g>
<g
transform="rotate(120 50 50)"
>
<rect
fill="var(--color-secondary-default)"
height="30"
rx="0"
ry="0"
width="10"
x="45"
y="0"
>
<animate
attributeName="opacity"
begin="-0.5833333333333334s"
dur="1s"
repeatCount="indefinite"
values="1;0"
/>
</rect>
</g>
<g
transform="rotate(150 50 50)"
>
<rect
fill="var(--color-secondary-default)"
height="30"
rx="0"
ry="0"
width="10"
x="45"
y="0"
>
<animate
attributeName="opacity"
begin="-0.5s"
dur="1s"
repeatCount="indefinite"
values="1;0"
/>
</rect>
</g>
<g
transform="rotate(180 50 50)"
>
<rect
fill="var(--color-secondary-default)"
height="30"
rx="0"
ry="0"
width="10"
x="45"
y="0"
>
<animate
attributeName="opacity"
begin="-0.4166666666666667s"
dur="1s"
repeatCount="indefinite"
values="1;0"
/>
</rect>
</g>
<g
transform="rotate(210 50 50)"
>
<rect
fill="var(--color-secondary-default)"
height="30"
rx="0"
ry="0"
width="10"
x="45"
y="0"
>
<animate
attributeName="opacity"
begin="-0.3333333333333333s"
dur="1s"
repeatCount="indefinite"
values="1;0"
/>
</rect>
</g>
<g
transform="rotate(240 50 50)"
>
<rect
fill="var(--color-secondary-default)"
height="30"
rx="0"
ry="0"
width="10"
x="45"
y="0"
>
<animate
attributeName="opacity"
begin="-0.25s"
dur="1s"
repeatCount="indefinite"
values="1;0"
/>
</rect>
</g>
<g
transform="rotate(270 50 50)"
>
<rect
fill="var(--color-secondary-default)"
height="30"
rx="0"
ry="0"
width="10"
x="45"
y="0"
>
<animate
attributeName="opacity"
begin="-0.16666666666666666s"
dur="1s"
repeatCount="indefinite"
values="1;0"
/>
</rect>
</g>
<g
transform="rotate(300 50 50)"
>
<rect
fill="var(--color-secondary-default)"
height="30"
rx="0"
ry="0"
width="10"
x="45"
y="0"
>
<animate
attributeName="opacity"
begin="-0.08333333333333333s"
dur="1s"
repeatCount="indefinite"
values="1;0"
/>
</rect>
</g>
<g
transform="rotate(330 50 50)"
>
<rect
fill="var(--color-secondary-default)"
height="30"
rx="0"
ry="0"
width="10"
x="45"
y="0"
>
<animate
attributeName="opacity"
begin="0s"
dur="1s"
repeatCount="indefinite"
values="1;0"
/>
</rect>
</g>
</svg>
</div>
</div>
</div>
<button
class="mm-box mm-text mm-button-base mm-button-base--size-md token-balance-portfolio mm-button-secondary mm-text--body-md-medium mm-box--padding-0 mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent mm-box--rounded-pill mm-box--border-color-primary-default box--border-style-solid box--border-width-1"
>
Portfolio
<span
class="mm-box mm-icon mm-icon--size-sm mm-box--margin-inline-start-1 mm-box--display-inline-block mm-box--color-inherit"
style="mask-image: url('./images/icons/export.svg');"
/>
</button>
</div>
</div>
`;

View File

@ -0,0 +1,171 @@
import React, { useContext } from 'react';
import { useSelector } from 'react-redux';
import classnames from 'classnames';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import {
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../../../shared/constants/metametrics';
import { Box, ButtonSecondary, IconName } from '../../component-library';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
import {
getMmiPortfolioEnabled,
getMmiPortfolioUrl,
} from '../../../selectors/institutional/selectors';
///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask)
import { getPortfolioUrl } from '../../../helpers/utils/portfolio';
///: END:ONLY_INCLUDE_IN
import {
///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask)
getCurrentChainId,
getMetaMetricsId,
///: END:ONLY_INCLUDE_IN
getSelectedAccountCachedBalance,
isBalanceCached,
} from '../../../selectors';
import Spinner from '../../ui/spinner';
import UserPreferencedCurrencyDisplay from '../../app/user-preferenced-currency-display';
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
import {
AlignItems,
Display,
JustifyContent,
TextColor,
TextVariant,
} from '../../../helpers/constants/design-system';
export const BalanceOverview = () => {
const trackEvent = useContext(MetaMetricsContext);
const t = useI18nContext();
///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask)
const metaMetricsId = useSelector(getMetaMetricsId);
const chainId = useSelector(getCurrentChainId);
///: END:ONLY_INCLUDE_IN
const balanceIsCached = useSelector(isBalanceCached);
const balance = useSelector(getSelectedAccountCachedBalance);
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
const mmiPortfolioEnabled = useSelector(getMmiPortfolioEnabled);
const mmiPortfolioUrl = useSelector(getMmiPortfolioUrl);
const portfolioEvent = () => {
trackEvent({
category: MetaMetricsEventCategory.Navigation,
event: MetaMetricsEventName.MMIPortfolioButtonClicked,
});
};
const renderInstitutionalButtons = () => {
return mmiPortfolioEnabled ? (
<ButtonSecondary
className="token-balance-mmi-portfolio"
onClick={() => {
portfolioEvent();
window.open(mmiPortfolioUrl, '_blank');
}}
endIconName={IconName.Export}
>
{t('portfolio')}
</ButtonSecondary>
) : null;
};
///: END:ONLY_INCLUDE_IN
return (
<Box
className="token-balance-overview"
display={Display.Flex}
justifyContent={JustifyContent.spaceBetween}
alignItems={AlignItems.center}
padding={4}
>
<Box className="token-balance-overview__balance">
{balance ? (
<UserPreferencedCurrencyDisplay
className={classnames({
'token-balance-overview__cached-secondary-balance':
balanceIsCached,
'token-balance-overview__secondary-balance': !balanceIsCached,
})}
data-testid="token-balance-overview__secondary-currency"
value={balance}
type={PRIMARY}
ethNumberOfDecimals={4}
textProps={{
variant: TextVariant.headingLg,
color: TextColor.textDefault,
}}
suffixProps={{
variant: TextVariant.headingLg,
color: TextColor.textDefault,
}}
/>
) : null}
<Box className="token-balance-overview__primary-container">
{balance ? (
<UserPreferencedCurrencyDisplay
className={classnames('token-balance-overview__primary-balance', {
'token-balance-overview__cached-balance': balanceIsCached,
})}
data-testid="token-balance-overview__primary-currency"
value={balance}
type={SECONDARY}
ethNumberOfDecimals={4}
hideTitle
textProps={{
variant: TextVariant.bodyMd,
color: TextColor.textAlternative,
}}
suffixProps={{
variant: TextVariant.bodyMd,
color: TextColor.textAlternative,
}}
/>
) : (
<Spinner
color="var(--color-secondary-default)"
className="loading-overlay__spinner"
/>
)}
{balanceIsCached ? (
<span className="token-balance-overview__cached-star">*</span>
) : null}
</Box>
</Box>
{
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
renderInstitutionalButtons()
///: END:ONLY_INCLUDE_IN
}
{
///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask)
<ButtonSecondary
className="token-balance-portfolio"
endIconName={IconName.Export}
onClick={() => {
const url = getPortfolioUrl(
'',
'ext_portfolio_button',
metaMetricsId,
);
global.platform.openTab({ url });
trackEvent({
category: MetaMetricsEventCategory.Navigation,
event: MetaMetricsEventName.PortfolioLinkClicked,
properties: {
location: 'Home',
text: 'Portfolio',
chain_id: chainId,
token_symbol: 'ETH',
},
});
}}
>
{t('portfolio')}
</ButtonSecondary>
///: END:ONLY_INCLUDE_IN
}
</Box>
);
};

View File

@ -0,0 +1,11 @@
import React from 'react';
import { BalanceOverview } from '.';
export default {
title: 'Components/Multichain/BalanceOverview',
component: BalanceOverview,
};
export const DefaultStory = () => <BalanceOverview />;
DefaultStory.storyName = 'Default';

View File

@ -0,0 +1,21 @@
import React from 'react';
import configureStore from '../../../store/store';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import mockState from '../../../../test/data/mock-state.json';
import { BalanceOverview } from '.';
const render = () => {
const store = configureStore({
metamask: {
...mockState.metamask,
},
});
return renderWithProvider(<BalanceOverview />, store);
};
describe('Balance Overview and Portfolio for Tokens', () => {
it('should match snapshot', () => {
const { container } = render();
expect(container).toMatchSnapshot();
});
});

View File

@ -0,0 +1 @@
export { BalanceOverview } from './balance-overview';

View File

@ -5,6 +5,7 @@ export { AccountPicker } from './account-picker';
export { ActivityListItem } from './activity-list-item';
export { AppHeader } from './app-header';
export { AppFooter } from './app-footer';
export { BalanceOverview } from './balance-overview';
export { DetectedTokensBanner } from './detected-token-banner';
export { GlobalMenu } from './global-menu';
export { ImportTokenLink } from './import-token-link';

View File

@ -781,6 +781,7 @@ export default class Home extends PureComponent {
} else if (this.state.notificationClosing || this.state.redirecting) {
return null;
}
const tabPadding = process.env.MULTICHAIN ? 4 : 0; // TODO: Remove tabPadding and add paddingTop={4} to parent container Box of Tabs
const showWhatsNew =
completedOnboarding &&
@ -848,23 +849,25 @@ export default class Home extends PureComponent {
///: END:ONLY_INCLUDE_IN
}
<div className="home__main-view">
<div className="home__balance-wrapper">
{
///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask)
<EthOverview showAddress />
///: END:ONLY_INCLUDE_IN
}
{
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
<EthOverview
showAddress
mmiPortfolioEnabled={mmiPortfolioEnabled}
mmiPortfolioUrl={mmiPortfolioUrl}
/>
///: END:ONLY_INCLUDE_IN
}
</div>
<Box style={{ flexGrow: '1' }}>
{process.env.MULTICHAIN ? null : (
<div className="home__balance-wrapper">
{
///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask)
<EthOverview showAddress />
///: END:ONLY_INCLUDE_IN
}
{
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
<EthOverview
showAddress
mmiPortfolioEnabled={mmiPortfolioEnabled}
mmiPortfolioUrl={mmiPortfolioUrl}
/>
///: END:ONLY_INCLUDE_IN
}
</div>
)}
<Box style={{ flexGrow: '1' }} paddingTop={tabPadding}>
<Tabs
t={this.context.t}
defaultActiveTabKey={defaultHomeActiveTabName}