Skip to content

Commit 5a4f717

Browse files
authored
Merge pull request #4468 from Emurgo/chore/YW-174/staking-revamp
Staking Revamp
2 parents 26fb657 + 0476e01 commit 5a4f717

File tree

27 files changed

+1585
-108
lines changed

27 files changed

+1585
-108
lines changed

packages/yoroi-extension/app/Routes.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,7 @@ export const YoroiRoutes = (stores: StoresMap): Node => {
204204
<Route element={<DappCenterSubpages stores={stores} />}>
205205
<Route path={ROUTES.DAPP_CONNECTOR.DAPP_CENTER} element={<DappCenterPage stores={stores} />} />
206206
</Route>
207-
<Route element={<StakingSubpages stores={stores} />}>
208-
<Route path={ROUTES.STAKING_REVAMP.ROOT} element={<StakingPageRevamp stores={stores} />} />
209-
</Route>
207+
210208
<Route element={<WalletsSubpages stores={stores} />}>
211209
<Route path={ROUTES.WALLETS.TRANSACTIONS} element={<WalletSummaryPage stores={stores} />} />
212210
<Route path={ROUTES.WALLETS.SEND} element={<WalletSendPage stores={stores} />} />
@@ -256,6 +254,9 @@ export const YoroiRoutes = (stores: StoresMap): Node => {
256254
<Route path={ROUTES.GOVERNANCE.ROOT} element={<GovernanceStatusPage stores={stores} />} />
257255
<Route path={ROUTES.GOVERNANCE.OPTIONS} element={<GovernanceOptionsPage stores={stores} />} />
258256
</Route>
257+
<Route element={<StakingSubpages stores={stores} />}>
258+
<Route path={ROUTES.STAKING_REVAMP.ROOT} element={<StakingPageRevamp stores={stores} />} />
259+
</Route>
259260
<Route element={<PortfolioSubpages stores={stores} />}>
260261
<Route path={ROUTES.PORTFOLIO.ROOT} element={<PortfolioPage stores={stores} />} />
261262
<Route path={ROUTES.PORTFOLIO.DAPPS} element={<PortfolioDappsPage stores={stores} />} />
Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,49 @@
1-
import { Stack } from '@mui/material';
1+
import { Box, Stack, styled } from '@mui/material';
22
import { PoolList } from './useCases/PoolList/PoolList';
33
import { RewardsSummaryCard } from './useCases/RewardsSummary/RewardsSummaryCard';
4+
import { StakePoolDelegated } from './useCases/DelegatedStakePoolInfo/StakePoolDelegated';
5+
import EpochProgress from './useCases/EpochProgress/EpochProgress';
6+
import { LegacyDialogs } from './useCases/LegacyDialogs/LegacyDialogs';
7+
import { useStaking } from './module/StakingContextProvider';
8+
import BuySellDialog from '../../../components/buySell/BuySellDialog';
9+
import WalletEmptyBanner from './common/components/EmptyWalletBanner';
410

511
export const StakingRoot = () => {
12+
const { isWalletWithNoFunds, selectedWallet, legacyUIDialogs, currentlyDelegating } = useStaking();
13+
614
return (
715
<Stack>
8-
<RewardsSummaryCard />
16+
{isWalletWithNoFunds ? (
17+
<WalletEmptyBanner
18+
onBuySellClick={() => legacyUIDialogs.open({ dialog: BuySellDialog })}
19+
isTestnet={selectedWallet.isTestnet}
20+
/>
21+
) : null}
22+
{currentlyDelegating && (
23+
<WrapperCards>
24+
<RewardsSummaryCard />
25+
<RightCardsWrapper>
26+
<StakePoolDelegated />
27+
<EpochProgress />
28+
</RightCardsWrapper>
29+
</WrapperCards>
30+
)}
931
<PoolList />
32+
<LegacyDialogs />
1033
</Stack>
1134
);
1235
};
36+
37+
const WrapperCards = styled(Box)({
38+
display: 'flex',
39+
gap: '24px',
40+
justifyContent: 'space-between',
41+
marginBottom: '24px',
42+
});
43+
44+
const RightCardsWrapper = styled(Box)({
45+
display: 'flex',
46+
width: '100%',
47+
flexDirection: 'column',
48+
gap: '24px',
49+
});
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import React from 'react';
2+
import { Box } from '@mui/system';
3+
import { Button, Stack, Typography } from '@mui/material';
4+
import { ReactComponent as CoverBg } from './wallet-empty-banner.inline.svg';
5+
import { captureEvent } from '../../../../../../posthog';
6+
import { TESTNET_FAUCET } from '../constants';
7+
import { useStrings } from '../hooks/useStrings';
8+
9+
export type WalletEmptyBannerProps = {
10+
onBuySellClick: () => void;
11+
isTestnet: boolean;
12+
};
13+
14+
const WalletEmptyBanner: React.FC<WalletEmptyBannerProps> = ({ isTestnet, onBuySellClick }) => {
15+
const strings = useStrings();
16+
17+
const handleClick = () => {
18+
if (isTestnet) {
19+
window.open(TESTNET_FAUCET, '_blank');
20+
} else {
21+
onBuySellClick();
22+
}
23+
24+
captureEvent('Wallet Page Exchange Clicked');
25+
};
26+
27+
return (
28+
<Box>
29+
<Box
30+
sx={{
31+
background: theme => theme.palette.ds.bg_gradient_1,
32+
marginBottom: '40px',
33+
borderRadius: '8px',
34+
overflowY: 'hidden',
35+
position: 'relative',
36+
padding: '16px',
37+
height: 'auto',
38+
}}
39+
id="wallet|staking-emptyWalletBanner-box"
40+
>
41+
<Box sx={{ position: 'absolute', right: '1%', top: 'auto', bottom: '-3px' }}>
42+
<CoverBg />
43+
</Box>
44+
45+
<Box>
46+
<Typography component="div" variant="h3" color="ds.gray_max" fontWeight={500} fontSize="18px" mb="8px">
47+
{isTestnet ? strings.welcomeMessageTestnet : strings.welcomeMessage}
48+
</Typography>
49+
50+
<Typography component="div" variant="body1" color="ds.gray_max" mb="24px">
51+
{isTestnet ? strings.welcomeMessageSubtitleTestnet : strings.welcomeMessageSubtitle}
52+
{isTestnet ? (
53+
<>
54+
<br />
55+
{strings.welcomeMessageSubtitleTestnetExtra}
56+
</>
57+
) : null}
58+
</Typography>
59+
</Box>
60+
61+
<Stack direction="row" gap="16px">
62+
<Button
63+
variant="contained"
64+
color="primary"
65+
size="medium"
66+
sx={{
67+
'&.MuiButton-sizeMedium': {
68+
padding: '9px 20px',
69+
height: 'unset',
70+
},
71+
}}
72+
onClick={handleClick}
73+
>
74+
<Typography component="div" variant="button" fontWeight={500} sx={{ lineHeight: '19px' }}>
75+
{isTestnet ? strings.goToFaucetButton : strings.buyAda}
76+
</Typography>
77+
</Button>
78+
</Stack>
79+
</Box>
80+
</Box>
81+
);
82+
};
83+
84+
export default WalletEmptyBanner;

packages/yoroi-extension/app/UI/features/staking/common/components/wallet-empty-banner.inline.svg

Lines changed: 508 additions & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const TESTNET_FAUCET = 'https://docs.cardano.org/cardano-testnets/tools/faucet';

packages/yoroi-extension/app/UI/features/staking/common/hooks/useStrings.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,74 @@ export const messages = Object.freeze(
4848
id: 'global.labels.error',
4949
defaultMessage: '!!!Error',
5050
},
51+
stakePoolDelegated: {
52+
id: 'wallet.dashboard.upcomingRewards.stakePoolDelegated',
53+
defaultMessage: '!!!Stake Pool Delegated',
54+
},
55+
roa30dLabel: {
56+
id: 'wallet.staking.banner.roa30d',
57+
defaultMessage: '!!!ROA 30d',
58+
},
59+
poolSizeLabel: {
60+
id: 'wallet.staking.pool.size',
61+
defaultMessage: '!!!Pool size',
62+
},
63+
poolSaturation: {
64+
id: 'wallet.staking.pool.saturation',
65+
defaultMessage: '!!!Saturation',
66+
},
67+
updatePoolLabel: {
68+
id: 'global.updatePool',
69+
defaultMessage: '!!! UPDATE POOL',
70+
},
71+
undelegatePool: {
72+
id: 'transaction.review.undelegatePool',
73+
defaultMessage: '!!!Unstake entire wallet balance from',
74+
},
75+
undelegateLabel: {
76+
id: 'global.labael.undelegate',
77+
defaultMessage: '!!!Undelegate',
78+
},
79+
deregisteringStakingKey: {
80+
id: 'transaction.review.deregisteringStakingKey',
81+
defaultMessage: '!!!Undelegating from the pool',
82+
},
83+
epochProgress: {
84+
id: 'wallet.staking.epochProgress',
85+
defaultMessage: '!!!Epoch Progress',
86+
},
87+
welcomeMessage: {
88+
id: 'wallet.emptyWalletMessage',
89+
defaultMessage: '!!!Your wallet is empty',
90+
},
91+
welcomeMessageSubtitle: {
92+
id: 'wallet.emptyWalletMessageSubtitle',
93+
defaultMessage: '!!!Top up your wallet safely using our trusted partners',
94+
},
95+
welcomeMessageTestnet: {
96+
id: 'wallet.emptyWalletMessage.testnet',
97+
defaultMessage: '!!!Learn Cardano with test ADA ⭐',
98+
},
99+
welcomeMessageSubtitleTestnet: {
100+
id: 'wallet.emptyWalletMessageSubtitle.testnet',
101+
defaultMessage: '!!!Stake your test ADA by participating in our testnet staking program.',
102+
},
103+
welcomeMessageSubtitleTestnetExtra: {
104+
id: 'wallet.emptyWalletMessageSubtitle.testnetExtra',
105+
defaultMessage: "!!!Get your TADA. It's your key to testing a new world of possibilities.",
106+
},
107+
goToFaucetButton: {
108+
id: 'wallet.emptyWalletMessage.goToFaucet',
109+
defaultMessage: '!!!ADD TEST ADA',
110+
},
111+
buyAda: {
112+
id: 'button.buyAda',
113+
defaultMessage: '!!!Buy ADA',
114+
},
115+
stakePoolLabel: {
116+
id: 'wallet.delegation.transaction.stakePoolLabel',
117+
defaultMessage: '!!!Stake pool',
118+
},
51119
})
52120
);
53121

@@ -65,5 +133,22 @@ export const useStrings = () => {
65133
rewardValue: intl.formatMessage(messages.rewardValue),
66134
rewardsLabel: intl.formatMessage(messages.rewardsLabel),
67135
errorLabel: intl.formatMessage(messages.errorLabel),
136+
stakePoolDelegated: intl.formatMessage(messages.stakePoolDelegated),
137+
roa30dLabel: intl.formatMessage(messages.roa30dLabel),
138+
poolSizeLabel: intl.formatMessage(messages.poolSizeLabel),
139+
poolSaturation: intl.formatMessage(messages.poolSaturation),
140+
updatePoolLabel: intl.formatMessage(messages.updatePoolLabel),
141+
undelegatePool: intl.formatMessage(messages.undelegatePool),
142+
undelegateLabel: intl.formatMessage(messages.undelegateLabel),
143+
deregisteringStakingKey: intl.formatMessage(messages.deregisteringStakingKey),
144+
epochProgress: intl.formatMessage(messages.epochProgress),
145+
welcomeMessage: intl.formatMessage(messages.welcomeMessage),
146+
welcomeMessageSubtitle: intl.formatMessage(messages.welcomeMessageSubtitle),
147+
welcomeMessageTestnet: intl.formatMessage(messages.welcomeMessageTestnet),
148+
welcomeMessageSubtitleTestnet: intl.formatMessage(messages.welcomeMessageSubtitleTestnet),
149+
welcomeMessageSubtitleTestnetExtra: intl.formatMessage(messages.welcomeMessageSubtitleTestnetExtra),
150+
goToFaucetButton: intl.formatMessage(messages.goToFaucetButton),
151+
buyAda: intl.formatMessage(messages.buyAda),
152+
stakePoolLabel: intl.formatMessage(messages.stakePoolLabel),
68153
}).current;
69154
};

packages/yoroi-extension/app/UI/features/staking/common/types/index.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Define types
1+
import type { ExplorerPoolInfo as PoolInfo } from '@emurgo/yoroi-lib';
22
export type StakingActions = {};
33

44
// Define state type
@@ -13,6 +13,13 @@ export type StakingState = {
1313
historyGraphData: GraphData | null;
1414
primaryTokenInfo: any;
1515
toUnitOfAccount: (entry: any) => void | { currency: string; amount: string };
16+
defaultDelegatedAsset: any;
17+
selectedWallet: any;
18+
delegationStore: any;
19+
legacyUIDialogs: any;
20+
delegationRequests: any;
21+
isWalletWithNoFunds: boolean;
22+
currentlyDelegating: boolean;
1623
};
1724

1825
export interface GraphItems {
@@ -33,3 +40,35 @@ export interface RewardsGraphData {
3340
export interface GraphData {
3441
readonly rewardsGraphData: RewardsGraphData;
3542
}
43+
44+
export interface SocialLinks {
45+
tw?: string;
46+
fb?: string;
47+
gh?: string;
48+
tc?: string;
49+
tg?: string;
50+
di?: string;
51+
yt?: string;
52+
web?: string;
53+
icon?: string;
54+
}
55+
56+
export interface PoolData {
57+
id: string;
58+
name: string;
59+
ticker?: string;
60+
avatar?: string | null;
61+
roa?: string | null;
62+
poolSize?: string | null;
63+
share?: string | null;
64+
websiteUrl?: string;
65+
socialLinks?: SocialLinks;
66+
}
67+
68+
export interface PoolTransition {
69+
currentPool?: PoolInfo | null;
70+
deadlineMilliseconds?: number | null;
71+
shouldShowTransitionFunnel: boolean;
72+
suggestedPool?: PoolInfo | null;
73+
deadlinePassed: boolean;
74+
}

packages/yoroi-extension/app/UI/features/staking/module/StakingContextProvider.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,23 @@ export const StakingContextProvider = observer(({ children, stores }: StakingPro
4343
throw new Error(`Page opened for non-reward wallet`);
4444
}
4545

46+
// Extract rewardHistory result to make it observable in the dependency array
47+
const rewardHistoryResult = delegationRequests.rewardHistory.result;
48+
49+
// Reset graphData when wallet changes
50+
React.useEffect(() => {
51+
setGraphData(null);
52+
}, [selectedWallet.publicDeriverId]);
53+
54+
// Generate graph data when reward history is available
4655
React.useEffect(() => {
56+
// Only generate graph data if rewardHistory.result is available
57+
// This prevents generating graph data with null result which causes infinite loading
58+
if (rewardHistoryResult == null) {
59+
setGraphData(null);
60+
return;
61+
}
62+
4763
const historyGraphData = generateGraphData({
4864
delegationRequests,
4965
currentEpoch: stores.substores.ada.time.getCurrentTimeRequests(selectedWallet).currentEpoch,
@@ -54,9 +70,18 @@ export const StakingContextProvider = observer(({ children, stores }: StakingPro
5470
defaultTokenId: selectedWallet.defaultTokenId,
5571
});
5672
setGraphData(historyGraphData);
57-
}, [delegationRequests, selectedWallet, currentlyDelegating]);
58-
59-
React.useEffect(() => {}, []);
73+
}, [
74+
delegationRequests,
75+
rewardHistoryResult,
76+
selectedWallet.publicDeriverId,
77+
selectedWallet.networkId,
78+
selectedWallet.defaultTokenId,
79+
currentlyDelegating,
80+
stores.profile.shouldHideBalance,
81+
stores.substores.ada.time,
82+
stores.delegation.getLocalPoolInfo,
83+
stores.tokenInfoStore.tokenInfo,
84+
]);
6085

6186
const totalDelegated = () => {
6287
if (!showRewardAmount) return undefined;

packages/yoroi-extension/app/UI/features/staking/module/state.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ export const defaultStakingState: StakingState = {
1212
historyGraphData: null,
1313
primaryTokenInfo: null,
1414
toUnitOfAccount: () => ({ currency: '', amount: '' }),
15+
defaultDelegatedAsset: null,
16+
selectedWallet: null,
17+
delegationStore: null,
18+
legacyUIDialogs: null,
19+
delegationRequests: null,
20+
isWalletWithNoFunds: false,
21+
currentlyDelegating: false,
1522
};
1623

1724
// Define action handlers

0 commit comments

Comments
 (0)