Skip to content

Commit 00e51b6

Browse files
committed
feat(suite): add empty state and refetch logic for Solana staking rewards
1 parent 4b45ef7 commit 00e51b6

File tree

7 files changed

+108
-28
lines changed

7 files changed

+108
-28
lines changed

packages/suite/src/support/messages.ts

+13
Original file line numberDiff line numberDiff line change
@@ -8568,6 +8568,19 @@ export default defineMessages({
85688568
defaultMessage:
85698569
'An epoch in Solana is approximately {count, plural, one {# day} other {# days}} long.',
85708570
},
8571+
TR_STAKE_REFRESH_REWARDS_TOOLTIP: {
8572+
id: 'TR_STAKE_REFRESH_REWARDS_TOOLTIP',
8573+
defaultMessage: 'Refresh your rewards for this account.',
8574+
},
8575+
TR_STAKE_REWARDS_ARE_EMPTY: {
8576+
id: 'TR_STAKE_REWARDS_ARE_EMPTY',
8577+
defaultMessage: 'No Rewards',
8578+
},
8579+
TR_STAKE_WAIT_TO_CHECK_REWARDS: {
8580+
id: 'TR_STAKE_WAIT_TO_CHECK_REWARDS',
8581+
defaultMessage:
8582+
'Wait up to {count, plural, one {# day} other {# days}} to check your rewards',
8583+
},
85718584
TR_STAKE_ETH_CARD_TITLE: {
85728585
id: 'TR_STAKE_ETH_CARD_TITLE',
85738586
defaultMessage: 'The easiest way to earn {symbol}',

packages/suite/src/views/wallet/staking/components/SolStakingDashboard/SolStakingDashboard.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { ApyCard } from '../StakingDashboard/components/ApyCard';
1616
import { PayoutCard } from '../StakingDashboard/components/PayoutCard';
1717
import { ClaimCard } from '../StakingDashboard/components/ClaimCard';
1818
import { StakingCard } from '../StakingDashboard/components/StakingCard';
19-
import { RewardsList } from './components/RewardsList';
19+
import { RewardsList } from './components/Rewards/RewardsList';
2020

2121
interface SolStakingDashboardProps {
2222
selectedAccount: SelectedAccountLoaded;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { SOLANA_EPOCH_DAYS } from '@suite-common/wallet-constants';
2+
3+
import { Translation } from 'src/components/suite';
4+
import { AccountExceptionLayout } from 'src/components/wallet';
5+
6+
export const RewardsEmpty = () => (
7+
<AccountExceptionLayout
8+
title={<Translation id="TR_STAKE_REWARDS_ARE_EMPTY" />}
9+
description={
10+
<Translation
11+
id="TR_STAKE_WAIT_TO_CHECK_REWARDS"
12+
values={{ count: SOLANA_EPOCH_DAYS }}
13+
/>
14+
}
15+
iconName="arrowLineDown"
16+
iconVariant="tertiary"
17+
/>
18+
);

packages/suite/src/views/wallet/staking/components/SolStakingDashboard/components/RewardsList.tsx packages/suite/src/views/wallet/staking/components/SolStakingDashboard/components/Rewards/RewardsList.tsx

+64-21
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useRef, useState } from 'react';
1+
import React, { useCallback, useEffect, useRef, useState } from 'react';
22

33
import {
44
EverstakeRewardsEndpointType,
@@ -7,13 +7,24 @@ import {
77
StakeAccountRewards,
88
} from '@suite-common/wallet-core';
99
import { formatNetworkAmount } from '@suite-common/wallet-utils';
10-
import { Badge, Card, Column, Icon, Row, SkeletonStack, Text, Tooltip } from '@trezor/components';
10+
import {
11+
Badge,
12+
Card,
13+
Column,
14+
Icon,
15+
IconButton,
16+
Row,
17+
SkeletonStack,
18+
Text,
19+
Tooltip,
20+
} from '@trezor/components';
1121
import { spacings } from '@trezor/theme';
1222
import { SOLANA_EPOCH_DAYS } from '@suite-common/wallet-constants';
23+
import { useDebounce } from '@trezor/react-utils';
1324

1425
import {
15-
CoinBalance,
1626
FiatValue,
27+
FormattedCryptoAmount,
1728
FormattedDate,
1829
HiddenPlaceholder,
1930
Translation,
@@ -25,21 +36,25 @@ import { Pagination } from 'src/components/wallet';
2536
import SkeletonTransactionItem from 'src/views/wallet/transactions/TransactionList/SkeletonTransactionItem';
2637
import { ColDate } from 'src/views/wallet/transactions/TransactionList/TransactionsGroup/CommonComponents';
2738

39+
import { RewardsEmpty } from './RewardsEmpty';
40+
2841
const PAGE_SIZE_DEFAULT = 10;
2942

3043
interface RewardsListProps {
3144
account: Account;
3245
}
3346

3447
export const RewardsList = ({ account }: RewardsListProps) => {
35-
const anchor = useSelector(state => state.router.anchor);
3648
const { data, isLoading } =
37-
useSelector(state => selectStakingRewards(state, account?.symbol)) || {};
49+
useSelector(state => selectStakingRewards(state, account.symbol)) || {};
3850

3951
const { rewards } = data ?? {};
52+
const selectedAccountRewards = rewards?.[account.descriptor];
53+
4054
const sectionRef = useRef<HTMLDivElement>(null);
4155

4256
const dispatch = useDispatch();
57+
const debounce = useDebounce();
4358

4459
const perPage = PAGE_SIZE_DEFAULT;
4560
const startPage = 1;
@@ -50,32 +65,41 @@ export const RewardsList = ({ account }: RewardsListProps) => {
5065
const startIndex = (currentPage - 1) * perPage;
5166
const stopIndex = startIndex + perPage;
5267

68+
const isSolanaMainnet = account.symbol === 'sol';
69+
70+
const fetchRewards = useCallback(
71+
async ({ symbol, descriptor }: Account) => {
72+
await debounce(() => {
73+
if (symbol !== 'sol') return;
74+
dispatch(
75+
fetchEverstakeRewards({
76+
symbol,
77+
endpointType: EverstakeRewardsEndpointType.GetRewards,
78+
address: descriptor,
79+
}),
80+
);
81+
});
82+
},
83+
[dispatch, debounce],
84+
);
85+
5386
useEffect(() => {
54-
// Fetch rewards only for the Solana mainnet
55-
if (account.symbol === 'sol') {
56-
dispatch(
57-
fetchEverstakeRewards({
58-
symbol: account.symbol,
59-
endpointType: EverstakeRewardsEndpointType.GetRewards,
60-
address: account.descriptor,
61-
}),
62-
);
63-
}
64-
}, [anchor, account, dispatch]);
87+
fetchRewards(account);
88+
}, [account, fetchRewards]);
6589

6690
useEffect(() => {
67-
if (rewards) {
68-
const slicedRewards = rewards?.slice(startIndex, stopIndex);
91+
if (selectedAccountRewards) {
92+
const slicedRewards = selectedAccountRewards?.slice(startIndex, stopIndex);
6993
setSlicedRewards(slicedRewards);
7094
}
71-
}, [currentPage, rewards, startIndex, stopIndex]);
95+
}, [currentPage, selectedAccountRewards, startIndex, stopIndex]);
7296

7397
useEffect(() => {
7498
// reset page on account change
7599
setSelectedPage(startPage);
76100
}, [account.descriptor, account.symbol, startPage]);
77101

78-
const totalItems = rewards?.length ?? 0;
102+
const totalItems = selectedAccountRewards?.length ?? 0;
79103
const showPagination = totalItems > perPage;
80104
const isLastPage = stopIndex >= totalItems;
81105

@@ -86,10 +110,29 @@ export const RewardsList = ({ account }: RewardsListProps) => {
86110
}
87111
};
88112

113+
if (!isSolanaMainnet || !totalItems) {
114+
return <RewardsEmpty />;
115+
}
116+
89117
return (
90118
<DashboardSection
91119
ref={sectionRef}
92120
heading={<Translation id="TR_REWARDS" />}
121+
actions={
122+
<Column>
123+
<Tooltip
124+
maxWidth={250}
125+
content={<Translation id="TR_STAKE_REFRESH_REWARDS_TOOLTIP" />}
126+
>
127+
<IconButton
128+
icon="arrowClockwise"
129+
variant="tertiary"
130+
size="small"
131+
onClick={() => fetchRewards(account)}
132+
/>
133+
</Tooltip>
134+
</Column>
135+
}
93136
data-testid="@wallet/accounts/rewards-list"
94137
>
95138
{isLoading ? (
@@ -147,7 +190,7 @@ export const RewardsList = ({ account }: RewardsListProps) => {
147190
{reward?.amount && (
148191
<Column alignItems="end">
149192
<HiddenPlaceholder>
150-
<CoinBalance
193+
<FormattedCryptoAmount
151194
value={formatNetworkAmount(
152195
reward?.amount,
153196
account.symbol,

suite-common/wallet-core/src/stake/stakeReducer.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { cloneObject } from '@trezor/utils';
44
import { NetworkSymbol } from '@suite-common/wallet-config';
55

66
import { stakeActions } from './stakeActions';
7-
import { ValidatorsQueue, StakeAccountRewards } from './stakeTypes';
7+
import { ValidatorsQueue, StakeRewardsByAccount } from './stakeTypes';
88
import { fetchEverstakeAssetData, fetchEverstakeData, fetchEverstakeRewards } from './stakeThunks';
99
import { SerializedTx } from '../send/sendFormTypes';
1010

@@ -40,7 +40,7 @@ export interface StakeState {
4040
error: boolean | string;
4141
isLoading: boolean;
4242
lastSuccessfulFetchTimestamp: Timestamp;
43-
data: { rewards?: StakeAccountRewards[] };
43+
data: { rewards?: StakeRewardsByAccount };
4444
};
4545
};
4646
};
@@ -174,7 +174,7 @@ export const prepareStakeReducer = createReducerWithExtraDeps(stakeInitialState,
174174
.addCase(fetchEverstakeRewards.pending, (state, action) => {
175175
const { symbol } = action.meta.arg;
176176

177-
if (!state.data[symbol]) {
177+
if (!state.data[symbol]?.stakingRewards) {
178178
state.data[symbol] = {
179179
stakingRewards: {
180180
error: false,

suite-common/wallet-core/src/stake/stakeThunks.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
EverstakeEndpointType,
2020
EverstakeRewardsEndpointType,
2121
ValidatorsQueue,
22-
StakeAccountRewards,
22+
StakeRewardsByAccount,
2323
} from './stakeTypes';
2424
import { EVERSTAKE_ENDPOINT_PREFIX, EVERSTAKE_REWARDS_SOLANA_ENPOINT } from './stakeConstants';
2525
import { selectAllNetworkSymbolsOfVisibleAccounts } from '../accounts/accountsReducer';
@@ -108,7 +108,7 @@ export const fetchEverstakeAssetData = createThunk<
108108
);
109109

110110
export const fetchEverstakeRewards = createThunk<
111-
{ rewards: StakeAccountRewards[] },
111+
{ rewards: StakeRewardsByAccount },
112112
{
113113
symbol: SupportedSolanaNetworkSymbols;
114114
endpointType: EverstakeRewardsEndpointType;
@@ -132,7 +132,9 @@ export const fetchEverstakeRewards = createThunk<
132132
const data = await response.json();
133133

134134
return fulfillWithValue({
135-
rewards: data,
135+
rewards: {
136+
[address]: data,
137+
},
136138
});
137139
} catch (error) {
138140
return rejectWithValue(error.toString());

suite-common/wallet-core/src/stake/stakeTypes.ts

+4
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,7 @@ export type StakeAccountRewards = {
105105
currency: string;
106106
time: string;
107107
};
108+
109+
export type StakeRewardsByAccount = {
110+
[address: string]: StakeAccountRewards[];
111+
};

0 commit comments

Comments
 (0)