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(receive): revamp receive page layout #3253

Merged
merged 14 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion packages/yoroi-extension/.flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ deprecated-utility=error

[options]
types_first=true
include_warnings=true
include_warnings=false
esproposal.decorators=ignore
module.ignore_non_literal_requires=true
module.name_mapper.extension='scss' -> '<PROJECT_ROOT>/flow/mappers/CSSModule.js.flow'
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
284 changes: 284 additions & 0 deletions packages/yoroi-extension/app/components/wallet/WalletReceiveRevamp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
// @flow
import type { Node } from 'react';
import type { AddressFilterKind, StandardAddress } from '../../types/AddressFilterTypes';
import type { Notification } from '../../types/notificationType';
import type { $npm$ReactIntl$IntlFormat } from 'react-intl';
import type { UnitOfAccountSettingType } from '../../types/unitOfAccountType';
import type { TokenEntry, TokenLookupKey } from '../../api/common/lib/MultiToken';
import type { TokenRow } from '../../api/ada/lib/storage/database/primitives/tables';
import { Component } from 'react';
import { observer } from 'mobx-react';
import { defineMessages, intlShape } from 'react-intl';
import classnames from 'classnames';
import { ReactComponent as VerifyIcon } from '../../assets/images/revamp/verify-icon.inline.svg';
import { ReactComponent as GenerateURIIcon } from '../../assets/images/revamp/generate-uri.inline.svg';
import styles from './WalletReceiveRevamp.scss';
import CopyableAddress from '../widgets/CopyableAddress';
import RawHash from '../widgets/hashWrappers/RawHash';
import ExplorableHashContainer from '../../containers/widgets/ExplorableHashContainer';
import { SelectedExplorer } from '../../domain/SelectedExplorer';
import { truncateAddressShort, splitAmount, truncateToken } from '../../utils/formatters';
import { ReactComponent as NoTransactionModernSvg } from '../../assets/images/transaction/no-transactions-yet.modern.inline.svg';
import { hiddenAmount } from '../../utils/strings';
import { getTokenName } from '../../stores/stateless/tokenHelpers';
import { CoreAddressTypes } from '../../api/ada/lib/storage/database/primitives/enums';
import { Box, Typography } from '@mui/material';

const messages = defineMessages({
generatedAddressesSectionTitle: {
id: 'wallet.receive.page.generatedAddressesSectionTitle',
defaultMessage: '!!!Generated addresses',
},
copyAddressLabel: {
id: 'wallet.receive.page.copyAddressLabel',
defaultMessage: '!!!Copy address',
},
verifyAddressLabel: {
id: 'wallet.receive.page.verifyAddressLabel',
defaultMessage: '!!!Verify address',
},
generateURLLabel: {
id: 'wallet.receive.page.generateURLLabel',
defaultMessage: '!!!Generate URL',
},
outputAmountUTXO: {
id: 'wallet.receive.page.outputAmountUTXO',
defaultMessage: '!!!Balance (UTXO sum)',
},
noResultsFoundLabel: {
id: 'wallet.receive.page.noResultsFoundLabel',
defaultMessage: '!!!No results found.',
},
notFoundAnyAddresses: {
id: 'wallet.receive.page.notFoundAnyAddresses',
defaultMessage: '!!!No wallet addresses have been used yet.',
},
label: {
id: 'wallet.receive.page.label',
defaultMessage: '!!!Label ',
},
});

type Props = {|
+hierarchy: {|
path: Array<string>,
filter: AddressFilterKind,
|},
+header: Node,
+selectedExplorer: SelectedExplorer,
+walletAddresses: $ReadOnlyArray<$ReadOnly<StandardAddress>>,
+onCopyAddressTooltip: (string, string) => void,
+notification: ?Notification,
+onVerifyAddress: ($ReadOnly<StandardAddress>) => Promise<void>,
+onGeneratePaymentURI: void | (string => void),
+shouldHideBalance: boolean,
+unitOfAccountSetting: UnitOfAccountSettingType,
+getTokenInfo: ($ReadOnly<Inexact<TokenLookupKey>>) => $ReadOnly<TokenRow>,
+addressBook: boolean,
|};

@observer
export default class WalletReceiveRevamp extends Component<Props> {
static contextTypes: {| intl: $npm$ReactIntl$IntlFormat |} = {
intl: intlShape.isRequired,
};

getAmount: TokenEntry => ?Node = tokenEntry => {
if (this.props.shouldHideBalance) {
return <span>{hiddenAmount}</span>;
}
const tokenInfo = this.props.getTokenInfo(tokenEntry);

const shiftedAmount = tokenEntry.amount.shiftedBy(-tokenInfo.Metadata.numberOfDecimals);

const [beforeDecimalRewards, afterDecimalRewards] = splitAmount(
shiftedAmount,
tokenInfo.Metadata.numberOfDecimals
);
// recall: can't be negative in this situation
const adjustedBefore = '+' + beforeDecimalRewards;

return (
<>
{adjustedBefore}
<span className={styles.afterDecimal}>{afterDecimalRewards}</span>{' '}
{truncateToken(getTokenName(tokenInfo))}
</>
);
};

getValueBlock: void => {|
header: ?Node,
body: ($ReadOnly<StandardAddress>) => ?Node,
|} = () => {
if (this.props.addressBook) {
return { header: undefined, body: () => undefined };
}
const { intl } = this.context;

const header = (
<Typography component="h2" variant="body2" color="grayscale.500">
{intl.formatMessage(messages.outputAmountUTXO)}
</Typography>
);
const body = address => (
<Typography variant="body1" color="grayscale.900">
{address.values != null ? (
<span>{this.getAmount(address.values.getDefaultEntry())}</span>
) : (
'-'
)}
</Typography>
);
return { header, body };
};

getHierarchy: void => Node = () => {
const hierarchy = this.props.hierarchy.path.join(' > ');

return (
<Box mb="24px">
<Typography variant="h5" fontWeight={500}>
{hierarchy}
</Typography>
</Box>
);
};

render(): Node {
const {
walletAddresses,
onVerifyAddress,
onGeneratePaymentURI,
onCopyAddressTooltip,
notification,
} = this.props;
const { intl } = this.context;
const valueBlock = this.getValueBlock();
const walletReceiveContent = (
<div className={styles.generatedAddresses}>
{/* Header Addresses */}
<Box
py="13px"
px="24px"
borderBottom="1px solid"
borderBottomColor="grayscale.200"
className={styles.generatedAddressesGrid}
>
<Typography color="grayscale.500" component="h2" variant="body2">
{intl.formatMessage(messages.generatedAddressesSectionTitle)}
</Typography>
{valueBlock.header}
{onGeneratePaymentURI != null && (
<Typography color="grayscale.500" component="h2" variant="body2">
{intl.formatMessage(messages.generateURLLabel)}
</Typography>
)}
<Typography color="grayscale.500" component="h2" variant="body2">
{intl.formatMessage(messages.verifyAddressLabel)}
</Typography>
</Box>

{/* Content Addresses */}
{walletAddresses.map((address, index) => {
const addressClasses = classnames([
'generatedAddress-' + (index + 1),
styles.walletAddress,
styles.generatedAddressesGrid,
address.isUsed === true ? styles.usedWalletAddress : null,
]);
const notificationElementId = `address-${index}-copyNotification`;
return (
<Box
key={`gen-${address.address}`}
sx={{ p: '13px 24px !important', pl: '32px !important' }}
className={addressClasses}
>
{/* Address Id */}
<CopyableAddress
hash={address.address}
elementId={notificationElementId}
onCopyAddress={() => onCopyAddressTooltip(address.address, notificationElementId)}
notification={notification}
placementTooltip="bottom-start"
>
<ExplorableHashContainer
selectedExplorer={this.props.selectedExplorer}
hash={address.address}
light={address.isUsed === true}
linkType={
address.type === CoreAddressTypes.CARDANO_REWARD ? 'stakeAddress' : 'address'
}
>
<RawHash light={address.isUsed === true}>
<Typography variant="body1" color="grayscale.900">
{truncateAddressShort(address.address, 16)}
</Typography>
</RawHash>
</ExplorableHashContainer>
</CopyableAddress>
{/* Address balance block start */}
{valueBlock.body(address)}
{/* Generate payment URL for Address action */}
{onGeneratePaymentURI != null && (
<div
className={classnames([
styles.addressActionItemBlock,
styles.generateURLActionBlock,
])}
>
<button
type="button"
onClick={onGeneratePaymentURI.bind(this, address.address)}
className={styles.btnGenerateURI}
>
<div className={styles.generateURLActionBlock}>
<span className={styles.generateURIIcon}>
<GenerateURIIcon />
</span>
</div>
</button>
</div>
)}
{/* Verify Address action */}
<div
className={classnames([styles.addressActionItemBlock, styles.verifyActionBlock])}
>
<button type="button" onClick={onVerifyAddress.bind(this, address)}>
<div>
<span className={styles.verifyIcon}>
<VerifyIcon />
</span>
</div>
</button>
</div>
{/* Action block end */}
</Box>
);
})}
</div>
);

if (walletAddresses === undefined || walletAddresses.length === 0) {
return (
<div className={styles.component}>
{this.getHierarchy()}
{this.props.header}
<div className={styles.notFound}>
<NoTransactionModernSvg />
<h1>{intl.formatMessage(messages.noResultsFoundLabel)}</h1>
<p>{intl.formatMessage(messages.notFoundAnyAddresses)}</p>
</div>
</div>
);
}

return (
<Box className={styles.component} pl="24px">
{this.getHierarchy()}
{this.props.header}
{walletReceiveContent}
</Box>
);
}
}
Loading