Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions apps/browser-extension-wallet/.env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ USE_ADA_HANDLE=true
USE_DATA_CHECK=false
USE_POSTHOG_ANALYTICS=true
USE_COMBINED_PASSWORD_NAME_STEP_COMPONENT=false
USE_MULTI_DELEGATION_STAKING_ACTIVITY=false

USE_POSTHOG_ANALYTICS_FOR_OPTED_OUT=false
USE_MATOMO_ANALYTICS_FOR_OPTED_OUT=false
Expand Down
2 changes: 2 additions & 0 deletions apps/browser-extension-wallet/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ USE_POSTHOG_ANALYTICS=true
USE_POSTHOG_ANALYTICS_FOR_OPTED_OUT=false
USE_MATOMO_ANALYTICS_FOR_OPTED_OUT=false
USE_COMBINED_PASSWORD_NAME_STEP_COMPONENT=false
USE_MULTI_DELEGATION_STAKING_ACTIVITY=false

# In App URLs
CATALYST_GOOGLE_PLAY_URL=https://play.google.com/store/apps/details?id=io.iohk.vitvoting
CATALYST_APP_STORE_URL=https://apps.apple.com/fr/app/catalyst-voting/id1517473397?l=en
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const MultiDelegationStakingPopup = (): JSX.Element => {
name: 'AnalyticsEventNames.Staking.STAKING_MULTI_DELEGATION_POPUP'
});
}, []);
const { walletActivities } = useWalletActivities({ sendAnalytics });
const { walletActivities, walletActivitiesStatus } = useWalletActivities({ sendAnalytics });
const { fiatCurrency } = useCurrencyStore();
const { executeWithPassword } = useWalletManager();
const isLoadingNetworkInfo = useWalletStore(networkInfoStatusSelector);
Expand Down Expand Up @@ -125,6 +125,7 @@ export const MultiDelegationStakingPopup = (): JSX.Element => {
walletStoreNetworkInfo: networkInfo,
walletStoreBlockchainProvider: blockchainProvider,
walletStoreWalletActivities: walletActivities,
walletStoreWalletActivitiesStatus: walletActivitiesStatus,
// TODO: LW-7575 make compactNumber reusable and not pass it here.
compactNumber: compactNumberWithUnit,
walletAddress,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,15 @@ const buildGetActivityDetail =
walletInfo
} = get();

set({ fetchingActivityInfo: true });

if (activityDetail.type === 'rewards') {
const { activity, status, type } = activityDetail;
const poolInfos = await getPoolInfos(
activity.rewards.map(({ poolId }) => poolId),
stakePoolProvider
);
set({ fetchingActivityInfo: false });

return {
activity: {
Expand Down Expand Up @@ -128,7 +131,6 @@ const buildGetActivityDetail =
const { activity: tx, status, type, direction } = activityDetail;
const walletAssets = await firstValueFrom(wallet.assetInfo$);
const protocolParameters = await firstValueFrom(wallet.protocolParameters$);
set({ fetchingActivityInfo: true });

// Assets
const assetIds = getTransactionAssetsId(tx.body.outputs);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { OutsideHandlesProvider, Staking } from '@lace/staking';
import React, { useCallback } from 'react';
import React, { useCallback, useEffect } from 'react';
import {
useAnalyticsContext,
useBackgroundServiceAPIContext,
Expand All @@ -17,6 +17,9 @@ import {
MULTIDELEGATION_FIRST_VISIT_LS_KEY,
MULTIDELEGATION_FIRST_VISIT_SINCE_PORTFOLIO_PERSISTENCE_LS_KEY
} from '@utils/constants';
import { ActivityDetail } from '../../activity';
import { Drawer, DrawerNavigation } from '@lace/common';
import { useTranslation } from 'react-i18next';

export const MultiDelegationStaking = (): JSX.Element => {
const { theme } = useTheme();
Expand All @@ -38,7 +41,9 @@ export const MultiDelegationStaking = (): JSX.Element => {
fetchNetworkInfo,
networkInfo,
blockchainProvider,
currentChain
currentChain,
activityDetail,
resetActivityState
} = useWalletStore((state) => ({
getKeyAgentType: state.getKeyAgentType,
inMemoryWallet: state.inMemoryWallet,
Expand All @@ -50,8 +55,11 @@ export const MultiDelegationStaking = (): JSX.Element => {
fetchNetworkInfo: state.fetchNetworkInfo,
blockchainProvider: state.blockchainProvider,
walletInfo: state.walletInfo,
currentChain: state.currentChain
currentChain: state.currentChain,
activityDetail: state.activityDetail,
resetActivityState: state.resetActivityState
}));
const { t } = useTranslation();
const sendAnalytics = useCallback(() => {
// TODO implement analytics for the new flow
const analytics = {
Expand All @@ -66,7 +74,7 @@ export const MultiDelegationStaking = (): JSX.Element => {
name: 'AnalyticsEventNames.Staking.STAKING_MULTI_DELEGATION_BROWSER'
});
}, []);
const { walletActivities } = useWalletActivities({ sendAnalytics });
const { walletActivities, walletActivitiesStatus } = useWalletActivities({ sendAnalytics });
const { fiatCurrency } = useCurrencyStore();
const { executeWithPassword } = useWalletManager();
const [multidelegationFirstVisit, { updateLocalStorage: setMultidelegationFirstVisit }] = useLocalStorage(
Expand All @@ -80,6 +88,11 @@ export const MultiDelegationStaking = (): JSX.Element => {
const walletAddress = walletInfo.addresses?.[0].address?.toString();
const analytics = useAnalyticsContext();

// Reset current transaction details and close drawer if network (blockchainProvider) has changed
useEffect(() => {
resetActivityState();
}, [resetActivityState, blockchainProvider]);

return (
<OutsideHandlesProvider
{...{
Expand All @@ -106,6 +119,7 @@ export const MultiDelegationStaking = (): JSX.Element => {
walletStoreNetworkInfo: networkInfo,
walletStoreBlockchainProvider: blockchainProvider,
walletStoreWalletActivities: walletActivities,
walletStoreWalletActivitiesStatus: walletActivitiesStatus,
// TODO: LW-7575 make compactNumber reusable and not pass it here.
compactNumber: compactNumberWithUnit,
multidelegationFirstVisit,
Expand All @@ -118,6 +132,27 @@ export const MultiDelegationStaking = (): JSX.Element => {
}}
>
<Staking currentChain={currentChain} theme={theme.name} />
{/*
Note: Mounting the browser-extension activity details drawer here is just a workaround.
Ideally, the Drawer/Activity detail should be fully managed within the "Staking" component,
which contains the respective "Activity" section, but that would require moving/refactoring
large chunks of code, ATM tightly coupled with browser-extension state/logic,
to a separate package (core perhaps?).
*/}
<Drawer
visible={!!activityDetail}
onClose={resetActivityState}
navigation={
<DrawerNavigation
title={t('transactions.detail.title')}
onCloseIconClick={() => {
resetActivityState();
}}
/>
}
>
{activityDetail && priceResult && <ActivityDetail price={priceResult} />}
</Drawer>
</OutsideHandlesProvider>
);
};
1 change: 1 addition & 0 deletions packages/common/src/analytics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export enum PostHogAction {
StakingAboutStakingFaqClick = 'staking | about staking | faq | click',
StakingMultiDelegationDedicatedBlogClick = 'staking | multi-delegation | dedicated blog | click',
StakingMultiDelegationGotItClick = 'staking | multi-delegation | got it | click',
StakingActivityClick = 'staking | activity | click',
StakingOverviewClick = 'staking | overview | click',
StakingOverviewCopyAddressClick = 'staking | overview | copy address | click',
StakingOverviewManageClick = 'staking | overview | manage | click',
Expand Down
23 changes: 23 additions & 0 deletions packages/staking/src/features/activity/Activity.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { StateStatus, useOutsideHandles } from 'features/outside-handles-provider';
import { getGroupedRewardsActivities } from './helpers/getGroupedRewardsHistory';
import { NoStakingActivity } from './NoStakingActivity';
import { RewardsHistory } from './RewardsHistory';

export const Activity = () => {
const { walletStoreWalletActivitiesStatus: walletActivitiesStatus, walletStoreWalletActivities: walletActivities } =
useOutsideHandles();
const groupedRewardsActivities = getGroupedRewardsActivities(walletActivities);

return (
<>
{walletActivitiesStatus === StateStatus.LOADED && groupedRewardsActivities.length === 0 ? (
<NoStakingActivity />
) : (
<RewardsHistory
walletActivitiesStatus={walletActivitiesStatus}
groupedRewardsActivities={groupedRewardsActivities}
/>
)}
</>
);
};
13 changes: 13 additions & 0 deletions packages/staking/src/features/activity/NoStakingActivity.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { style } from '@lace/ui';
import { theme } from 'features/theme';

export const sadFaceIcon = style({
height: theme.spacing.$112,
width: theme.spacing.$112,
});

export const noActivityText = style({
color: theme.colors.$activityNoActivityTextColor,
fontSize: theme.fontSizes.$14,
fontWeight: theme.fontWeights.$semibold,
});
17 changes: 17 additions & 0 deletions packages/staking/src/features/activity/NoStakingActivity.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import SadFaceIcon from '@lace/core/src/ui/assets/icons/sad-face.component.svg';
import { Flex } from '@lace/ui';
import { Typography } from 'antd';
import { useTranslation } from 'react-i18next';
import * as styles from './NoStakingActivity.css';

export const NoStakingActivity = () => {
const { t } = useTranslation();
return (
<Flex h="$fill" flexDirection="column" alignItems="center" justifyContent="center" gap="$8">
<SadFaceIcon className={styles.sadFaceIcon} />
<Typography.Text className={styles.noActivityText}>
{t('activity.rewardsHistory.noStakingActivityYet')}
</Typography.Text>
</Flex>
);
};
29 changes: 29 additions & 0 deletions packages/staking/src/features/activity/RewardsHistory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { AssetActivityListProps, GroupedAssetActivityList } from '@lace/core';
import { Box, Text } from '@lace/ui';
import { Skeleton } from 'antd';
import { StateStatus } from 'features/outside-handles-provider';
import { useTranslation } from 'react-i18next';

const LACE_APP_ID = 'lace-app';

type RewardsHistoryProps = {
groupedRewardsActivities: AssetActivityListProps[];
walletActivitiesStatus: StateStatus;
};
export const RewardsHistory = ({ groupedRewardsActivities, walletActivitiesStatus }: RewardsHistoryProps) => {
const { t } = useTranslation();

return (
<>
<Box mb="$16">
<Text.SubHeading>{t('activity.rewardsHistory.title')}</Text.SubHeading>
</Box>
<Skeleton loading={walletActivitiesStatus !== StateStatus.LOADED}>
<GroupedAssetActivityList
lists={groupedRewardsActivities}
infiniteScrollProps={{ scrollableTarget: LACE_APP_ID }}
/>
</Skeleton>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { AssetActivityListProps } from '@lace/core';

export const getGroupedRewardsActivities = (walletActivities: AssetActivityListProps[]) =>
walletActivities
.map((group) => ({
...group,
items: group.items.filter((item) => item.type === 'rewards'),
}))
.filter((group) => group.items.length > 0);
1 change: 1 addition & 0 deletions packages/staking/src/features/activity/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Activity } from './Activity';
3 changes: 3 additions & 0 deletions packages/staking/src/features/i18n/translations/en.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Translations } from '../types';

export const en: Translations = {
'activity.rewardsHistory.noStakingActivityYet': 'No staking activity yet.',
'activity.rewardsHistory.title': 'History',
'browsePools.stakePoolTableBrowser.addPool': 'Add pool',
'browsePools.stakePoolTableBrowser.disabledTooltip': 'Maximum number of pools selected',
'browsePools.stakePoolTableBrowser.emptyMessage': 'No results matching your search',
Expand Down Expand Up @@ -159,6 +161,7 @@ export const en: Translations = {
'portfolioBar.maxPools': '(max {{maxPoolsCount}})',
'portfolioBar.next': 'Next',
'portfolioBar.selectedPools': '{{selectedPoolsCount}} pools selected',
'root.nav.activityTitle': 'Activity',
'root.nav.browsePoolsTitle': 'Browse pools',
'root.nav.overviewTitle': 'Overview',
'root.nav.title': 'Staking Navigation',
Expand Down
7 changes: 7 additions & 0 deletions packages/staking/src/features/i18n/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ type KeysStructure = {
close: '';
};
};
activity: {
rewardsHistory: {
title: '';
noStakingActivityYet: '';
};
};
browsePools: {
stakePoolTableBrowser: {
searchInputPlaceholder: '';
Expand Down Expand Up @@ -241,6 +247,7 @@ type KeysStructure = {
root: {
title: '';
nav: {
activityTitle: '';
browsePoolsTitle: '';
title: '';
overviewTitle: '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export type OutsideHandlesContextValue = {
walletStoreGetKeyAgentType: () => string;
walletStoreInMemoryWallet: Wallet.ObservableWallet;
walletStoreWalletActivities: AssetActivityListProps[];
walletStoreWalletActivitiesStatus: StateStatus;
walletStoreWalletUICardanoCoin: Wallet.CoinId;
walletManagerExecuteWithPassword: <T>(
password: string,
Expand Down
44 changes: 33 additions & 11 deletions packages/staking/src/features/staking/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useOutsideHandles } from '../outside-handles-provider';
import { DelegationFlow, useDelegationPortfolioStore } from '../store';

export enum Page {
activity = 'activity',
overview = 'overview',
browsePools = 'browsePools',
}
Expand All @@ -19,23 +20,33 @@ const isValueAValidSubPage = (value: string): value is Page => Object.values<str
export const Navigation = ({ children }: NavigationProps) => {
const { analytics } = useOutsideHandles();
const { activePage, portfolioMutators } = useDelegationPortfolioStore((store) => ({
activePage: [
DelegationFlow.Overview,
DelegationFlow.CurrentPoolDetails,
DelegationFlow.PortfolioManagement,
].includes(store.activeDelegationFlow)
? Page.overview
: Page.browsePools,
activePage: (() => {
const flowToPage: Record<DelegationFlow, Page> = {
[DelegationFlow.Activity]: Page.activity,
[DelegationFlow.Overview]: Page.overview,
[DelegationFlow.CurrentPoolDetails]: Page.overview,
[DelegationFlow.PortfolioManagement]: Page.overview,
[DelegationFlow.ChangingPreferences]: Page.browsePools,
[DelegationFlow.BrowsePools]: Page.browsePools,
[DelegationFlow.NewPortfolio]: Page.browsePools,
[DelegationFlow.PoolDetails]: Page.browsePools,
};
return flowToPage[store.activeDelegationFlow];
})(),
portfolioMutators: store.mutators,
}));
const { t } = useTranslation();
const onValueChange = (value: string) => {
if (!isValueAValidSubPage(value)) return;
analytics.sendEventToPostHog(
value === Page.overview ? PostHogAction.StakingOverviewClick : PostHogAction.StakingBrowsePoolsClick
);
const pageToEventParams = {
[Page.activity]: ['GoToActivity', PostHogAction.StakingActivityClick] as const,
[Page.overview]: ['GoToOverview', PostHogAction.StakingOverviewClick] as const,
[Page.browsePools]: ['GoToBrowsePools', PostHogAction.StakingBrowsePoolsClick] as const,
};
const [command, posthogEvent] = pageToEventParams[value];
analytics.sendEventToPostHog(posthogEvent);
portfolioMutators.executeCommand({
type: value === Page.overview ? 'GoToOverview' : 'GoToBrowsePools',
type: command,
});
};

Expand All @@ -62,6 +73,17 @@ export const Navigation = ({ children }: NavigationProps) => {
tabIndex={0}
highlightWidth="half"
/>
{process.env.USE_MULTI_DELEGATION_STAKING_ACTIVITY === 'true' ? (
<SubNavigation.Item
name={t('root.nav.activityTitle')}
value={Page.activity}
data-testid="activity-tab"
tabIndex={0}
highlightWidth="half"
/>
) : (
<></>
)}
</SubNavigation.Root>
{children(activePage)}
</>
Expand Down
4 changes: 3 additions & 1 deletion packages/staking/src/features/staking/StakingView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Box, Text } from '@lace/ui';
import { Activity } from 'features/activity';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { BrowsePools } from '../BrowsePools';
Expand Down Expand Up @@ -43,9 +44,10 @@ export const StakingView = () => {
</Box>
<Navigation>
{(activePage) => (
<Box mt="$40">
<Box mt="$40" h="$fill">
{activePage === Page.overview && <Overview />}
{activePage === Page.browsePools && <BrowsePools />}
{activePage === Page.activity && <Activity />}
</Box>
)}
</Navigation>
Expand Down
Loading