Skip to content

Commit

Permalink
feat(staking): add earnings projections to the staking modal
Browse files Browse the repository at this point in the history
  • Loading branch information
dev-pvl authored and tomasklim committed Oct 14, 2024
1 parent e583e84 commit 9a86875
Show file tree
Hide file tree
Showing 23 changed files with 528 additions and 181 deletions.
1 change: 1 addition & 0 deletions packages/components/src/components/Image/images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const SVG_IMAGES = {
TREZOR_SAFE_PROMO_UNDERLINE: 'trezor-safe-promo-underline.svg',
CONFIRM_EVM_EXPLANATION_ETH: 'confirm-evm-explanation-eth.svg',
CONFIRM_EVM_EXPLANATION_OTHER: 'confirm-evm-explanation-other.svg',
GAINS_GRAPH: 'gains-graph.svg',
} as const;

export type PngImage = keyof typeof PNG_IMAGES;
Expand Down
13 changes: 13 additions & 0 deletions packages/suite-data/files/images/svg/gains-graph.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions packages/suite/src/components/suite/StakingProcess/InfoRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Badge, Column, List, Paragraph, Row } from '@trezor/components';
import { Subheading } from './Subheading';
import { RowContent, RowSubheading } from './types';

interface InfoRowProps {
heading: React.ReactNode;
subheading: RowSubheading;
content: RowContent;
isExpanded?: boolean;
}

export const InfoRow = ({ heading, subheading, content, isExpanded = false }: InfoRowProps) => {
const displayContent = content.isBadge ? (
<Badge size="tiny">{content.text}</Badge>
) : (
<Paragraph variant="tertiary" typographyStyle="hint">
{content.text}
</Paragraph>
);

return (
<List.Item>
<Row justifyContent="space-between">
<Column alignItems="normal">
{heading}
<Subheading isExpanded={isExpanded} subheading={subheading} />
</Column>
{displayContent}
</Row>
</List.Item>
);
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import {
selectAccountStakeTransactions,
selectValidatorsQueue,
Expand All @@ -9,13 +10,15 @@ import { useSelector } from 'react-redux';
import { Translation } from 'src/components/suite';
import { getDaysToAddToPool } from 'src/utils/suite/stake';
import { InfoRow } from './InfoRow';
import { Account } from 'src/types/wallet';
import { CoinjoinRootState } from 'src/reducers/wallet/coinjoinReducer';

interface StakingInfoProps {
account?: Account;
isExpanded?: boolean;
}

export const StakingInfo = ({ account }: StakingInfoProps) => {
export const StakingInfo = ({ isExpanded }: StakingInfoProps) => {
const { account } = useSelector((state: CoinjoinRootState) => state.wallet.selectedAccount);

const { data } =
useSelector((state: StakeRootState) => selectValidatorsQueue(state, account?.symbol)) || {};

Expand All @@ -33,11 +36,20 @@ export const StakingInfo = ({ account }: StakingInfoProps) => {

const infoRows = [
{
label: <Translation id="TR_STAKE_SIGN_TRANSACTION" />,
heading: <Translation id="TR_STAKE_SIGN_TRANSACTION" />,
subheading: { isCurrentStep: true },
content: { text: <Translation id="TR_COINMARKET_NETWORK_FEE" />, isBadge: true },
},
{
label: <Translation id="TR_STAKE_ENTER_THE_STAKING_POOL" />,
heading: <Translation id="TR_STAKE_ENTER_THE_STAKING_POOL" />,
subheading: {
text: (
<Translation
id="TR_STAKING_GETTING_READY"
values={{ symbol: account.symbol.toUpperCase() }}
/>
),
},
content: {
text: (
<>
Expand All @@ -47,15 +59,16 @@ export const StakingInfo = ({ account }: StakingInfoProps) => {
},
},
{
label: <Translation id="TR_STAKE_EARN_REWARDS_WEEKLY" />,
heading: <Translation id="TR_STAKE_EARN_REWARDS_WEEKLY" />,
subheading: { text: <Translation id="TR_STAKING_REWARDS_ARE_RESTAKED" /> },
content: { text: `~${ethApy}% p.a.` },
},
];

return (
<>
{infoRows.map(({ label, content }, index) => (
<InfoRow key={index} label={label} content={content} />
{infoRows.map(({ heading, content, subheading }, index) => (
<InfoRow key={index} {...{ heading, subheading, content, isExpanded }} />
))}
</>
);
Expand Down
26 changes: 26 additions & 0 deletions packages/suite/src/components/suite/StakingProcess/Subheading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Icon, Paragraph, Row } from '@trezor/components';
import { Translation } from '../Translation';
import { spacings } from '@trezor/theme';
import { RowSubheading } from './types';

interface SubheadingProps {
subheading: RowSubheading;
isExpanded?: boolean;
}

export const Subheading = ({ isExpanded, subheading }: SubheadingProps) => {
if (!isExpanded || !subheading) return null;

return subheading.isCurrentStep ? (
<Row gap={spacings.xxs}>
<Icon name="mapPinFilled" variant="warning" size="medium" />
<Paragraph variant="warning" typographyStyle="hint">
<Translation id="TR_STAKING_YOU_ARE_HERE" />
</Paragraph>
</Row>
) : (
<Paragraph variant="tertiary" typographyStyle="hint">
{subheading.text}
</Paragraph>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react';
import {
selectAccountUnstakeTransactions,
selectValidatorsQueue,
TransactionsRootState,
StakeRootState,
} from '@suite-common/wallet-core';
import { Translation } from 'src/components/suite';
import { InfoRow } from './InfoRow';
import { useSelector } from 'react-redux';
import { getDaysToUnstake } from 'src/utils/suite/stake';
import { CoinjoinRootState } from 'src/reducers/wallet/coinjoinReducer';

interface UnstakingInfoProps {
isExpanded?: boolean;
}

export const UnstakingInfo = ({ isExpanded }: UnstakingInfoProps) => {
const { account } = useSelector((state: CoinjoinRootState) => state.wallet.selectedAccount);

const { data } =
useSelector((state: StakeRootState) => selectValidatorsQueue(state, account?.symbol)) || {};

const unstakeTxs = useSelector((state: TransactionsRootState) =>
selectAccountUnstakeTransactions(state, account?.key ?? ''),
);

if (!account) return null;

const daysToUnstake = getDaysToUnstake(unstakeTxs, data);
const accountSymbol = account.symbol.toUpperCase();

const infoRows = [
{
heading: <Translation id="TR_STAKE_SIGN_UNSTAKING_TRANSACTION" />,
subheading: { isCurrentStep: true },
content: {
text: <Translation id="TR_COINMARKET_NETWORK_FEE" />,
isBadge: true,
},
},
{
heading: <Translation id="TR_STAKE_LEAVE_STAKING_POOL" />,
subheading: {
text: (
<Translation
id="TR_STAKING_CONSOLIDATING_FUNDS"
values={{ symbol: accountSymbol }}
/>
),
},
content: {
text: <Translation id="TR_STAKE_DAYS" values={{ count: daysToUnstake }} />,
},
},
{
heading: (
<Translation id="TR_STAKE_CLAIM_UNSTAKED" values={{ symbol: accountSymbol }} />
),
subheading: {
text: (
<Translation
id="TR_STAKING_YOUR_UNSTAKED_FUNDS"
values={{ symbol: accountSymbol }}
/>
),
},
content: {
text: <Translation id="TR_COINMARKET_NETWORK_FEE" />,
isBadge: true,
},
},
{
heading: <Translation id="TR_STAKE_IN_ACCOUNT" values={{ symbol: accountSymbol }} />,
subheading: null,
content: { text: null },
},
];

return (
<>
{infoRows.map(({ heading, content, subheading }, index) => (
<InfoRow key={index} {...{ heading, subheading, content, isExpanded }} />
))}
</>
);
};
9 changes: 9 additions & 0 deletions packages/suite/src/components/suite/StakingProcess/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type RowSubheading = {
text?: React.ReactNode;
isCurrentStep?: boolean;
} | null;

export type RowContent = {
isBadge?: boolean;
text?: React.ReactNode;
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import React from 'react';

import {
Icon,
IconName,
Expand All @@ -19,8 +21,8 @@ import { spacings } from '@trezor/theme';
import { selectSelectedAccount } from 'src/reducers/wallet/selectedAccountReducer';
import { getUnstakingPeriodInDays } from 'src/utils/suite/stake';
import { selectValidatorsQueueData } from '@suite-common/wallet-core';
import { StakingInfo } from './StakingInfo';
import { UnstakingInfo } from './UnstakingInfo';
import { StakingInfo } from 'src/components/suite/StakingProcess/StakingInfo';
import { UnstakingInfo } from 'src/components/suite/StakingProcess/UnstakingInfo';

interface StakingDetails {
id: number;
Expand Down Expand Up @@ -68,7 +70,7 @@ export const StakeEthInANutshellModal = ({ onCancel }: StakeEthInANutshellModalP
{
heading: <Translation id="TR_STAKE_STAKING_PROCESS" />,
badge: <Translation id="TR_TX_FEE" />,
content: <StakingInfo account={account} />,
content: <StakingInfo />,
},
{
heading: <Translation id="TR_STAKE_UNSTAKING_PROCESS" />,
Expand All @@ -78,7 +80,7 @@ export const StakeEthInANutshellModal = ({ onCancel }: StakeEthInANutshellModalP
<Translation id="TR_TX_FEE" />
</>
),
content: <UnstakingInfo account={account} />,
content: <UnstakingInfo />,
},
];

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { FIAT_INPUT, CRYPTO_INPUT } from 'src/types/wallet/stakeForms';
import { MIN_ETH_FOR_WITHDRAWALS } from 'src/constants/suite/ethStaking';
import { spacings, spacingsPx } from '@trezor/theme';
import { validateStakingMax } from 'src/utils/suite/stake';

const IconWrapper = styled.div`
transform: rotate(90deg);
Expand Down Expand Up @@ -53,6 +54,7 @@ export const Inputs = () => {
required: translationString('AMOUNT_IS_NOT_SET'),
validate: {
min: validateMin(translationString),
max: validateStakingMax(translationString),
decimals: validateDecimals(translationString, { decimals: network.decimals }),
reserveOrBalance: validateReserveOrBalance(translationString, {
account,
Expand Down
Loading

0 comments on commit 9a86875

Please sign in to comment.