Skip to content

Commit 8eb3ddc

Browse files
authored
feat(staking): [LW-8877] Add "Activity" tab to staking (#697)
* feat(staking): add activity tab * feat(staking): [LW-8877] Add activity tab to staking, show rewards list (#684) * feat(staking): add rewards list to Staking Activity tab * feat(staking): expose activity detail drawer in staking section to be used by staking activity * feat(staking): add "no staking activity yet" screen * fix(staking): align "no staking activity" component to center vertically
1 parent 1deae02 commit 8eb3ddc

File tree

21 files changed

+237
-19
lines changed

21 files changed

+237
-19
lines changed

apps/browser-extension-wallet/.env.defaults

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ USE_ADA_HANDLE=true
2323
USE_DATA_CHECK=false
2424
USE_POSTHOG_ANALYTICS=true
2525
USE_COMBINED_PASSWORD_NAME_STEP_COMPONENT=false
26+
USE_MULTI_DELEGATION_STAKING_ACTIVITY=false
2627

2728
USE_POSTHOG_ANALYTICS_FOR_OPTED_OUT=false
2829
USE_MATOMO_ANALYTICS_FOR_OPTED_OUT=false

apps/browser-extension-wallet/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ USE_POSTHOG_ANALYTICS=true
2626
USE_POSTHOG_ANALYTICS_FOR_OPTED_OUT=false
2727
USE_MATOMO_ANALYTICS_FOR_OPTED_OUT=false
2828
USE_COMBINED_PASSWORD_NAME_STEP_COMPONENT=false
29+
USE_MULTI_DELEGATION_STAKING_ACTIVITY=false
30+
2931
# In App URLs
3032
CATALYST_GOOGLE_PLAY_URL=https://play.google.com/store/apps/details?id=io.iohk.vitvoting
3133
CATALYST_APP_STORE_URL=https://apps.apple.com/fr/app/catalyst-voting/id1517473397?l=en

apps/browser-extension-wallet/src/features/delegation/components/MultiDelegationStakingPopup.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export const MultiDelegationStakingPopup = (): JSX.Element => {
7373
name: 'AnalyticsEventNames.Staking.STAKING_MULTI_DELEGATION_POPUP'
7474
});
7575
}, []);
76-
const { walletActivities } = useWalletActivities({ sendAnalytics });
76+
const { walletActivities, walletActivitiesStatus } = useWalletActivities({ sendAnalytics });
7777
const { fiatCurrency } = useCurrencyStore();
7878
const { executeWithPassword } = useWalletManager();
7979
const isLoadingNetworkInfo = useWalletStore(networkInfoStatusSelector);
@@ -127,6 +127,7 @@ export const MultiDelegationStakingPopup = (): JSX.Element => {
127127
walletStoreNetworkInfo: networkInfo,
128128
walletStoreBlockchainProvider: blockchainProvider,
129129
walletStoreWalletActivities: walletActivities,
130+
walletStoreWalletActivitiesStatus: walletActivitiesStatus,
130131
// TODO: LW-7575 make compactNumber reusable and not pass it here.
131132
compactNumber: compactNumberWithUnit,
132133
walletAddress,

apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,15 @@ const buildGetActivityDetail =
8989
walletInfo
9090
} = get();
9191

92+
set({ fetchingActivityInfo: true });
93+
9294
if (activityDetail.type === 'rewards') {
9395
const { activity, status, type } = activityDetail;
9496
const poolInfos = await getPoolInfos(
9597
activity.rewards.map(({ poolId }) => poolId),
9698
stakePoolProvider
9799
);
100+
set({ fetchingActivityInfo: false });
98101

99102
return {
100103
activity: {
@@ -128,7 +131,6 @@ const buildGetActivityDetail =
128131
const { activity: tx, status, type, direction } = activityDetail;
129132
const walletAssets = await firstValueFrom(wallet.assetInfo$);
130133
const protocolParameters = await firstValueFrom(wallet.protocolParameters$);
131-
set({ fetchingActivityInfo: true });
132134

133135
// Assets
134136
const assetIds = getTransactionAssetsId(tx.body.outputs);

apps/browser-extension-wallet/src/views/browser-view/features/staking/components/MultiDelegationStaking.tsx

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { OutsideHandlesProvider, Staking } from '@lace/staking';
2-
import React, { useCallback } from 'react';
2+
import React, { useCallback, useEffect } from 'react';
33
import {
44
useAnalyticsContext,
55
useBackgroundServiceAPIContext,
@@ -17,6 +17,9 @@ import {
1717
MULTIDELEGATION_FIRST_VISIT_LS_KEY,
1818
MULTIDELEGATION_FIRST_VISIT_SINCE_PORTFOLIO_PERSISTENCE_LS_KEY
1919
} from '@utils/constants';
20+
import { ActivityDetail } from '../../activity';
21+
import { Drawer, DrawerNavigation } from '@lace/common';
22+
import { useTranslation } from 'react-i18next';
2023

2124
export const MultiDelegationStaking = (): JSX.Element => {
2225
const { theme } = useTheme();
@@ -38,7 +41,9 @@ export const MultiDelegationStaking = (): JSX.Element => {
3841
fetchNetworkInfo,
3942
networkInfo,
4043
blockchainProvider,
41-
currentChain
44+
currentChain,
45+
activityDetail,
46+
resetActivityState
4247
} = useWalletStore((state) => ({
4348
getKeyAgentType: state.getKeyAgentType,
4449
inMemoryWallet: state.inMemoryWallet,
@@ -50,8 +55,11 @@ export const MultiDelegationStaking = (): JSX.Element => {
5055
fetchNetworkInfo: state.fetchNetworkInfo,
5156
blockchainProvider: state.blockchainProvider,
5257
walletInfo: state.walletInfo,
53-
currentChain: state.currentChain
58+
currentChain: state.currentChain,
59+
activityDetail: state.activityDetail,
60+
resetActivityState: state.resetActivityState
5461
}));
62+
const { t } = useTranslation();
5563
const sendAnalytics = useCallback(() => {
5664
// TODO implement analytics for the new flow
5765
const analytics = {
@@ -66,7 +74,7 @@ export const MultiDelegationStaking = (): JSX.Element => {
6674
name: 'AnalyticsEventNames.Staking.STAKING_MULTI_DELEGATION_BROWSER'
6775
});
6876
}, []);
69-
const { walletActivities } = useWalletActivities({ sendAnalytics });
77+
const { walletActivities, walletActivitiesStatus } = useWalletActivities({ sendAnalytics });
7078
const { fiatCurrency } = useCurrencyStore();
7179
const { executeWithPassword } = useWalletManager();
7280
const [multidelegationFirstVisit, { updateLocalStorage: setMultidelegationFirstVisit }] = useLocalStorage(
@@ -80,6 +88,11 @@ export const MultiDelegationStaking = (): JSX.Element => {
8088
const walletAddress = walletInfo.addresses?.[0].address?.toString();
8189
const analytics = useAnalyticsContext();
8290

91+
// Reset current transaction details and close drawer if network (blockchainProvider) has changed
92+
useEffect(() => {
93+
resetActivityState();
94+
}, [resetActivityState, blockchainProvider]);
95+
8396
return (
8497
<OutsideHandlesProvider
8598
{...{
@@ -106,6 +119,7 @@ export const MultiDelegationStaking = (): JSX.Element => {
106119
walletStoreNetworkInfo: networkInfo,
107120
walletStoreBlockchainProvider: blockchainProvider,
108121
walletStoreWalletActivities: walletActivities,
122+
walletStoreWalletActivitiesStatus: walletActivitiesStatus,
109123
// TODO: LW-7575 make compactNumber reusable and not pass it here.
110124
compactNumber: compactNumberWithUnit,
111125
multidelegationFirstVisit,
@@ -120,6 +134,27 @@ export const MultiDelegationStaking = (): JSX.Element => {
120134
}}
121135
>
122136
<Staking currentChain={currentChain} theme={theme.name} />
137+
{/*
138+
Note: Mounting the browser-extension activity details drawer here is just a workaround.
139+
Ideally, the Drawer/Activity detail should be fully managed within the "Staking" component,
140+
which contains the respective "Activity" section, but that would require moving/refactoring
141+
large chunks of code, ATM tightly coupled with browser-extension state/logic,
142+
to a separate package (core perhaps?).
143+
*/}
144+
<Drawer
145+
visible={!!activityDetail}
146+
onClose={resetActivityState}
147+
navigation={
148+
<DrawerNavigation
149+
title={t('transactions.detail.title')}
150+
onCloseIconClick={() => {
151+
resetActivityState();
152+
}}
153+
/>
154+
}
155+
>
156+
{activityDetail && priceResult && <ActivityDetail price={priceResult} />}
157+
</Drawer>
123158
</OutsideHandlesProvider>
124159
);
125160
};

packages/common/src/analytics/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export enum PostHogAction {
5656
StakingAboutStakingFaqClick = 'staking | about staking | faq | click',
5757
StakingMultiDelegationDedicatedBlogClick = 'staking | multi-delegation | dedicated blog | click',
5858
StakingMultiDelegationGotItClick = 'staking | multi-delegation | got it | click',
59+
StakingActivityClick = 'staking | activity | click',
5960
StakingOverviewClick = 'staking | overview | click',
6061
StakingOverviewCopyAddressClick = 'staking | overview | copy address | click',
6162
StakingOverviewManageClick = 'staking | overview | manage | click',
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { StateStatus, useOutsideHandles } from 'features/outside-handles-provider';
2+
import { getGroupedRewardsActivities } from './helpers/getGroupedRewardsHistory';
3+
import { NoStakingActivity } from './NoStakingActivity';
4+
import { RewardsHistory } from './RewardsHistory';
5+
6+
export const Activity = () => {
7+
const { walletStoreWalletActivitiesStatus: walletActivitiesStatus, walletStoreWalletActivities: walletActivities } =
8+
useOutsideHandles();
9+
const groupedRewardsActivities = getGroupedRewardsActivities(walletActivities);
10+
11+
return (
12+
<>
13+
{walletActivitiesStatus === StateStatus.LOADED && groupedRewardsActivities.length === 0 ? (
14+
<NoStakingActivity />
15+
) : (
16+
<RewardsHistory
17+
walletActivitiesStatus={walletActivitiesStatus}
18+
groupedRewardsActivities={groupedRewardsActivities}
19+
/>
20+
)}
21+
</>
22+
);
23+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { style } from '@lace/ui';
2+
import { theme } from 'features/theme';
3+
4+
export const sadFaceIcon = style({
5+
height: theme.spacing.$112,
6+
width: theme.spacing.$112,
7+
});
8+
9+
export const noActivityText = style({
10+
color: theme.colors.$activityNoActivityTextColor,
11+
fontSize: theme.fontSizes.$14,
12+
fontWeight: theme.fontWeights.$semibold,
13+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import SadFaceIcon from '@lace/core/src/ui/assets/icons/sad-face.component.svg';
2+
import { Flex } from '@lace/ui';
3+
import { Typography } from 'antd';
4+
import { useTranslation } from 'react-i18next';
5+
import * as styles from './NoStakingActivity.css';
6+
7+
export const NoStakingActivity = () => {
8+
const { t } = useTranslation();
9+
return (
10+
<Flex h="$fill" flexDirection="column" alignItems="center" justifyContent="center" gap="$8">
11+
<SadFaceIcon className={styles.sadFaceIcon} />
12+
<Typography.Text className={styles.noActivityText}>
13+
{t('activity.rewardsHistory.noStakingActivityYet')}
14+
</Typography.Text>
15+
</Flex>
16+
);
17+
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { AssetActivityListProps, GroupedAssetActivityList } from '@lace/core';
2+
import { Box, Text } from '@lace/ui';
3+
import { Skeleton } from 'antd';
4+
import { StateStatus } from 'features/outside-handles-provider';
5+
import { useTranslation } from 'react-i18next';
6+
7+
const LACE_APP_ID = 'lace-app';
8+
9+
type RewardsHistoryProps = {
10+
groupedRewardsActivities: AssetActivityListProps[];
11+
walletActivitiesStatus: StateStatus;
12+
};
13+
export const RewardsHistory = ({ groupedRewardsActivities, walletActivitiesStatus }: RewardsHistoryProps) => {
14+
const { t } = useTranslation();
15+
16+
return (
17+
<>
18+
<Box mb="$16">
19+
<Text.SubHeading>{t('activity.rewardsHistory.title')}</Text.SubHeading>
20+
</Box>
21+
<Skeleton loading={walletActivitiesStatus !== StateStatus.LOADED}>
22+
<GroupedAssetActivityList
23+
lists={groupedRewardsActivities}
24+
infiniteScrollProps={{ scrollableTarget: LACE_APP_ID }}
25+
/>
26+
</Skeleton>
27+
</>
28+
);
29+
};

0 commit comments

Comments
 (0)