diff --git a/apps/browser-extension-wallet/src/api/transformers.ts b/apps/browser-extension-wallet/src/api/transformers.ts index 4a016fd183..33e3858223 100644 --- a/apps/browser-extension-wallet/src/api/transformers.ts +++ b/apps/browser-extension-wallet/src/api/transformers.ts @@ -4,8 +4,8 @@ import { CoinOverview, CardanoStakePool, CardanoTxOut, - TransactionDetail, - CurrencyInfo + CurrencyInfo, + TransactionActivityDetail } from '../types'; import { Wallet } from '@lace/cardano'; import { addEllipsis, getNumberWithUnit } from '@lace/common'; @@ -111,7 +111,7 @@ const isStakePool = (props: CardanoStakePool | Wallet.Cardano.SlotLeader): props /** * format block information */ -export const blockTransformer = (block: Wallet.BlockInfo): TransactionDetail['blocks'] => ({ +export const blockTransformer = (block: Wallet.BlockInfo): TransactionActivityDetail['blocks'] => ({ blockId: block.header.hash.toString(), epoch: block.epoch.toString(), block: block.header.blockNo.toString(), diff --git a/apps/browser-extension-wallet/src/features/activity/components/Activity.tsx b/apps/browser-extension-wallet/src/features/activity/components/Activity.tsx index c666541ab0..570b6cd92c 100644 --- a/apps/browser-extension-wallet/src/features/activity/components/Activity.tsx +++ b/apps/browser-extension-wallet/src/features/activity/components/Activity.tsx @@ -5,7 +5,7 @@ import { StateStatus, useWalletStore } from '@src/stores'; import { useFetchCoinPrice, useRedirection } from '@hooks'; import { Drawer, DrawerNavigation } from '@lace/common'; import { GroupedAssetActivityList } from '@lace/core'; -import { TransactionDetail } from '@src/views/browser-view/features/activity'; +import { ActivityDetail } from '@src/views/browser-view/features/activity'; import styles from './Activity.module.scss'; import { FundWalletBanner } from '@src/views/browser-view/components'; import { walletRoutePaths } from '@routes'; @@ -21,7 +21,7 @@ import { useWalletActivities } from '@hooks/useWalletActivities'; export const Activity = (): React.ReactElement => { const { t } = useTranslation(); const { priceResult } = useFetchCoinPrice(); - const { walletInfo, transactionDetail, resetTransactionState } = useWalletStore(); + const { walletInfo, activityDetail, resetActivityState } = useWalletStore(); const layoutTitle = `${t('browserView.activity.title')}`; const redirectToAssets = useRedirection(walletRoutePaths.assets); const analytics = useAnalyticsContext(); @@ -43,21 +43,21 @@ export const Activity = (): React.ReactElement => { return ( { analytics.sendEventToPostHog(PostHogAction.ActivityActivityDetailXClick); - resetTransactionState(); + resetActivityState(); redirectToAssets(); }} /> } popupView > - {transactionDetail && priceResult && } + {activityDetail && priceResult && }
{hasActivities ? ( diff --git a/apps/browser-extension-wallet/src/lib/translations/en.json b/apps/browser-extension-wallet/src/lib/translations/en.json index c5f4872429..ef8c2ebf42 100644 --- a/apps/browser-extension-wallet/src/lib/translations/en.json +++ b/apps/browser-extension-wallet/src/lib/translations/en.json @@ -27,7 +27,7 @@ } } }, - "transactionDetailBrowser": { + "activityDetails": { "address": "Address", "sent": "Sent", "sending": "Sending", @@ -55,7 +55,8 @@ "from": "From", "to": "To", "multipleAddresses": "Multiple addresses", - "pools": "Pool(s)" + "pools": "Pool(s)", + "epoch": "Epoch" }, "walletNameAndPasswordSetupStep": { "title": "Let's set up your new wallet", diff --git a/apps/browser-extension-wallet/src/stores/createWalletStore.ts b/apps/browser-extension-wallet/src/stores/createWalletStore.ts index 5e8f8c45df..27e6e46ba8 100644 --- a/apps/browser-extension-wallet/src/stores/createWalletStore.ts +++ b/apps/browser-extension-wallet/src/stores/createWalletStore.ts @@ -8,7 +8,7 @@ import { stakePoolSearchSlice, walletInfoSlice, lockSlice, - transactionDetailSlice, + activityDetailSlice, uiSlice, blockchainProviderSlice } from './slices'; @@ -34,7 +34,7 @@ export const createWalletStore = ( ...networkSlice({ set, get }), ...stakePoolSearchSlice({ set, get }), ...lockSlice({ set, get }), - ...transactionDetailSlice({ set, get }), + ...activityDetailSlice({ set, get }), ...assetDetailsSlice({ set, get }) })); }; diff --git a/apps/browser-extension-wallet/src/stores/slices/__tests__/transaction-detail-slice.test.ts b/apps/browser-extension-wallet/src/stores/slices/__tests__/transaction-detail-slice.test.ts index 78b9f087ce..88c16ff1bd 100644 --- a/apps/browser-extension-wallet/src/stores/slices/__tests__/transaction-detail-slice.test.ts +++ b/apps/browser-extension-wallet/src/stores/slices/__tests__/transaction-detail-slice.test.ts @@ -1,50 +1,57 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { renderHook, act } from '@testing-library/react-hooks'; -import { BlockchainProviderSlice, TransactionDetailSlice, WalletInfoSlice } from '../../types'; +import { BlockchainProviderSlice, ActivityDetailSlice, WalletInfoSlice } from '../../types'; import { transactionMock } from '../../../utils/mocks/test-helpers'; -import { transactionDetailSlice } from '../transaction-detail-slice'; +import { activityDetailSlice } from '../activity-detail-slice'; import '@testing-library/jest-dom'; import create, { GetState, SetState } from 'zustand'; import { mockBlockchainProviders } from '@src/utils/mocks/blockchain-providers'; +import { ActivityStatus } from '@lace/core'; -const mockTransactionDetailSlice = ( - set: SetState, - get: GetState -): TransactionDetailSlice => { +const mockActivityDetailSlice = ( + set: SetState, + get: GetState +): ActivityDetailSlice => { get = () => ({ blockchainProvider: mockBlockchainProviders() } as BlockchainProviderSlice & - TransactionDetailSlice & + ActivityDetailSlice & WalletInfoSlice); - return transactionDetailSlice({ set, get }); + return activityDetailSlice({ set, get }); }; describe('Testing createStoreHook slice', () => { test('should create store hook with transaction slices slice', () => { - const useTransactionsStore = create(mockTransactionDetailSlice); + const useTransactionsStore = create(mockActivityDetailSlice); const { result } = renderHook(() => useTransactionsStore()); expect(result).toBeDefined(); }); test('should return transaction state and state handlers', () => { - const useTransactionsStore = create(mockTransactionDetailSlice); + const useTransactionsStore = create(mockActivityDetailSlice); const { result } = renderHook(() => useTransactionsStore()); expect(result.current).toBeDefined(); - expect(result.current.transactionDetail).not.toBeDefined(); - expect(result.current.fetchingTransactionInfo).toBeDefined(); - expect(result.current.getTransactionDetails).toBeDefined(); - expect(result.current.resetTransactionState).toBeDefined(); - expect(result.current.setTransactionDetail).toBeDefined(); + expect(result.current.activityDetail).not.toBeDefined(); + expect(result.current.fetchingActivityInfo).toBeDefined(); + expect(result.current.getActivityDetail).toBeDefined(); + expect(result.current.resetActivityState).toBeDefined(); + expect(result.current.setTransactionActivityDetail).toBeDefined(); + expect(result.current.setRewardsActivityDetail).toBeDefined(); }); test('should set transaction detail', () => { - const useTransactionsStore = create(mockTransactionDetailSlice); + const useTransactionsStore = create(mockActivityDetailSlice); const { result, waitForValueToChange } = renderHook(() => useTransactionsStore()); act(() => { - result.current.setTransactionDetail(transactionMock.tx, transactionMock.direction); + result.current.setTransactionActivityDetail({ + type: 'incoming', + status: ActivityStatus.SUCCESS, + activity: transactionMock.tx, + direction: transactionMock.direction + }); }); - waitForValueToChange(() => result.current.transactionDetail); - expect(result.current.transactionDetail).toBeDefined(); + waitForValueToChange(() => result.current.activityDetail); + expect(result.current.activityDetail).toBeDefined(); }); }); diff --git a/apps/browser-extension-wallet/src/stores/slices/__tests__/wallet-activities-slice.test.ts b/apps/browser-extension-wallet/src/stores/slices/__tests__/wallet-activities-slice.test.ts index c9cd1bad29..51c5a35462 100644 --- a/apps/browser-extension-wallet/src/stores/slices/__tests__/wallet-activities-slice.test.ts +++ b/apps/browser-extension-wallet/src/stores/slices/__tests__/wallet-activities-slice.test.ts @@ -5,7 +5,7 @@ import { StateStatus, AssetDetailsSlice, BlockchainProviderSlice, - TransactionDetailSlice, + ActivityDetailSlice, UISlice, WalletInfoSlice } from '@stores/types'; @@ -21,7 +21,7 @@ const mockActivitiesSlice = ( get: GetState< WalletInfoSlice & WalletActivitiesSlice & - TransactionDetailSlice & + ActivityDetailSlice & AssetDetailsSlice & UISlice & BlockchainProviderSlice @@ -35,7 +35,7 @@ const mockActivitiesSlice = ( walletInfo: mockWalletInfoTestnet } as WalletInfoSlice & WalletActivitiesSlice & - TransactionDetailSlice & + ActivityDetailSlice & AssetDetailsSlice & UISlice & BlockchainProviderSlice); diff --git a/apps/browser-extension-wallet/src/stores/slices/transaction-detail-slice.ts b/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts similarity index 62% rename from apps/browser-extension-wallet/src/stores/slices/transaction-detail-slice.ts rename to apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts index f62cabbcfc..726edac529 100644 --- a/apps/browser-extension-wallet/src/stores/slices/transaction-detail-slice.ts +++ b/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts @@ -1,14 +1,8 @@ /* eslint-disable complexity */ /* eslint-disable unicorn/no-array-reduce */ import isEmpty from 'lodash/isEmpty'; -import { - TransactionDetailSlice, - ZustandHandlers, - BlockchainProviderSlice, - WalletInfoSlice, - SliceCreator -} from '../types'; -import { CardanoTxOut, Transaction, TransactionDetail } from '../../types'; +import { ActivityDetailSlice, ZustandHandlers, BlockchainProviderSlice, WalletInfoSlice, SliceCreator } from '../types'; +import { CardanoTxOut, Transaction, ActivityDetail, TransactionActivityDetail } from '../../types'; import { blockTransformer, inputOutputTransformer } from '../../api/transformers'; import { Wallet } from '@lace/cardano'; import { getTransactionTotalOutput } from '../../utils/get-transaction-total-output'; @@ -16,7 +10,8 @@ import { inspectTxValues } from '@src/utils/tx-inspection'; import { firstValueFrom } from 'rxjs'; import { getAssetsInformation } from '@src/utils/get-assets-information'; import { MAX_POOLS_COUNT } from '@lace/staking'; -import { TransactionType } from '@lace/core'; +import { ActivityStatus, ActivityType } from '@lace/core'; +import { formatDate, formatTime } from '@src/utils/format-date'; /** * validates if the transaction is confirmed @@ -40,11 +35,13 @@ const getTransactionAssetsId = (outputs: CardanoTxOut[]) => { return assetIds; }; -const transactionMetadataTransformer = (metadata: Wallet.Cardano.TxMetadata): TransactionDetail['tx']['metadata'] => +const transactionMetadataTransformer = ( + metadata: Wallet.Cardano.TxMetadata +): TransactionActivityDetail['activity']['metadata'] => [...metadata.entries()].map(([key, value]) => ({ key: key.toString(), value: Wallet.cardanoMetadatumToObj(value) })); const shouldIncludeFee = ( - type: TransactionType, + type: ActivityType, delegationInfo: Wallet.Cardano.StakeDelegationCertificate[] | undefined ) => !( @@ -55,28 +52,83 @@ const shouldIncludeFee = ( (type === 'delegationDeregistration' && !!delegationInfo?.length) ); +const getPoolInfos = async (poolIds: Wallet.Cardano.PoolId[], stakePoolProvider: Wallet.StakePoolProvider) => { + const filters: Wallet.QueryStakePoolsArgs = { + filters: { + identifier: { + _condition: 'or', + values: poolIds.map((poolId) => ({ id: poolId })) + } + }, + pagination: { + startAt: 0, + limit: MAX_POOLS_COUNT + } + }; + const { pageResults: pools } = await stakePoolProvider.queryStakePools(filters); + + return pools; +}; + /** - * fetchs asset information + * fetches asset information */ -const getTransactionDetail = +const buildGetActivityDetail = ({ set, get }: ZustandHandlers< - TransactionDetailSlice & BlockchainProviderSlice & WalletInfoSlice - >): TransactionDetailSlice['getTransactionDetails'] => + ActivityDetailSlice & BlockchainProviderSlice & WalletInfoSlice + >): ActivityDetailSlice['getActivityDetail'] => // eslint-disable-next-line max-statements, sonarjs/cognitive-complexity async ({ coinPrices, fiatCurrency }) => { const { blockchainProvider: { chainHistoryProvider, stakePoolProvider, assetProvider }, inMemoryWallet: wallet, - transactionDetail: { tx, status, direction, type }, + activityDetail, walletInfo } = get(); + if (activityDetail.type === 'rewards') { + const { activity, status, type } = activityDetail; + const poolInfos = await getPoolInfos( + activity.rewards.map(({ poolId }) => poolId), + stakePoolProvider + ); + + return { + activity: { + includedUtcDate: formatDate({ date: activity.spendableDate, format: 'MM/DD/YYYY', type: 'utc' }), + includedUtcTime: `${formatTime({ date: activity.spendableDate, type: 'utc' })} UTC`, + rewards: { + totalAmount: Wallet.util.lovelacesToAdaString( + Wallet.BigIntMath.sum(activity.rewards?.map(({ rewards }) => rewards) || []).toString() + ), + spendableEpoch: activity.spendableEpoch, + rewards: activity.rewards.map((r) => { + const poolInfo = poolInfos.find((p) => p.id === r.poolId); + return { + amount: Wallet.util.lovelacesToAdaString(r.rewards.toString()), + pool: r.poolId + ? { + id: r.poolId, + name: poolInfo?.metadata?.name || '-', + ticker: poolInfo?.metadata?.ticker || '-' + } + : undefined + }; + }) + } + }, + status, + type + }; + } + + const { activity: tx, status, type, direction } = activityDetail; const walletAssets = await firstValueFrom(wallet.assetInfo$); const protocolParameters = await firstValueFrom(wallet.protocolParameters$); - set({ fetchingTransactionInfo: true }); + set({ fetchingActivityInfo: true }); // Assets const assetIds = getTransactionAssetsId(tx.body.outputs); @@ -134,7 +186,7 @@ const getTransactionDetail = (certificate) => certificate.__typename === 'StakeDelegationCertificate' ) as Wallet.Cardano.StakeDelegationCertificate[]; - let transaction: TransactionDetail['tx'] = { + let transaction: ActivityDetail['activity'] = { hash: tx.id.toString(), totalOutput: totalOutputInAda, fee: shouldIncludeFee(type, delegationInfo) ? feeInAda : undefined, @@ -148,19 +200,10 @@ const getTransactionDetail = }; if (type === 'delegation' && delegationInfo) { - const filters: Wallet.QueryStakePoolsArgs = { - filters: { - identifier: { - _condition: 'or', - values: delegationInfo.map((certificate) => ({ id: certificate.poolId })) - } - }, - pagination: { - startAt: 0, - limit: MAX_POOLS_COUNT - } - }; - const { pageResults: pools } = await stakePoolProvider.queryStakePools(filters); + const pools = await getPoolInfos( + delegationInfo.map(({ poolId }) => poolId), + stakePoolProvider + ); if (pools.length === 0) { console.error('Stake pool was not found for delegation tx'); @@ -176,20 +219,23 @@ const getTransactionDetail = } } - set({ fetchingTransactionInfo: false }); - return { tx: transaction, blocks, status, assetAmount, type }; + set({ fetchingActivityInfo: false }); + return { activity: transaction, blocks, status, assetAmount, type }; }; /** * has all transactions search related actions and states */ -export const transactionDetailSlice: SliceCreator< - TransactionDetailSlice & BlockchainProviderSlice & WalletInfoSlice, - TransactionDetailSlice +export const activityDetailSlice: SliceCreator< + ActivityDetailSlice & BlockchainProviderSlice & WalletInfoSlice, + ActivityDetailSlice > = ({ set, get }) => ({ - transactionDetail: undefined, - fetchingTransactionInfo: true, - getTransactionDetails: getTransactionDetail({ set, get }), - setTransactionDetail: (tx, direction, status, type) => set({ transactionDetail: { tx, direction, status, type } }), - resetTransactionState: () => set({ transactionDetail: undefined, fetchingTransactionInfo: false }) + activityDetail: undefined, + fetchingActivityInfo: true, + getActivityDetail: buildGetActivityDetail({ set, get }), + setTransactionActivityDetail: ({ activity, direction, status, type }) => + set({ activityDetail: { activity, direction, status, type } }), + setRewardsActivityDetail: ({ activity }) => + set({ activityDetail: { activity, status: ActivityStatus.SPENDABLE, type: 'rewards' } }), + resetActivityState: () => set({ activityDetail: undefined, fetchingActivityInfo: false }) }); diff --git a/apps/browser-extension-wallet/src/stores/slices/index.ts b/apps/browser-extension-wallet/src/stores/slices/index.ts index 5a62742c75..15e4ec3dd5 100644 --- a/apps/browser-extension-wallet/src/stores/slices/index.ts +++ b/apps/browser-extension-wallet/src/stores/slices/index.ts @@ -3,6 +3,6 @@ export * from './stake-pool-search-slice'; export * from './wallet-activities-slice'; export * from './wallet-info-slice'; export * from './lock-slice'; -export * from './transaction-detail-slice'; +export * from './activity-detail-slice'; export * from './ui-slice'; export * from './blockchain-provider-slice'; diff --git a/apps/browser-extension-wallet/src/stores/slices/wallet-activities-slice.ts b/apps/browser-extension-wallet/src/stores/slices/wallet-activities-slice.ts index 20c9ea541d..f1466663b7 100644 --- a/apps/browser-extension-wallet/src/stores/slices/wallet-activities-slice.ts +++ b/apps/browser-extension-wallet/src/stores/slices/wallet-activities-slice.ts @@ -6,14 +6,22 @@ import groupBy from 'lodash/groupBy'; import { mergeMap, combineLatest, tap, Observable, firstValueFrom } from 'rxjs'; import { map } from 'rxjs/operators'; import { Wallet } from '@lace/cardano'; -import { EraSummary, TxCBOR } from '@cardano-sdk/core'; +import { EraSummary, Reward, TxCBOR, epochSlotsCalc } from '@cardano-sdk/core'; import { pendingTxTransformer, txHistoryTransformer, filterOutputsByTxDirection, - isTxWithAssets + isTxWithAssets, + TransformedActivity, + TransformedTransactionActivity } from '@src/views/browser-view/features/activity/helpers'; -import { AssetActivityItemProps, AssetActivityListProps, ActivityAssetProp, TransactionType } from '@lace/core'; +import { + ActivityAssetProp, + ActivityStatus, + AssetActivityItemProps, + AssetActivityListProps, + TransactionActivityType +} from '@lace/core'; import { CurrencyInfo, TxDirections } from '@src/types'; import { getTxDirection, inspectTxType } from '@src/utils/tx-inspection'; import { assetTransformer } from '@src/utils/assets-transformers'; @@ -22,12 +30,13 @@ import { StateStatus, WalletInfoSlice, AssetDetailsSlice, - TransactionDetailSlice, + ActivityDetailSlice, UISlice, BlockchainProviderSlice, SliceCreator } from '../types'; import { getAssetsInformation } from '@src/utils/get-assets-information'; +import { rewardHistoryTransformer } from '@src/views/browser-view/features/activity/helpers/reward-history-transformer'; export interface FetchWalletActivitiesProps { fiatCurrency: CurrencyInfo; @@ -40,7 +49,7 @@ interface FetchWalletActivitiesPropsWithSetter extends FetchWalletActivitiesProp get: GetState< WalletInfoSlice & WalletActivitiesSlice & - TransactionDetailSlice & + ActivityDetailSlice & AssetDetailsSlice & UISlice & BlockchainProviderSlice @@ -48,9 +57,13 @@ interface FetchWalletActivitiesPropsWithSetter extends FetchWalletActivitiesProp set: SetState; } -export type FetchWalletActivitiesReturn = Observable>; +type ExtendedActivityProps = TransformedActivity & AssetActivityItemProps; +type MappedActivityListProps = Omit & { + items: ExtendedActivityProps[]; +}; +export type FetchWalletActivitiesReturn = Observable>; export type DelegationTransactionType = Extract< - TransactionType, + TransactionActivityType, 'delegation' | 'delegationRegistration' | 'delegationDeregistration' >; @@ -60,11 +73,11 @@ const delegationTransactionTypes: ReadonlySet = new S 'delegationDeregistration' ]); -type DelegationActivityItemProps = Omit & { +type DelegationActivityItemProps = Omit & { type: DelegationTransactionType; }; -const isDelegationActivity = (activity: AssetActivityItemProps): activity is DelegationActivityItemProps => +const isDelegationActivity = (activity: ExtendedActivityProps): activity is DelegationActivityItemProps => delegationTransactionTypes.has(activity.type as DelegationTransactionType); const getDelegationAmount = (activity: DelegationActivityItemProps) => { @@ -98,11 +111,18 @@ const getWalletActivitiesObservable = async ({ walletInfo, walletUI: { cardanoCoin }, inMemoryWallet, - setTransactionDetail, + setTransactionActivityDetail, + setRewardsActivityDetail, assetDetails, blockchainProvider: { assetProvider } } = get(); - const { transactions, eraSummaries$, protocolParameters$, assetInfo$ } = inMemoryWallet; + const { + transactions, + eraSummaries$, + protocolParameters$, + assetInfo$, + delegation: { rewardsHistory$ } + } = inMemoryWallet; const protocolParameters = await firstValueFrom(protocolParameters$); const walletAssets = await firstValueFrom(assetInfo$); const historicalTransactions$ = transactions.history$; @@ -113,50 +133,45 @@ const getWalletActivitiesObservable = async ({ const historicTransactionMapper = ( tx: Wallet.Cardano.HydratedTx, eraSummaries: EraSummary[] - ): AssetActivityItemProps | Array => { + ): Array => { const slotTimeCalc = Wallet.createSlotTimeCalc(eraSummaries); - const time = slotTimeCalc(tx.blockHeader.slot); + const date = slotTimeCalc(tx.blockHeader.slot); const transformedTransaction = txHistoryTransformer({ tx, walletAddresses: addresses, fiatCurrency, fiatPrice: cardanoFiatPrice, - time, + date, protocolParameters, cardanoCoin }); - const extendWithClickHandler = (transformedTx: Omit) => ({ + const extendWithClickHandler = (transformedTx: TransformedTransactionActivity) => ({ ...transformedTx, onClick: () => { if (sendAnalytics) sendAnalytics(); - setTransactionDetail(tx, transformedTx.direction, transformedTx.status, transformedTx.type); + setTransactionActivityDetail({ + activity: tx, + direction: transformedTx.direction, + status: transformedTx.status, + type: transformedTx.type + }); } }); - /* - considering the current SDK logic for automatically withdraw rewards when building a transaction and such behavior has to be transparent for the user, - we will remove the withdrawal from the transaction history as it is implemented today. - Instead, we will show rewards in the transaction history whenever the user receives them. - To make this happen we need to create a new record Rewards and added to the transaction history - */ - if (Array.isArray(transformedTransaction)) { - return transformedTransaction.map((tt) => extendWithClickHandler(tt)); - } - - return extendWithClickHandler(transformedTransaction); + return transformedTransaction.map((tt) => extendWithClickHandler(tt)); }; const pendingTransactionMapper = ( tx: Wallet.TxInFlight, eraSummaries: EraSummary[] - ): AssetActivityItemProps | Array => { - let time; + ): Array => { + let date; try { const slotTimeCalc = Wallet.createSlotTimeCalc(eraSummaries); - time = slotTimeCalc(tx.submittedAt); + date = slotTimeCalc(tx.submittedAt); } catch { - time = new Date(); + date = new Date(); } const transformedTransaction = pendingTxTransformer({ tx, @@ -165,28 +180,57 @@ const getWalletActivitiesObservable = async ({ fiatCurrency, protocolParameters, cardanoCoin, - time + date }); - const extendWithClickHandler = (transformedTx: Omit) => ({ + const extendWithClickHandler = (transformedTx: TransformedTransactionActivity) => ({ ...transformedTx, onClick: () => { if (sendAnalytics) sendAnalytics(); const deserializedTx: Wallet.Cardano.Tx = TxCBOR.deserialize(tx.cbor); - setTransactionDetail( - deserializedTx, - TxDirections.Outgoing, - Wallet.TransactionStatus.PENDING, - transformedTx.type - ); + setTransactionActivityDetail({ + activity: deserializedTx, + direction: TxDirections.Outgoing, + status: ActivityStatus.PENDING, + type: transformedTx.type + }); } }); - if (Array.isArray(transformedTransaction)) { - return transformedTransaction.map((tt) => extendWithClickHandler(tt)); - } + return transformedTransaction.map((tt) => extendWithClickHandler(tt)); + }; + + const epochRewardsMapper = ( + earnedEpoch: Wallet.Cardano.EpochNo, + rewards: Reward[], + eraSummaries: EraSummary[] + ): ExtendedActivityProps => { + const REWARD_SPENDABLE_DELAY_EPOCHS = 2; + const spendableEpoch = (earnedEpoch + REWARD_SPENDABLE_DELAY_EPOCHS) as Wallet.Cardano.EpochNo; + const slotTimeCalc = Wallet.createSlotTimeCalc(eraSummaries); + const rewardSpendableDate = slotTimeCalc(epochSlotsCalc(spendableEpoch, eraSummaries).firstSlot); + + const transformedEpochRewards = rewardHistoryTransformer({ + rewards, + fiatCurrency, + fiatPrice: cardanoFiatPrice, + cardanoCoin, + date: rewardSpendableDate + }); - return extendWithClickHandler(transformedTransaction); + return { + ...transformedEpochRewards, + onClick: () => { + if (sendAnalytics) sendAnalytics(); + setRewardsActivityDetail({ + activity: { + rewards, + spendableEpoch, + spendableDate: rewardSpendableDate + } + }); + } + }; }; const filterTransactionByAssetId = (tx: Wallet.Cardano.HydratedTx[]) => @@ -200,7 +244,7 @@ const getWalletActivitiesObservable = async ({ }); /** - * Sorts and sanitizes historical transactions data + * Sanitizes historical transactions data */ const getHistoricalTransactions = (eraSummaries: EraSummary[]) => historicalTransactions$.pipe( @@ -210,12 +254,6 @@ const getWalletActivitiesObservable = async ({ ? allTransactions : filterTransactionByAssetId(allTransactions) ), - map((allTransactions: Wallet.Cardano.HydratedTx[]) => - allTransactions.sort( - (firstTransaction, secondTransaction) => - secondTransaction.blockHeader.slot.valueOf() - firstTransaction.blockHeader.slot.valueOf() - ) - ), map((allTransactions: Wallet.Cardano.HydratedTx[]) => flattenDeep(allTransactions.map((tx) => historicTransactionMapper(tx, eraSummaries))) ) @@ -231,16 +269,32 @@ const getWalletActivitiesObservable = async ({ ) ); + /** + * Sanitizes historical rewards data + */ + const getRewardsHistory = (eraSummaries: EraSummary[]) => + rewardsHistory$.pipe( + map((allRewards: Wallet.RewardsHistory) => + Object.entries(groupBy(allRewards.all, ({ epoch }) => epoch.toString())) + .map(([epoch, rewards]) => epochRewardsMapper(Number(epoch) as Wallet.Cardano.EpochNo, rewards, eraSummaries)) + .filter((reward) => reward.date.getTime() < Date.now()) + ) + ); + /** * 1. Listens for time settings - * 2. Passes it to historical transactions and pending transactions lists - * 3. Emits both lists combined and sets current state for Zustand + * 2. Passes it to historical transactions, pending transactions and rewards history lists + * 3. Emits the lists combined and sets current state for Zustand */ - return combineLatest([eraSummaries$, pendingTransactions$, historicalTransactions$]).pipe( + return combineLatest([eraSummaries$, pendingTransactions$, historicalTransactions$, rewardsHistory$]).pipe( mergeMap(([eraSummaries]) => - combineLatest([getHistoricalTransactions(eraSummaries), getPendingTransactions(eraSummaries)]) + combineLatest([ + getHistoricalTransactions(eraSummaries), + getPendingTransactions(eraSummaries), + getRewardsHistory(eraSummaries) + ]) ), - map(async ([historicalTransactions, pendingTransactions]) => { + map(async ([historicalTransactions, pendingTransactions, rewards]) => { const confirmedTxs = await historicalTransactions; const pendingTxs = await pendingTransactions; /* After the transaction is confirmed is not being removed from pendingTransactions$, so we have to remove it manually from pending list @@ -249,16 +303,26 @@ const getWalletActivitiesObservable = async ({ const filteredPendingTxs = pendingTxs.filter((pending) => confirmedTxs.some((confirmed) => confirmed?.id !== pending?.id) ); - return [...filteredPendingTxs, ...confirmedTxs]; + return [...filteredPendingTxs, ...confirmedTxs, ...rewards]; }), - map(async (allTransactions: Promise) => groupBy(await allTransactions, 'date')), - map(async (lists) => - Object.entries(await lists).map(([listName, transactionsList]) => ({ - title: listName, - items: transactionsList - })) + map(async (allTransactions) => + (await allTransactions).sort((firstTx, secondTx) => { + // ensure pending txs are always first + if (firstTx.status === ActivityStatus.PENDING && secondTx.status !== ActivityStatus.PENDING) return 1; + if (secondTx.status === ActivityStatus.PENDING && firstTx.status !== ActivityStatus.PENDING) return -1; + // otherwise sort by date + return secondTx.date.getTime() - firstTx.date.getTime(); + }) + ), + map(async (allTransactions) => groupBy(await allTransactions, 'formattedDate')), + map( + async (lists): Promise => + Object.entries(await lists).map(([listName, transactionsList]) => ({ + title: listName, + items: transactionsList + })) ), - tap(async (allActivities: Promise) => { + tap(async (allActivities) => { const activities = await allActivities; const flattenedActivities = flattenDeep(activities.map(({ items }: AssetActivityListProps) => items)); const allAssetsIds = uniq( @@ -276,9 +340,9 @@ const getWalletActivitiesObservable = async ({ } }); - const walletActivities = activities.map((activityList: AssetActivityListProps) => ({ + const walletActivities = activities.map((activityList) => ({ ...activityList, - items: activityList.items.map((activity: AssetActivityItemProps) => ({ + items: activityList.items.map((activity) => ({ ...activity, ...(isDelegationActivity(activity) && { amount: `${getDelegationAmount(activity)} ${cardanoCoin.symbol}`, @@ -328,12 +392,7 @@ const getWalletActivitiesObservable = async ({ * has all wallet activities related actions and states */ export const walletActivitiesSlice: SliceCreator< - WalletInfoSlice & - WalletActivitiesSlice & - TransactionDetailSlice & - AssetDetailsSlice & - UISlice & - BlockchainProviderSlice, + WalletInfoSlice & WalletActivitiesSlice & ActivityDetailSlice & AssetDetailsSlice & UISlice & BlockchainProviderSlice, WalletActivitiesSlice > = ({ set, get }) => ({ getWalletActivitiesObservable: ({ diff --git a/apps/browser-extension-wallet/src/stores/types.ts b/apps/browser-extension-wallet/src/stores/types.ts index 8221bba964..c6894715ea 100644 --- a/apps/browser-extension-wallet/src/stores/types.ts +++ b/apps/browser-extension-wallet/src/stores/types.ts @@ -1,13 +1,19 @@ import { SetState, State, GetState, StoreApi } from 'zustand'; import { Wallet, StakePoolSortOptions } from '@lace/cardano'; -import { AssetActivityListProps, TransactionType } from '@lace/core'; +import { + AssetActivityListProps, + ActivityStatus, + RewardsActivityType, + TransactionActivityType, + ActivityType +} from '@lace/core'; import { PriceResult } from '../hooks'; import { NetworkInformation, WalletInfo, WalletLocked, TxDirection, - TransactionDetail, + ActivityDetail, WalletUI, NetworkConnectionStates, CurrencyInfo @@ -16,6 +22,8 @@ import { FetchWalletActivitiesProps, FetchWalletActivitiesReturn, IBlockchainPro import { IAssetDetails } from '@src/views/browser-view/features/assets/types'; import { TokenInfo } from '@src/utils/get-assets-information'; import { WalletManagerUi } from '@cardano-sdk/web-extension'; +import { Reward } from '@cardano-sdk/core'; +import { EpochNo } from '@cardano-sdk/core/dist/cjs/Cardano'; export enum StateStatus { IDLE = 'idle', @@ -120,25 +128,36 @@ export interface UISlice { setBalancesVisibility: (visible: boolean) => void; } -export interface TransactionDetailSlice { - transactionDetail?: { +export interface ActivityDetailSlice { + activityDetail?: { + type: ActivityType; + status: ActivityStatus; + direction?: TxDirection; + } & ( + | { + type: RewardsActivityType; + status: ActivityStatus.SPENDABLE; + direction?: never; + activity: { spendableEpoch: EpochNo; spendableDate: Date; rewards: Reward[] }; + } + | { + type: TransactionActivityType; + activity: Wallet.Cardano.HydratedTx | Wallet.Cardano.Tx; + direction: TxDirection; + } + ); + fetchingActivityInfo: boolean; + setTransactionActivityDetail: (params: { + activity: Wallet.Cardano.HydratedTx | Wallet.Cardano.Tx; direction: TxDirection; - tx: Wallet.Cardano.HydratedTx | Wallet.Cardano.Tx; - status?: Wallet.TransactionStatus; - type?: TransactionType; - }; - fetchingTransactionInfo: boolean; - setTransactionDetail: ( - tx: Wallet.Cardano.HydratedTx | Wallet.Cardano.Tx, - direction: TxDirection, - status?: Wallet.TransactionStatus, - type?: TransactionType - ) => void; - getTransactionDetails: (params: { - coinPrices: PriceResult; - fiatCurrency: CurrencyInfo; - }) => Promise; - resetTransactionState: () => void; + status: ActivityStatus; + type: TransactionActivityType; + }) => void; + setRewardsActivityDetail: (params: { + activity: { spendableEpoch: EpochNo; spendableDate: Date; rewards: Reward[] }; + }) => void; + getActivityDetail: (params: { coinPrices: PriceResult; fiatCurrency: CurrencyInfo }) => Promise; + resetActivityState: () => void; } export interface AssetDetailsSlice { @@ -159,7 +178,7 @@ export type WalletStore = WalletActivitiesSlice & StakePoolSearchSlice & LockSlice & WalletInfoSlice & - TransactionDetailSlice & + ActivityDetailSlice & AssetDetailsSlice & UISlice & BlockchainProviderSlice; diff --git a/apps/browser-extension-wallet/src/types/activity-detail.ts b/apps/browser-extension-wallet/src/types/activity-detail.ts new file mode 100644 index 0000000000..2339669fe3 --- /dev/null +++ b/apps/browser-extension-wallet/src/types/activity-detail.ts @@ -0,0 +1,74 @@ +import type { + TransactionMetadataProps, + TxOutputInput, + ActivityStatus, + RewardsInfo, + TransactionActivityType, + RewardsActivityType +} from '@lace/core'; + +export enum TxDirections { + Outgoing = 'Outgoing', + Incoming = 'Incoming', + Self = 'Self' +} + +export type TxDirection = keyof typeof TxDirections; + +export type TransactionPool = { + name: string; + ticker: string; + id: string; +}; + +type TransactionActivity = { + hash: string; + includedUtcDate?: string; + includedUtcTime?: string; + totalOutput?: string; + fee?: string; + depositReclaim?: string; + deposit?: string; + addrInputs?: TxOutputInput[]; + addrOutputs?: TxOutputInput[]; + metadata?: TransactionMetadataProps['metadata']; + pools?: TransactionPool[]; +}; + +type RewardsActivity = { + rewards: RewardsInfo; + includedUtcDate: string; + includedUtcTime: string; +}; + +type BlocksInfo = { + isPopup?: boolean; + blockId?: string; + epoch?: string; + block?: string; + slot?: string; + confirmations?: string; + size?: string; + transactions?: string; + utcDate?: string; + utcTime?: string; + nextBlock?: string; + prevBlock?: string; + createdBy?: string; +}; + +export type TransactionActivityDetail = { + type: TransactionActivityType; + status: ActivityStatus; + activity: TransactionActivity; + blocks?: BlocksInfo; + assetAmount?: number; +}; + +export type RewardsActivityDetail = { + type: RewardsActivityType; + status: ActivityStatus.SPENDABLE; + activity: RewardsActivity; +}; + +export type ActivityDetail = TransactionActivityDetail | RewardsActivityDetail; diff --git a/apps/browser-extension-wallet/src/types/index.ts b/apps/browser-extension-wallet/src/types/index.ts index f16cdfb6e3..24679b4876 100644 --- a/apps/browser-extension-wallet/src/types/index.ts +++ b/apps/browser-extension-wallet/src/types/index.ts @@ -4,7 +4,7 @@ export * from './network-information'; export * from './util'; export * from './wallet-balance'; export * from './wallet'; -export * from './tx'; +export * from './activity-detail'; export * from './dappConnector'; export * from './ui'; export * from './side-menu'; diff --git a/apps/browser-extension-wallet/src/types/tx.ts b/apps/browser-extension-wallet/src/types/tx.ts deleted file mode 100644 index 776d21d3fb..0000000000 --- a/apps/browser-extension-wallet/src/types/tx.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { TransactionMetadataProps, TxOutputInput, TransactionType } from '@lace/core'; -import { Wallet } from '@lace/cardano'; - -export enum TxDirections { - Outgoing = 'Outgoing', - Incoming = 'Incoming', - Self = 'Self' -} - -export type TxDirection = keyof typeof TxDirections; - -export type TransactionPool = { - name: string; - ticker: string; - id: string; -}; - -export interface TransactionDetail { - tx: { - hash: string; - includedUtcDate?: string; - includedUtcTime?: string; - totalOutput?: string; - fee?: string; - depositReclaim?: string; - deposit?: string; - addrInputs?: TxOutputInput[]; - addrOutputs?: TxOutputInput[]; - metadata?: TransactionMetadataProps['metadata']; - pools?: TransactionPool[]; - rewards?: string; - }; - blocks?: { - isPopup?: boolean; - blockId?: string; - epoch?: string; - block?: string; - slot?: string; - confirmations?: string; - size?: string; - transactions?: string; - utcDate?: string; - utcTime?: string; - nextBlock?: string; - prevBlock?: string; - createdBy?: string; - }; - status?: Wallet.TransactionStatus; - assetAmount?: number; - type?: TransactionType; -} diff --git a/apps/browser-extension-wallet/src/utils/mocks/store.tsx b/apps/browser-extension-wallet/src/utils/mocks/store.tsx index 1ce4269591..2a689b5c24 100644 --- a/apps/browser-extension-wallet/src/utils/mocks/store.tsx +++ b/apps/browser-extension-wallet/src/utils/mocks/store.tsx @@ -34,20 +34,21 @@ export const walletStoreMock = async ( resetStakePools: jest.fn(), fetchStakePools: jest.fn(), getWalletActivitiesObservable: jest.fn(), - fetchingTransactionInfo: false, - getTransactionDetails: jest.fn(), + fetchingActivityInfo: false, + getActivityDetail: jest.fn(), inMemoryWallet: wallet as Wallet.ObservableWallet, getKeyAgentType: jest.fn(() => Wallet.KeyManagement.KeyAgentType.InMemory), // TODO: mock [LW-5454] cardanoWallet: undefined, isWalletLocked: jest.fn(() => false), networkStateStatus: StateStatus.LOADED, - resetTransactionState: jest.fn(), + resetActivityState: jest.fn(), resetWalletLock: jest.fn(), selectedStakePool: undefined, setCardanoWallet: jest.fn(), setSelectedStakePool: jest.fn(), - setTransactionDetail: jest.fn(), + setRewardsActivityDetail: jest.fn(), + setTransactionActivityDetail: jest.fn(), setWalletLock: jest.fn(), stakePoolSearchResults: { pageResults: [], totalResultCount: 0 }, stakePoolSearchResultsStatus: StateStatus.LOADED, diff --git a/apps/browser-extension-wallet/src/utils/mocks/test-helpers.tsx b/apps/browser-extension-wallet/src/utils/mocks/test-helpers.tsx index 9eea9a8688..2e5c487611 100644 --- a/apps/browser-extension-wallet/src/utils/mocks/test-helpers.tsx +++ b/apps/browser-extension-wallet/src/utils/mocks/test-helpers.tsx @@ -5,7 +5,7 @@ import React, { FunctionComponent } from 'react'; import { Wallet } from '@lace/cardano'; import { SendStoreProvider } from '../../features/send/stores'; import { createSignal } from '@react-rxjs/utils'; -import { Balance, CardanoTxBuild, WalletInfo, TxDirection, TransactionDetail } from '@types'; +import { Balance, CardanoTxBuild, WalletInfo, TxDirection, TransactionActivityDetail } from '@types'; import { DisplayedCoinDetail, IAssetInfo } from '../../features/send/types'; import { APP_MODE_POPUP, cardanoCoin } from '../constants'; import { fakeApiRequest } from './fake-api-request'; @@ -109,7 +109,10 @@ export const mockInMemoryWallet = { slot: 1 } }), - assetInfo$: of([]) + assetInfo$: of([]), + delegation: { + rewardsHistory$: of([]) + } } as unknown as Wallet.ObservableWallet; export const mockWalletUI = { @@ -409,7 +412,7 @@ export const blockMock: Wallet.BlockInfo = { date: new Date(1_638_829_263_730) }; -export const formatBlockMock: TransactionDetail['blocks'] = { +export const formatBlockMock: TransactionActivityDetail['blocks'] = { block: '3114964', blockId: '717ca157f1e696a612af87109ba1f30cd4bb311ded5b504c78a6face463def95', confirmations: '17013', diff --git a/apps/browser-extension-wallet/src/utils/tx-inspection.ts b/apps/browser-extension-wallet/src/utils/tx-inspection.ts index b940dc594b..f9ceba29b8 100644 --- a/apps/browser-extension-wallet/src/utils/tx-inspection.ts +++ b/apps/browser-extension-wallet/src/utils/tx-inspection.ts @@ -9,7 +9,7 @@ import { totalAddressOutputsValueInspector } from '@cardano-sdk/core'; import { Wallet } from '@lace/cardano'; -import { TransactionType } from '@lace/core'; +import { ActivityType, TransactionActivityType } from '@lace/core'; import { TxDirection, TxDirections } from '@src/types'; const hasWalletStakeAddress = ( @@ -18,7 +18,7 @@ const hasWalletStakeAddress = ( ) => withdrawals.some((item) => item.stakeAddress === stakeAddress); interface TxTypeProps { - type: TransactionType; + type: ActivityType; } export const getTxDirection = ({ type }: TxTypeProps): TxDirections => { @@ -47,7 +47,7 @@ export const inspectTxType = ({ }: { walletAddresses: Wallet.KeyManagement.GroupedAddress[]; tx: Wallet.Cardano.HydratedTx; -}): Exclude => { +}): TransactionActivityType => { const { paymentAddresses, rewardAccounts } = walletAddresses.reduce( (acc, curr) => ({ paymentAddresses: [...acc.paymentAddresses, curr.address], diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/ActivityDetail.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/ActivityDetail.tsx new file mode 100644 index 0000000000..cc713a0dc1 --- /dev/null +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/ActivityDetail.tsx @@ -0,0 +1,149 @@ +import React, { ReactElement, useCallback, useEffect, useMemo, useState } from 'react'; +import uniq from 'lodash/uniq'; +import flatMap from 'lodash/flatMap'; +import { Skeleton } from 'antd'; +import { Wallet } from '@lace/cardano'; +import { + AssetActivityListProps, + ActivityStatus, + TxOutputInput, + TxSummary, + ActivityType, + useTranslate, + RewardsDetails +} from '@lace/core'; +import { PriceResult } from '@hooks'; +import { useWalletStore } from '@stores'; +import { ActivityDetail as ActivityDetailType } from '@src/types'; +import { useCurrencyStore } from '@providers'; +import { TransactionDetailsProxy } from './TransactionDetailsProxy'; + +const MAX_SUMMARY_ADDRESSES = 5; + +export type AddressListType = { + id: number; + name: string; + address: string; +}; + +export const getTransactionData = ({ + addrOutputs, + addrInputs, + walletAddresses, + isIncomingTransaction +}: { + addrOutputs: TxOutputInput[]; + addrInputs: TxOutputInput[]; + walletAddresses: string[]; + isIncomingTransaction: boolean; +}): TxSummary[] => { + if (!addrOutputs || !addrInputs || !walletAddresses) { + return []; + } + + // For incomming type of tx the sender addresses will be all addresses available in activityInfo?.tx.addrInputs list (except the current one) + if (isIncomingTransaction) { + const outputData = addrOutputs.filter((input) => walletAddresses.includes(input.addr)); + const addrs = uniq( + flatMap(addrInputs, (input) => (!walletAddresses.includes(input.addr) ? [input.addr] : [])) + ) as string[]; + + return outputData.map((output) => ({ + ...output, + // Show up to 5 addresses below multiple addresses (see LW-4040) + addr: addrs.slice(0, MAX_SUMMARY_ADDRESSES) + })); + } + + // For outgoing/sent type of tx the receiver addresses will be all addresses available in activityInfo?.tx.addrOutputs list (except the current one) + return addrOutputs + .filter((output) => !walletAddresses.includes(output.addr)) + .map((output) => ({ + ...output, + ...(!Array.isArray(output.addr) && { addr: [output.addr] }) + })); +}; + +const getCurrentTransactionStatus = ( + activities: AssetActivityListProps[], + txId: Wallet.Cardano.TransactionId +): ActivityStatus | undefined => { + const todayActivity = activities.find((activity) => activity.title === 'Today'); + const transaction = todayActivity?.items.find((item) => item.id === String(txId)); + return transaction?.status; +}; + +interface ActivityDetailProps { + price: PriceResult; +} + +const getTypeLabel = (type: ActivityType, t: ReturnType['t']) => { + if (type === 'rewards') return t('package.core.activityDetails.rewards'); + if (type === 'delegation') return t('package.core.activityDetails.delegation'); + if (type === 'delegationRegistration') return t('package.core.activityDetails.registration'); + if (type === 'delegationDeregistration') return t('package.core.activityDetails.deregistration'); + if (type === 'incoming') return t('package.core.activityDetails.received'); + return t('package.core.activityDetails.sent'); +}; + +export const ActivityDetail = ({ price }: ActivityDetailProps): ReactElement => { + const { + walletUI: { cardanoCoin } + } = useWalletStore(); + const { t } = useTranslate(); + const { getActivityDetail, activityDetail, fetchingActivityInfo, walletActivities } = useWalletStore(); + const [activityInfo, setActivityInfo] = useState(); + const { fiatCurrency } = useCurrencyStore(); + + const currentTransactionStatus = useMemo( + () => + activityDetail.type !== 'rewards' + ? getCurrentTransactionStatus(walletActivities, activityDetail.activity.id) ?? activityInfo?.status + : activityInfo?.status, + [activityDetail.activity, activityDetail.type, activityInfo?.status, walletActivities] + ); + + const fetchActivityInfo = useCallback(async () => { + const result = await getActivityDetail({ coinPrices: price, fiatCurrency }); + setActivityInfo(result); + }, [getActivityDetail, setActivityInfo, price, fiatCurrency]); + + useEffect(() => { + fetchActivityInfo(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (fetchingActivityInfo || !activityInfo) return ; + + const name = + activityInfo.status === ActivityStatus.PENDING + ? t('package.core.activityDetails.sending') + : getTypeLabel(activityInfo.type, t); + + const amountTransformer = (ada: string) => + `${Wallet.util.convertAdaToFiat({ ada, fiat: price?.cardano?.price })} ${fiatCurrency?.code}`; + + return ( + <> + {activityInfo.type === 'rewards' ? ( + + ) : ( + + )} + + ); +}; diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/ActivityLayout.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/ActivityLayout.tsx index 5193e2c394..619e7fc619 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/ActivityLayout.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/ActivityLayout.tsx @@ -5,7 +5,7 @@ import { GroupedAssetActivityList } from '@lace/core'; import { useFetchCoinPrice } from '../../../../../hooks'; import { StateStatus, useWalletStore } from '../../../../../stores'; import { Drawer, DrawerNavigation, useObservable } from '@lace/common'; -import { TransactionDetail } from './TransactionDetail'; +import { ActivityDetail } from './ActivityDetail'; import { useTranslation } from 'react-i18next'; import { FundWalletBanner, EducationalList, SectionLayout, Layout } from '@src/views/browser-view/components'; import { SectionTitle } from '@components/Layout/SectionTitle'; @@ -25,7 +25,7 @@ import { useWalletActivities } from '@hooks/useWalletActivities'; export const ActivityLayout = (): ReactElement => { const { t } = useTranslation(); const { priceResult } = useFetchCoinPrice(); - const { inMemoryWallet, walletInfo, transactionDetail, resetTransactionState, blockchainProvider } = useWalletStore(); + const { inMemoryWallet, walletInfo, activityDetail, resetActivityState, blockchainProvider } = useWalletStore(); const analytics = useAnalyticsContext(); const sendAnalytics = useCallback(() => { analytics.sendEventToMatomo({ @@ -73,8 +73,8 @@ export const ActivityLayout = (): ReactElement => { // Reset current transaction details and close drawer if network (blockchainProvider) has changed useEffect(() => { - resetTransactionState(); - }, [resetTransactionState, blockchainProvider]); + resetActivityState(); + }, [resetActivityState, blockchainProvider]); const isLoadingFirstTime = isNil(total); return ( @@ -87,19 +87,19 @@ export const ActivityLayout = (): ReactElement => { sideText={activitiesCount ? `(${activitiesCount})` : ''} /> { analytics.sendEventToPostHog(PostHogAction.ActivityActivityDetailXClick); - resetTransactionState(); + resetActivityState(); }} /> } > - {transactionDetail && priceResult && } + {activityDetail && priceResult && } {walletActivities?.length > 0 ? ( diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/TransactionDetail.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/TransactionDetail.tsx deleted file mode 100644 index fda580fcbb..0000000000 --- a/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/TransactionDetail.tsx +++ /dev/null @@ -1,178 +0,0 @@ -/* eslint-disable sonarjs/cognitive-complexity */ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import React, { ReactElement, useCallback, useEffect, useMemo, useState } from 'react'; -import uniq from 'lodash/uniq'; -import flatMap from 'lodash/flatMap'; -import { Skeleton } from 'antd'; -import { config } from '@src/config'; -import { Wallet } from '@lace/cardano'; -import { - AssetActivityListProps, - TransactionDetailBrowser, - TransactionStatus, - TxOutputInput, - TxSummary -} from '@lace/core'; -import { PriceResult } from '@hooks'; -import { useWalletStore } from '@stores'; -import { TransactionDetail as TransactionDetailType } from '@src/types'; -import { useAddressBookContext, withAddressBookContext } from '@src/features/address-book/context'; -import { APP_MODE_POPUP } from '@src/utils/constants'; -import { useAnalyticsContext, useCurrencyStore, useExternalLinkOpener } from '@providers'; -import { PostHogAction } from '@providers/AnalyticsProvider/analyticsTracker'; - -const MAX_SUMMARY_ADDRESSES = 5; - -export type AddressListType = { - id: number; - name: string; - address: string; -}; - -export const getTransactionData = ({ - addrOutputs, - addrInputs, - walletAddresses, - isIncomingTransaction -}: { - addrOutputs: TxOutputInput[]; - addrInputs: TxOutputInput[]; - walletAddresses: string[]; - isIncomingTransaction: boolean; -}): TxSummary[] => { - if (!addrOutputs || !addrInputs || !walletAddresses) { - return []; - } - - // For incomming type of tx the sender addresses will be all addresses available in transactionInfo?.tx.addrInputs list (except the current one) - if (isIncomingTransaction) { - const outputData = addrOutputs.filter((input) => walletAddresses.includes(input.addr)); - const addrs = uniq( - flatMap(addrInputs, (input) => (!walletAddresses.includes(input.addr) ? [input.addr] : [])) - ) as string[]; - - return outputData.map((output) => ({ - ...output, - // Show up to 5 addresses below multiple addresses (see LW-4040) - addr: addrs.slice(0, MAX_SUMMARY_ADDRESSES) - })); - } - - // For outgoing/sent type of tx the receiver addresses will be all addresses available in transactionInfo?.tx.addrOutputs list (except the current one) - return addrOutputs - .filter((output) => !walletAddresses.includes(output.addr)) - .map((output) => ({ - ...output, - ...(!Array.isArray(output.addr) && { addr: [output.addr] }) - })); -}; - -const getCurrentTransactionStatus = ( - activities: AssetActivityListProps[], - txId: Wallet.Cardano.TransactionId -): TransactionStatus => { - const todayActivity = activities.find((activity) => activity.title === 'Today'); - const transaction = todayActivity?.items.find((item) => item.id === String(txId)); - return transaction?.status; -}; - -interface TransactionDetailProps { - price: PriceResult; -} - -export const TransactionDetail = withAddressBookContext(({ price }): ReactElement => { - const { - walletInfo, - walletUI: { cardanoCoin, appMode }, - environmentName - } = useWalletStore(); - const isPopupView = appMode === APP_MODE_POPUP; - const { getTransactionDetails, transactionDetail, fetchingTransactionInfo, walletActivities } = useWalletStore(); - const [transactionInfo, setTransactionInfo] = useState(); - const { fiatCurrency } = useCurrencyStore(); - const { list: addressList } = useAddressBookContext(); - const { CEXPLORER_BASE_URL, CEXPLORER_URL_PATHS } = config(); - const openExternalLink = useExternalLinkOpener(); - const analytics = useAnalyticsContext(); - - const explorerBaseUrl = useMemo( - () => `${CEXPLORER_BASE_URL[environmentName]}/${CEXPLORER_URL_PATHS.Tx}`, - [CEXPLORER_BASE_URL, CEXPLORER_URL_PATHS.Tx, environmentName] - ); - - const currentTransactionStatus = useMemo( - () => getCurrentTransactionStatus(walletActivities, transactionDetail.tx.id) || transactionInfo?.status, - [transactionDetail.tx.id, transactionInfo?.status, walletActivities] - ); - - const fetchTransactionInfo = useCallback(async () => { - const result = await getTransactionDetails({ coinPrices: price, fiatCurrency }); - setTransactionInfo(result); - }, [getTransactionDetails, setTransactionInfo, price, fiatCurrency]); - - useEffect(() => { - fetchTransactionInfo(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const addressToNameMap = useMemo( - () => new Map(addressList?.map((item: AddressListType) => [item.address, item.name])), - [addressList] - ); - - const isIncomingTransaction = transactionDetail.direction === 'Incoming'; - const { addrOutputs, addrInputs } = transactionInfo?.tx || {}; - const txSummary = useMemo( - () => - getTransactionData({ - addrOutputs, - addrInputs, - walletAddresses: walletInfo.addresses.map((addr) => addr.address.toString()), - isIncomingTransaction - }), - [isIncomingTransaction, addrOutputs, addrInputs, walletInfo.addresses] - ); - - if (fetchingTransactionInfo || !transactionInfo) return ; - - const getHeaderDescription = () => { - if (transactionInfo.type === 'rewards') return ''; - if (transactionInfo.type === 'delegation') return '1 token'; - return ` (${transactionInfo?.assetAmount})`; - }; - - const handleOpenExternalLink = () => { - analytics.sendEventToPostHog(PostHogAction.ActivityActivityDetailTransactionHashClick); - const externalLink = `${explorerBaseUrl}/${transactionInfo.tx.hash}`; - externalLink && currentTransactionStatus === 'success' && openExternalLink(externalLink); - }; - - return ( - - `${Wallet.util.convertAdaToFiat({ ada, fiat: price?.cardano?.price })} ${fiatCurrency?.code}` - } - headerDescription={getHeaderDescription() || cardanoCoin.symbol} - txSummary={txSummary} - addressToNameMap={addressToNameMap} - coinSymbol={cardanoCoin.symbol} - rewards={transactionInfo.tx?.rewards} - type={transactionInfo?.type} - isPopupView={isPopupView} - openExternalLink={handleOpenExternalLink} - sendAnalyticsInputs={() => analytics.sendEventToPostHog(PostHogAction.ActivityActivityDetailInputsClick)} - sendAnalyticsOutputs={() => analytics.sendEventToPostHog(PostHogAction.ActivityActivityDetailOutputsClick)} - /> - ); -}); diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/TransactionDetailsProxy.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/TransactionDetailsProxy.tsx new file mode 100644 index 0000000000..f96efee301 --- /dev/null +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/TransactionDetailsProxy.tsx @@ -0,0 +1,102 @@ +import React, { ReactElement, useMemo } from 'react'; +import { ActivityStatus, TransactionDetails } from '@lace/core'; +import { AddressListType, getTransactionData } from './ActivityDetail'; +import { useWalletStore } from '@src/stores'; +import { useAnalyticsContext, useExternalLinkOpener } from '@providers'; +import { useAddressBookContext, withAddressBookContext } from '@src/features/address-book/context'; +import type { TransactionActivityDetail, TxDirection } from '@src/types'; +import { APP_MODE_POPUP } from '@src/utils/constants'; +import { config } from '@src/config'; +import { PostHogAction } from '@providers/AnalyticsProvider/analyticsTracker'; + +type TransactionDetailsProxyProps = { + name: string; + activityInfo: TransactionActivityDetail; + direction: TxDirection; + status: ActivityStatus; + amountTransformer: (amount: string) => string; +}; +export const TransactionDetailsProxy = withAddressBookContext( + ({ name, activityInfo, direction, status, amountTransformer }: TransactionDetailsProxyProps): ReactElement => { + const analytics = useAnalyticsContext(); + const { + walletInfo, + environmentName, + walletUI: { cardanoCoin, appMode } + } = useWalletStore(); + const isPopupView = appMode === APP_MODE_POPUP; + const openExternalLink = useExternalLinkOpener(); + const { list: addressList } = useAddressBookContext(); + + const { CEXPLORER_BASE_URL, CEXPLORER_URL_PATHS } = config(); + const explorerBaseUrl = useMemo( + () => `${CEXPLORER_BASE_URL[environmentName]}/${CEXPLORER_URL_PATHS.Tx}`, + [CEXPLORER_BASE_URL, CEXPLORER_URL_PATHS.Tx, environmentName] + ); + const getHeaderDescription = () => { + if (activityInfo.type === 'delegation') return '1 token'; + return ` (${activityInfo?.assetAmount})`; + }; + const isIncomingTransaction = direction === 'Incoming'; + const { + addrOutputs, + addrInputs, + hash, + includedUtcDate, + includedUtcTime, + fee, + pools, + deposit, + depositReclaim, + metadata + } = activityInfo.activity; + const txSummary = useMemo( + () => + getTransactionData({ + addrOutputs, + addrInputs, + walletAddresses: walletInfo.addresses.map((addr) => addr.address.toString()), + isIncomingTransaction + }), + [isIncomingTransaction, addrOutputs, addrInputs, walletInfo.addresses] + ); + + const handleOpenExternalLink = () => { + analytics.sendEventToPostHog(PostHogAction.ActivityActivityDetailTransactionHashClick); + const externalLink = `${explorerBaseUrl}/${hash}`; + externalLink && status === 'success' && openExternalLink(externalLink); + }; + + const addressToNameMap = useMemo( + () => new Map(addressList?.map((item: AddressListType) => [item.address, item.name])), + [addressList] + ); + + return ( + // eslint-disable-next-line react/jsx-pascal-case + analytics.sendEventToPostHog(PostHogAction.ActivityActivityDetailInputsClick)} + sendAnalyticsOutputs={() => analytics.sendEventToPostHog(PostHogAction.ActivityActivityDetailOutputsClick)} + /> + ); + } +); diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/__tests__/TransactionDetail.test.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/__tests__/ActivityDetail.test.tsx similarity index 96% rename from apps/browser-extension-wallet/src/views/browser-view/features/activity/components/__tests__/TransactionDetail.test.tsx rename to apps/browser-extension-wallet/src/views/browser-view/features/activity/components/__tests__/ActivityDetail.test.tsx index edd6d5ef59..0b05f37f93 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/__tests__/TransactionDetail.test.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/__tests__/ActivityDetail.test.tsx @@ -1,5 +1,5 @@ import '@testing-library/jest-dom'; -import { getTransactionData } from '../TransactionDetail'; +import { getTransactionData } from '../ActivityDetail'; import { incomingTransactionOutput, missingDataTransactionOutput, diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/index.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/index.tsx index 3fefcef142..a1e9cab57e 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/index.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/index.tsx @@ -1,2 +1,2 @@ export * from './ActivityLayout'; -export * from './TransactionDetail'; +export * from './ActivityDetail'; diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/__tests__/pending-tx-transformer.test.ts b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/__tests__/pending-tx-transformer.test.ts index ae3023817c..5ca67babdb 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/__tests__/pending-tx-transformer.test.ts +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/__tests__/pending-tx-transformer.test.ts @@ -80,7 +80,7 @@ describe('Testing tx transformers utils', () => { }; test('should return parsed pending tx', async () => { mockLovelacesToAdaString.mockImplementation(actualLovelacesToAdaString); - const time = new Date(); + const date = new Date(); const result = pendingTxTransformer({ tx: { ...pendingTx, cbor: TxCBOR.serialize(pendingTx) }, walletAddresses: [ @@ -98,13 +98,12 @@ describe('Testing tx transformers utils', () => { fiatPrice: 1, protocolParameters: { poolDeposit: 3, stakeKeyDeposit: 2 } as Wallet.ProtocolParameters, cardanoCoin, - time + date }); expect(result).toStrictEqual([ { type: 'outgoing', status: 'sending', - date: 'Sending', deposit: undefined, depositReclaim: undefined, direction: 'Outgoing', @@ -119,8 +118,10 @@ describe('Testing tx transformers utils', () => { } ], assetsNumber: 2, - timestamp: formatTime({ - date: time, + date, + formattedDate: 'Sending', + formattedTimestamp: formatTime({ + date, format: DEFAULT_TIME_FORMAT, type: 'local' }) diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/__tests__/tx-history-transformer.test.ts b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/__tests__/tx-history-transformer.test.ts index 7607198672..41f8185555 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/__tests__/tx-history-transformer.test.ts +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/__tests__/tx-history-transformer.test.ts @@ -100,7 +100,7 @@ describe('Testing txHistoryTransformer function', () => { ) } ] as Wallet.KeyManagement.GroupedAddress[], - time: date, + date, fiatCurrency: { code: 'USD', symbol: '$' @@ -127,7 +127,7 @@ describe('Testing txHistoryTransformer function', () => { ) } ] as Wallet.KeyManagement.GroupedAddress[], - time: date, + date, fiatCurrency: { code: 'USD', symbol: '$' @@ -164,7 +164,7 @@ describe('Testing txHistoryTransformer function', () => { ) } ] as Wallet.KeyManagement.GroupedAddress[], - time: date, + date, fiatCurrency: { code: 'USD', symbol: '$' @@ -187,12 +187,11 @@ describe('Testing txHistoryTransformer function', () => { walletAddresses: props.walletAddresses, fiatCurrency: props.fiatCurrency, fiatPrice: props.fiatPrice, - time: props.time, + date: props.date, protocolParameters: props.protocolParameters, cardanoCoin: props.cardanoCoin, status: Wallet.TransactionStatus.SUCCESS, - direction, - date: '01 February 2022' + direction }); expect(result.length).toBe(1); expect(result[0].status).toBe('success'); diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/common-tx-transformer.ts b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/common-tx-transformer.ts index 59f09077d2..fecd9d0c19 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/common-tx-transformer.ts +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/common-tx-transformer.ts @@ -2,8 +2,11 @@ import BigNumber from 'bignumber.js'; import { Wallet } from '@lace/cardano'; import { CurrencyInfo, TxDirections } from '@types'; import { inspectTxValues, inspectTxType } from '@src/utils/tx-inspection'; -import { formatTime } from '@src/utils/format-date'; -import type { TransformedTx } from './types'; +import { formatDate, formatTime } from '@src/utils/format-date'; +import type { TransformedActivity, TransformedTransactionActivity } from './types'; +import { ActivityStatus } from '@lace/core'; +import capitalize from 'lodash/capitalize'; +import dayjs from 'dayjs'; export interface TxTransformerInput { tx: Wallet.TxInFlight | Wallet.Cardano.HydratedTx; @@ -12,10 +15,9 @@ export interface TxTransformerInput { fiatPrice?: number; protocolParameters: Wallet.ProtocolParameters; cardanoCoin: Wallet.CoinId; - time: Date; + date: Date; direction?: TxDirections; status?: Wallet.TransactionStatus; - date?: string; } export const getFormattedFiatAmount = ({ @@ -33,7 +35,7 @@ export const getFormattedFiatAmount = ({ return fiatAmount ? `${fiatAmount} ${fiatCurrency.code}` : '-'; }; -const splitDelegationTx = (tx: TransformedTx): TransformedTx[] => { +const splitDelegationTx = (tx: TransformedActivity): TransformedTransactionActivity[] => { if (tx.deposit) { return [ { @@ -71,6 +73,15 @@ const splitDelegationTx = (tx: TransformedTx): TransformedTx[] => { return []; }; +const transformTransactionStatus = (status: Wallet.TransactionStatus): ActivityStatus => { + const statuses = { + [Wallet.TransactionStatus.PENDING]: ActivityStatus.PENDING, + [Wallet.TransactionStatus.ERROR]: ActivityStatus.ERROR, + [Wallet.TransactionStatus.SUCCESS]: ActivityStatus.SUCCESS, + [Wallet.TransactionStatus.SPENDABLE]: ActivityStatus.SPENDABLE + }; + return statuses[status]; +}; /** Simplifies the transaction object to be used in the activity list @@ -93,11 +104,10 @@ export const txTransformer = ({ fiatPrice, protocolParameters, cardanoCoin, - time, date, direction, status -}: TxTransformerInput): TransformedTx[] => { +}: TxTransformerInput): TransformedTransactionActivity[] => { const implicitCoin = Wallet.Cardano.util.computeImplicitCoin(protocolParameters, tx.body); const deposit = implicitCoin.deposit ? Wallet.util.lovelacesToAdaString(implicitCoin.deposit.toString()) : undefined; const depositReclaimValue = Wallet.util.calculateDepositReclaim(implicitCoin); @@ -110,8 +120,11 @@ export const txTransformer = ({ direction }); const outputAmount = new BigNumber(coins.toString()); - const timestamp = formatTime({ - date: time, + const formattedDate = dayjs().isSame(date, 'day') + ? 'Today' + : formatDate({ date, format: 'DD MMMM YYYY', type: 'local' }); + const formattedTimestamp = formatTime({ + date, type: 'local' }); @@ -121,18 +134,20 @@ export const txTransformer = ({ .sort((a, b) => Number(b.val) - Number(a.val)) : []; - const baseTransformedTx = { + const baseTransformedActivity = { id: tx.id.toString(), deposit, depositReclaim, fee: Wallet.util.lovelacesToAdaString(tx.body.fee.toString()), - status, + status: transformTransactionStatus(status), amount: Wallet.util.getFormattedAmount({ amount: outputAmount.toString(), cardanoCoin }), fiatAmount: getFormattedFiatAmount({ amount: outputAmount, fiatCurrency, fiatPrice }), assets: assetsEntries, assetsNumber: (assets?.size ?? 0) + 1, date, - timestamp + formattedDate: + status === Wallet.TransactionStatus.PENDING ? capitalize(Wallet.TransactionStatus.PENDING) : formattedDate, + formattedTimestamp }; // Note that TxInFlight at type level does not expose its inputs with address, @@ -142,12 +157,12 @@ export const txTransformer = ({ const type = inspectTxType({ walletAddresses, tx: tx as unknown as Wallet.Cardano.HydratedTx }); if (type === 'delegation') { - return splitDelegationTx(baseTransformedTx); + return splitDelegationTx(baseTransformedActivity); } return [ { - ...baseTransformedTx, + ...baseTransformedActivity, type, direction } diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/pending-tx-transformer.ts b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/pending-tx-transformer.ts index 7b985cad87..ba811a87dc 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/pending-tx-transformer.ts +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/pending-tx-transformer.ts @@ -1,7 +1,6 @@ import { TxTransformerInput, txTransformer } from './common-tx-transformer'; import { Wallet } from '@lace/cardano'; -import type { TransformedTx } from './types'; -import capitalize from 'lodash/capitalize'; +import type { TransformedTransactionActivity } from './types'; import { TxDirections } from '@types'; interface TxHistoryTransformerInput extends Omit { @@ -15,8 +14,8 @@ export const pendingTxTransformer = ({ fiatPrice, protocolParameters, cardanoCoin, - time -}: TxHistoryTransformerInput): TransformedTx[] => + date +}: TxHistoryTransformerInput): TransformedTransactionActivity[] => txTransformer({ tx, walletAddresses, @@ -24,8 +23,7 @@ export const pendingTxTransformer = ({ fiatPrice, protocolParameters, cardanoCoin, - time, + date, status: Wallet.TransactionStatus.PENDING, - direction: TxDirections.Outgoing, - date: capitalize(Wallet.TransactionStatus.PENDING) + direction: TxDirections.Outgoing }); diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/reward-history-transformer.ts b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/reward-history-transformer.ts new file mode 100644 index 0000000000..a08f6c785b --- /dev/null +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/reward-history-transformer.ts @@ -0,0 +1,51 @@ +import { Wallet } from '@lace/cardano'; +import { getFormattedFiatAmount } from './common-tx-transformer'; +import type { TransformedRewardsActivity } from './types'; +import dayjs from 'dayjs'; +import { formatDate, formatTime } from '@src/utils/format-date'; +import BigNumber from 'bignumber.js'; +import type { CurrencyInfo } from '@src/types'; +import type { Reward } from '@cardano-sdk/core'; +import { ActivityStatus } from '@lace/core'; + +interface RewardHistoryTransformerInput { + rewards: Reward[]; // TODO this supposes rewards grouped by epoch which is a bit fragile + fiatCurrency: CurrencyInfo; + fiatPrice: number; + date: Date; + cardanoCoin: Wallet.CoinId; +} + +export const rewardHistoryTransformer = ({ + rewards, + fiatCurrency, + fiatPrice, + date, + cardanoCoin +}: RewardHistoryTransformerInput): TransformedRewardsActivity => { + const formattedTimestamp = formatTime({ + date, + type: 'local' + }); + const formattedDate = dayjs().isSame(date, 'day') + ? 'Today' + : formatDate({ date, format: 'DD MMMM YYYY', type: 'local' }); + + const totalRewardsAmount = Wallet.BigIntMath.sum(rewards.map(({ rewards: _rewards }) => _rewards)); + + return { + type: 'rewards', + direction: 'Incoming', + amount: Wallet.util.getFormattedAmount({ amount: totalRewardsAmount.toString(), cardanoCoin }), + fiatAmount: getFormattedFiatAmount({ + amount: new BigNumber(totalRewardsAmount.toString()), + fiatCurrency, + fiatPrice + }), + status: ActivityStatus.SPENDABLE, + assets: [], + date, + formattedTimestamp, + formattedDate + }; +}; diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/tx-history-transformer.ts b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/tx-history-transformer.ts index 24192fc12c..aa63380042 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/tx-history-transformer.ts +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/tx-history-transformer.ts @@ -1,9 +1,7 @@ import { Wallet } from '@lace/cardano'; -import dayjs from 'dayjs'; -import { formatDate } from '@src/utils/format-date'; import { getTxDirection, inspectTxType } from '@src/utils/tx-inspection'; import { txTransformer, TxTransformerInput } from './common-tx-transformer'; -import type { TransformedTx } from './types'; +import type { TransformedTransactionActivity } from './types'; interface TxHistoryTransformerInput extends Omit { tx: Wallet.Cardano.HydratedTx; @@ -14,10 +12,10 @@ export const txHistoryTransformer = ({ walletAddresses, fiatCurrency, fiatPrice, - time, + date, protocolParameters, cardanoCoin -}: TxHistoryTransformerInput): TransformedTx[] => { +}: TxHistoryTransformerInput): TransformedTransactionActivity[] => { const type = inspectTxType({ walletAddresses, tx }); const direction = getTxDirection({ type }); @@ -26,11 +24,10 @@ export const txHistoryTransformer = ({ walletAddresses, fiatCurrency, fiatPrice, - time, + date, protocolParameters, cardanoCoin, status: Wallet.TransactionStatus.SUCCESS, - direction, - date: dayjs().isSame(time, 'day') ? 'Today' : formatDate({ date: time, format: 'DD MMMM YYYY', type: 'local' }) + direction }); }; diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/types.ts b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/types.ts index ce438b040c..6a08ada9ef 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/types.ts +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/types.ts @@ -1,3 +1,57 @@ -import type { AssetActivityItemProps } from '@lace/core'; +import type { + ActivityAssetProp, + ActivityStatus, + ActivityType, + RewardsActivityType, + TransactionActivityType +} from '@lace/core'; -export type TransformedTx = Omit; +export type TransformedActivity = { + id?: string; + fee?: string; + deposit?: string; // e.g. stake registrations + depositReclaim?: string; // e.g. stake de-registrations + /** + * Amount formated with symbol (e.g. 50 ADA) + */ + amount: string; + /** + * Date of the activity + */ + date: Date; + /** + * Amount in Fiat currency (e.g. 125$) + */ + fiatAmount: string; + /** + * Activity status: `sending` | `success` | 'error + */ + status?: ActivityStatus; + /** + * Activity or asset custom icon + */ + customIcon?: string; + /** + * Activity type + */ + type?: ActivityType; + /** + * Number of assets (default: 1) + */ + assetsNumber?: number; + formattedDate: string; + formattedTimestamp: string; + /** + * Direction: 'Incoming' | 'Outgoing' | 'Self' + * TODO: Create a separate package for common types across apps/packages + */ + direction?: 'Incoming' | 'Outgoing' | 'Self'; + /** + * assets details + */ + assets?: ActivityAssetProp[]; +}; + +export type TransformedTransactionActivity = TransformedActivity & { type: TransactionActivityType }; + +export type TransformedRewardsActivity = TransformedActivity & { type: RewardsActivityType }; diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetTransactionDetails/AssetTransactionDetails.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetActivityDetails/AssetActivityDetails.tsx similarity index 69% rename from apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetTransactionDetails/AssetTransactionDetails.tsx rename to apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetActivityDetails/AssetActivityDetails.tsx index d870133ea3..6486b8e6bb 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetTransactionDetails/AssetTransactionDetails.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetActivityDetails/AssetActivityDetails.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { Drawer, DrawerNavigation } from '@lace/common'; -import { TransactionDetail } from '@views/browser/features/activity'; +import { ActivityDetail } from '@views/browser/features/activity'; import { APP_MODE_POPUP, AppMode } from '@src/utils/constants'; import { useWalletStore } from '@src/stores'; import { useFetchCoinPrice } from '@hooks/useFetchCoinPrice'; -export interface AssetTransactionDetailsProps { +export interface AssetActivityDetailsProps { afterVisibleChange: (visible: boolean) => void; appMode: AppMode; onBack: () => void; @@ -13,15 +13,15 @@ export interface AssetTransactionDetailsProps { isVisible?: boolean; } -export const AssetTransactionDetails = ({ +export const AssetActivityDetails = ({ afterVisibleChange, appMode, onBack, onClose, isVisible -}: AssetTransactionDetailsProps): React.ReactElement => { +}: AssetActivityDetailsProps): React.ReactElement => { const { priceResult } = useFetchCoinPrice(); - const { transactionDetail } = useWalletStore(); + const { activityDetail } = useWalletStore(); return ( } popupView={appMode === APP_MODE_POPUP} > - {transactionDetail && priceResult && } + {activityDetail && priceResult && } ); }; diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetTransactionDetails/__tests__/AssetTransactionDetails.test.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetActivityDetails/__tests__/AssetTransactionDetails.test.tsx similarity index 87% rename from apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetTransactionDetails/__tests__/AssetTransactionDetails.test.tsx rename to apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetActivityDetails/__tests__/AssetTransactionDetails.test.tsx index 8cd46ec2c2..d74a7d9e6d 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetTransactionDetails/__tests__/AssetTransactionDetails.test.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/features/assets/components/AssetActivityDetails/__tests__/AssetTransactionDetails.test.tsx @@ -1,10 +1,10 @@ const mockUseFetchCoinPrice = jest.fn().mockReturnValue({ priceResult: { cardano: { price: 2 }, tokens: new Map() } }); -const mockUseWalletStore = jest.fn().mockReturnValue({ transactionDetail: {} } as Stores.WalletStore); +const mockUseWalletStore = jest.fn().mockReturnValue({ activityDetail: {} } as Stores.WalletStore); /* eslint-disable import/imports-first */ import React from 'react'; import '@testing-library/jest-dom'; import { render } from '@testing-library/react'; -import { AssetTransactionDetails } from '../AssetTransactionDetails'; +import { AssetActivityDetails } from '../AssetActivityDetails'; import { APP_MODE_BROWSER } from '@src/utils/constants'; import * as UseFetchCoinPrice from '@hooks/useFetchCoinPrice'; import * as Stores from '@stores'; @@ -12,7 +12,7 @@ import * as ActivityComponents from '@views/browser/features/activity'; jest.mock('@views/browser/features/activity', (): typeof ActivityComponents => ({ ...jest.requireActual('@views/browser/features/activity'), - TransactionDetail: () =>
+ ActivityDetail: () =>
})); jest.mock('@hooks/useFetchCoinPrice', (): typeof UseFetchCoinPrice => ({ ...jest.requireActual('@hooks/useFetchCoinPrice'), @@ -23,14 +23,14 @@ jest.mock('@stores', (): typeof Stores => ({ useWalletStore: mockUseWalletStore })); -describe('AssetTransactionDetails', () => { +describe('AssetActivityDetails', () => { const afterVisibleChangeMock = jest.fn(); const onBackMock = jest.fn(); const onCloseMock = jest.fn(); test('renders transaction detail drawer if visible and price result and transaction detail are defined', () => { const { queryByTestId } = render( - { mockUseFetchCoinPrice.mockReturnValueOnce({}); const { queryByTestId } = render( - { mockUseWalletStore.mockReturnValueOnce({}); const { queryByTestId } = render( - { test('does not render the transaction detail drawer if not visible', () => { const { queryByTestId } = render( - { balanceInFiat="1000 USD" fiatPriceVariation="0" activityList={[ - { amount: '100', fiatAmount: '450' }, - { amount: '200', fiatAmount: '400' } + { + amount: '100', + fiatAmount: '450', + formattedTimestamp: 'Timestamp' + }, + { + amount: '200', + fiatAmount: '400', + formattedTimestamp: 'Timestamp' + } ]} /> ); @@ -75,8 +83,16 @@ describe('AssetDetails', () => { balanceInFiat="1000 USD" fiatPriceVariation="0" activityList={[ - { amount: '100', fiatAmount: '450' }, - { amount: '200', fiatAmount: '400' } + { + amount: '100', + fiatAmount: '450', + formattedTimestamp: 'Timestamp' + }, + { + amount: '200', + fiatAmount: '400', + formattedTimestamp: 'Timestamp' + } ]} activityListStatus={StateStatus.LOADING} /> @@ -94,8 +110,16 @@ describe('AssetDetails', () => { balanceInFiat="1000 USD" fiatPriceVariation="0" activityList={[ - { amount: '100', fiatAmount: '450' }, - { amount: '200', fiatAmount: '400' } + { + amount: '100', + fiatAmount: '450', + formattedTimestamp: 'Timestamp' + }, + { + amount: '200', + fiatAmount: '400', + formattedTimestamp: 'Timestamp' + } ]} activityListStatus={StateStatus.IDLE} /> diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/assets/components/Assets.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/assets/components/Assets.tsx index 0bfe98e8f1..eaf1749844 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/assets/components/Assets.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/features/assets/components/Assets.tsx @@ -30,7 +30,7 @@ import { import { getTotalWalletBalance, sortAssets } from '../utils'; import { AssetsPortfolio } from './AssetsPortfolio/AssetsPortfolio'; import { AssetDetailsDrawer } from './AssetDetailsDrawer/AssetDetailsDrawer'; -import { AssetTransactionDetails } from './AssetTransactionDetails/AssetTransactionDetails'; +import { AssetActivityDetails } from './AssetActivityDetails/AssetActivityDetails'; import { AssetEducationalList } from './AssetEducationalList/AssetEducationalList'; const LIST_CHUNK_SIZE = 12; @@ -53,8 +53,8 @@ export const Assets = ({ topSection }: AssetsProps): React.ReactElement => { walletUI: { cardanoCoin, appMode, areBalancesVisible, getHiddenBalancePlaceholder }, setAssetDetails, assetDetails, - transactionDetail, - resetTransactionState, + activityDetail, + resetActivityState, blockchainProvider, environmentName } = useWalletStore(); @@ -63,7 +63,7 @@ export const Assets = ({ topSection }: AssetsProps): React.ReactElement => { const { setPickedCoin } = useCoinStateSelector(SEND_COIN_OUTPUT_ID); const { setTriggerPoint } = useAnalyticsSendFlowTriggerPoint(); - const [isTransactionDetailsOpen, setIsTransactionDetailsOpen] = useState(false); + const [isActivityDetailsOpen, setIsActivityDetailsOpen] = useState(false); const [fullAssetList, setFullAssetList] = useState(); const [listItemsAmount, setListItemsAmount] = useState(LIST_CHUNK_SIZE); const [selectedAssetId, setSelectedAssetId] = useState(); @@ -202,21 +202,21 @@ export const Assets = ({ topSection }: AssetsProps): React.ReactElement => { ] ); - const closeTransactionDetailsDrawer = () => setIsTransactionDetailsOpen(false); - const onTransactionDetailsBack = useCallback(() => { - resetTransactionState(); - closeTransactionDetailsDrawer(); + const closeActivityDetailsDrawer = () => setIsActivityDetailsOpen(false); + const onActivityDetailsBack = useCallback(() => { + resetActivityState(); + closeActivityDetailsDrawer(); setAssetDetails(fullAssetList.find((item) => item.id === selectedAssetId)); - }, [selectedAssetId, fullAssetList, resetTransactionState, setAssetDetails]); + }, [selectedAssetId, fullAssetList, resetActivityState, setAssetDetails]); - const onTransactionDetailsVisibleChange = useCallback( + const onActivityDetailsVisibleChange = useCallback( (visible: boolean) => { // Clear transaction details from state after drawer is closed - if (!visible && transactionDetail) { - resetTransactionState(); + if (!visible && activityDetail) { + resetActivityState(); } }, - [transactionDetail, resetTransactionState] + [activityDetail, resetActivityState] ); const onSendAssetClick = (id: string) => { @@ -242,14 +242,14 @@ export const Assets = ({ topSection }: AssetsProps): React.ReactElement => { }; useEffect(() => { - if (transactionDetail) { + if (activityDetail) { setAssetDetails(); } - if (!assetDetails && transactionDetail) { - setIsTransactionDetailsOpen(true); + if (!assetDetails && activityDetail) { + setIsActivityDetailsOpen(true); } - }, [assetDetails, transactionDetail, setAssetDetails]); + }, [assetDetails, activityDetail, setAssetDetails]); // TODO: move this to store once LW-1494 is done useEffect(() => { @@ -285,7 +285,7 @@ export const Assets = ({ topSection }: AssetsProps): React.ReactElement => { useEffect(() => { // Close asset tx details drawer if network (blockchainProvider) has changed - closeTransactionDetailsDrawer(); + closeActivityDetailsDrawer(); }, [blockchainProvider]); const assetsPortfolio = ( @@ -302,12 +302,12 @@ export const Assets = ({ topSection }: AssetsProps): React.ReactElement => { ); const drawers = ( <> - { +const ActivityStatusIcon = ({ status, type }: ActivityStatusIconProps) => { const iconStyle = { fontSize: txIconSize() }; switch (status) { - case TransactionStatus.SUCCESS: - return ; - case TransactionStatus.SPENDABLE: - return ; - case TransactionStatus.PENDING: + case ActivityStatus.SUCCESS: + return ; + case ActivityStatus.SPENDABLE: + return ; + case ActivityStatus.PENDING: return ; - case TransactionStatus.ERROR: + case ActivityStatus.ERROR: default: return ; } @@ -116,7 +106,7 @@ export const AssetActivityItem = ({ type, assetsNumber = 1, assets, - timestamp + formattedTimestamp }: AssetActivityItemProps): React.ReactElement => { const { t } = useTranslate(); const ref = useRef(null); @@ -166,7 +156,7 @@ export const AssetActivityItem = ({ }; }, [debouncedSetText]); - const isPendingTx = status === TransactionStatus.PENDING; + const isPendingTx = status === ActivityStatus.PENDING; const assetsText = useMemo(() => getText(assetsToShow), [getText, assetsToShow]); const assetAmountContent = DelegationTransactionTypes.has(type) ? ( @@ -178,9 +168,9 @@ export const AssetActivityItem = ({ {pluralize('package.core.assetActivityItem.entry.token', assetsNumber, true)}

); - const descriptionContent = timestamp ? ( + const descriptionContent = formattedTimestamp ? (

- {timestamp} + {formattedTimestamp}

) : ( assetAmountContent @@ -193,7 +183,7 @@ export const AssetActivityItem = ({ {customIcon ? ( asset image ) : ( - + )}
diff --git a/packages/core/src/ui/components/Activity/__tests__/AssetActivityItem.test.tsx b/packages/core/src/ui/components/Activity/__tests__/AssetActivityItem.test.tsx index 0740bb1672..b42bb1c35c 100644 --- a/packages/core/src/ui/components/Activity/__tests__/AssetActivityItem.test.tsx +++ b/packages/core/src/ui/components/Activity/__tests__/AssetActivityItem.test.tsx @@ -2,18 +2,18 @@ import * as React from 'react'; import { render, within, fireEvent, queryByTestId } from '@testing-library/react'; import '@testing-library/jest-dom'; -import { AssetActivityItem, AssetActivityItemProps, TransactionStatus } from '../AssetActivityItem'; +import { AssetActivityItem, AssetActivityItemProps, ActivityStatus } from '../AssetActivityItem'; const assetsAmountTestId = 'asset-amount'; describe('Testing AssetActivityItem component', () => { const props: AssetActivityItemProps = { id: '1', - fee: '3,40', type: 'outgoing', amount: '100', fiatAmount: '300 $', - status: TransactionStatus.ERROR, + formattedTimestamp: 'Timestamp', + status: ActivityStatus.ERROR, onClick: jest.fn(), assetsNumber: 1, assets: [{ id: '1', val: '1', info: { ticker: 'testTicker' } }] @@ -75,7 +75,7 @@ describe('Testing AssetActivityItem component', () => { }); test('should hide status when successful transaction', async () => { - const { findByTestId } = render(); + const { findByTestId } = render(); const activityItem = await findByTestId(assetActivityItemId); expect(queryByTestId(activityItem, 'activity-status')).not.toBeInTheDocument(); }); diff --git a/packages/core/src/ui/components/Activity/__tests__/AssetActivityList.test.tsx b/packages/core/src/ui/components/Activity/__tests__/AssetActivityList.test.tsx index 4dded5d6b2..bc29dd1314 100644 --- a/packages/core/src/ui/components/Activity/__tests__/AssetActivityList.test.tsx +++ b/packages/core/src/ui/components/Activity/__tests__/AssetActivityList.test.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { render, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import { AssetActivityList, AssetActivityListProps } from '../AssetActivityList'; -import { TransactionStatus } from '../AssetActivityItem'; +import { ActivityStatus } from '../AssetActivityItem'; const activityTranslations = { asset: 'asset', @@ -25,10 +25,12 @@ describe('Testing AssetActivityList component', () => { type: 'outgoing', name: 'Sent', description: 'ADA', - date: '19:47', + date: new Date('2021-01-01'), + formattedDate: 'Date', + formattedTimestamp: '19:47', amount: '100 ADA', fiatAmount: '300 $', - status: TransactionStatus.ERROR, + status: ActivityStatus.ERROR, assets: [{ id: '1', val: '1' }], translations: activityTranslations })) diff --git a/packages/core/src/ui/components/Activity/__tests__/GroupedAssetActivityList.test.tsx b/packages/core/src/ui/components/Activity/__tests__/GroupedAssetActivityList.test.tsx index 43cafcb261..9d94a228fe 100644 --- a/packages/core/src/ui/components/Activity/__tests__/GroupedAssetActivityList.test.tsx +++ b/packages/core/src/ui/components/Activity/__tests__/GroupedAssetActivityList.test.tsx @@ -9,6 +9,7 @@ const activityItem: AssetActivityItemProps = { type: 'outgoing', amount: '100 ADA', fiatAmount: '300 $', + formattedTimestamp: 'FormattedTimestamp', assetsNumber: 1, assets: [{ id: '1', val: '1' }] }; diff --git a/packages/core/src/ui/components/Transactions/TransactionDetailHeaderBrowser.module.scss b/packages/core/src/ui/components/ActivityDetail/ActivityDetailHeader.module.scss similarity index 100% rename from packages/core/src/ui/components/Transactions/TransactionDetailHeaderBrowser.module.scss rename to packages/core/src/ui/components/ActivityDetail/ActivityDetailHeader.module.scss diff --git a/packages/core/src/ui/components/Transactions/TransactionDetailHeaderBrowser.tsx b/packages/core/src/ui/components/ActivityDetail/ActivityDetailHeader.tsx similarity index 73% rename from packages/core/src/ui/components/Transactions/TransactionDetailHeaderBrowser.tsx rename to packages/core/src/ui/components/ActivityDetail/ActivityDetailHeader.tsx index 5a2c7b4388..4730ac5e24 100644 --- a/packages/core/src/ui/components/Transactions/TransactionDetailHeaderBrowser.tsx +++ b/packages/core/src/ui/components/ActivityDetail/ActivityDetailHeader.tsx @@ -1,19 +1,19 @@ import React from 'react'; import { ReactComponent as Info } from '../../assets/icons/info.component.svg'; import { Tooltip } from 'antd'; -import styles from './TransactionDetailHeaderBrowser.module.scss'; +import styles from './ActivityDetailHeader.module.scss'; -export interface TransactionDetailHeaderBrowserProps { +export interface ActivityDetailHeaderProps { name: string; description: string; tooltipContent?: string; } -export const TransactionDetailHeaderBrowser = ({ +export const ActivityDetailHeader = ({ name, description, tooltipContent -}: TransactionDetailHeaderBrowserProps): React.ReactElement => ( +}: ActivityDetailHeaderProps): React.ReactElement => (
{name}
diff --git a/packages/core/src/ui/components/Transactions/TransactionTypeIcon.tsx b/packages/core/src/ui/components/ActivityDetail/ActivityTypeIcon.tsx similarity index 67% rename from packages/core/src/ui/components/Transactions/TransactionTypeIcon.tsx rename to packages/core/src/ui/components/ActivityDetail/ActivityTypeIcon.tsx index 116b88e959..8d69396df2 100644 --- a/packages/core/src/ui/components/Transactions/TransactionTypeIcon.tsx +++ b/packages/core/src/ui/components/ActivityDetail/ActivityTypeIcon.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { TransactionType } from './TransactionType'; import { ReactComponent as OutgoingIcon } from '../../assets/icons/outgoing.component.svg'; import { ReactComponent as IncomingIcon } from '../../assets/icons/incoming.component.svg'; import { ReactComponent as SelfIcon } from '../../assets/icons/self-transaction.component.svg'; @@ -10,11 +9,23 @@ import { ReactComponent as RewardsIcon } from '../../assets/icons/rewards.compon import Icon, { QuestionOutlined } from '@ant-design/icons'; import { txIconSize } from '@src/ui/utils/icon-size'; -export interface TransactionTypeIconProps { - type: TransactionType; +export type TransactionActivityType = + | 'outgoing' + | 'incoming' + | 'delegation' + | 'delegationRegistration' + | 'delegationDeregistration' + | 'self'; + +export type RewardsActivityType = 'rewards'; + +export type ActivityType = TransactionActivityType | RewardsActivityType; + +export interface ActivityTypeIconProps { + type: ActivityType; } -const transactionTypeIcon: Record>> = { +const activityTypeIcon: Record>> = { outgoing: OutgoingIcon, incoming: IncomingIcon, self: SelfIcon, @@ -24,8 +35,8 @@ const transactionTypeIcon: Record { - const icon = type && transactionTypeIcon[type]; +export const ActivityTypeIcon = ({ type }: ActivityTypeIconProps): React.ReactElement => { + const icon = type && activityTypeIcon[type]; const iconStyle = { fontSize: txIconSize() }; return icon ? : ; diff --git a/packages/core/src/ui/components/ActivityDetail/RewardsDetails.tsx b/packages/core/src/ui/components/ActivityDetail/RewardsDetails.tsx new file mode 100644 index 0000000000..497752b27a --- /dev/null +++ b/packages/core/src/ui/components/ActivityDetail/RewardsDetails.tsx @@ -0,0 +1,130 @@ +import React from 'react'; +import cn from 'classnames'; +import styles from './TransactionDetails.module.scss'; +import { useTranslate } from '@src/ui/hooks'; +import { ActivityStatus } from '../Activity/AssetActivityItem'; +import { Ellipsis } from '@lace/common'; +import { ActivityDetailHeader } from './ActivityDetailHeader'; + +type RewardItem = { + pool?: { name: string; ticker: string; id: string }; + amount: string; +}; + +export type RewardsInfo = { + totalAmount: string; + spendableEpoch: number; + rewards: RewardItem[]; +}; + +export interface RewardsDetailsProps { + name: string; + headerDescription?: string; + status: ActivityStatus.SPENDABLE; + includedDate: string; + includedTime: string; + amountTransformer: (amount: string) => string; + coinSymbol: string; + rewards: RewardsInfo; +} + +export const RewardsDetails = ({ + name, + headerDescription, + status, + includedDate, + includedTime, + amountTransformer, + coinSymbol, + rewards +}: RewardsDetailsProps): React.ReactElement => { + const { t } = useTranslate(); + const poolRewards = rewards.rewards.filter((reward) => !!reward.pool); + const tooltipContent = t('package.core.activityDetails.rewardsDescription'); + + return ( +
+ +
+
{t('package.core.activityDetails.header')}
+

{t('package.core.activityDetails.summary')}

+
+
+
+
{name}
+
+
+ {`${rewards.totalAmount} ${coinSymbol}`} + {`${amountTransformer( + rewards.totalAmount + )}`} +
+
+
+
+ + {poolRewards.length > 0 && ( +
+
{t('package.core.activityDetails.pools')}
+
+ {poolRewards.map(({ pool, amount }) => ( +
+
+ {pool.name && ( +
+ {pool.name} +
+ )} + {pool.ticker && ( +
+ ({pool.ticker}) +
+ )} +
+ {pool.id && ( +
+ +
+ )} +
+ + {amount} {coinSymbol} + + + {amountTransformer(amount)} + +
+
+ ))} +
+
+ )} + +
+
{t('package.core.activityDetails.status')}
+
{`${status + .charAt(0) + .toUpperCase()}${status.slice(1)}`}
+
+
+
{t('package.core.activityDetails.epoch')}
+
{`${rewards.spendableEpoch}`}
+
+
+
{t('package.core.activityDetails.timestamp')}
+
+ {includedDate} +  {includedTime} +
+
+
+
+
+ ); +}; diff --git a/packages/core/src/ui/components/Transactions/TransactionDetailAsset.ts b/packages/core/src/ui/components/ActivityDetail/TransactionDetailAsset.ts similarity index 100% rename from packages/core/src/ui/components/Transactions/TransactionDetailAsset.ts rename to packages/core/src/ui/components/ActivityDetail/TransactionDetailAsset.ts diff --git a/packages/core/src/ui/components/Transactions/TransactionDetailBrowser.module.scss b/packages/core/src/ui/components/ActivityDetail/TransactionDetails.module.scss similarity index 95% rename from packages/core/src/ui/components/Transactions/TransactionDetailBrowser.module.scss rename to packages/core/src/ui/components/ActivityDetail/TransactionDetails.module.scss index 39ebb27770..a11c7e8b65 100644 --- a/packages/core/src/ui/components/Transactions/TransactionDetailBrowser.module.scss +++ b/packages/core/src/ui/components/ActivityDetail/TransactionDetails.module.scss @@ -283,6 +283,19 @@ $border-bottom: 1px solid var(--light-mode-light-grey-plus, var(--dark-mode-mid- gap: size_unit(1) } + .poolRewardAmount { + display: flex; + flex-direction: column; + font-size: var(--bodySmall); + .ada { + color: var(--text-color-primary, #ffffff); + } + + .fiat { + color: var(--text-color-secondary, #878e9e); + } + } + .lightLabel { color: var(--light-mode-dark-grey, var(--dark-mode-light-grey)); } diff --git a/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx b/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx new file mode 100644 index 0000000000..e54e20cc15 --- /dev/null +++ b/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx @@ -0,0 +1,346 @@ +/* eslint-disable no-magic-numbers */ +import React from 'react'; +import cn from 'classnames'; +import { InfoCircleOutlined } from '@ant-design/icons'; +import { Tooltip } from 'antd'; +import styles from './TransactionDetails.module.scss'; +import { TransactionDetailAsset, TxOutputInput, TransactionMetadataProps, TxSummary } from './TransactionDetailAsset'; +import type { ActivityStatus } from '../Activity'; +import { Ellipsis, toast } from '@lace/common'; +import { ReactComponent as Info } from '../../assets/icons/info-icon.component.svg'; +import { TransactionInputOutput } from './TransactionInputOutput'; +import { useTranslate } from '@src/ui/hooks'; +import CopyToClipboard from 'react-copy-to-clipboard'; +import { ActivityDetailHeader } from './ActivityDetailHeader'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const displayMetadataMsg = (value: any[]): string => value?.find((val: any) => val.hasOwnProperty('msg'))?.msg || ''; + +export interface TransactionDetailsProps { + hash?: string; + name: string; + status?: ActivityStatus; + /** + * Transaction generation date + */ + includedDate?: string; + /** + * Transaction generation time + */ + includedTime?: string; + assets?: TransactionDetailAsset[]; + /** + * Input address list + */ + addrInputs?: TxOutputInput[]; + /** + * Output address list + */ + addrOutputs?: TxOutputInput[]; + /** + * Transaction total output + */ + totalOutput?: string; + /** + * Transaction fee + */ + fee?: string; + pools?: { name: string; ticker: string; id: string }[]; + /** + * Transaction deposit + */ + deposit?: string; + /** + * Transaction returned deposit + */ + depositReclaim?: string; + /** + * Transaction metadata + */ + metadata?: TransactionMetadataProps['metadata']; + amountTransformer: (amount: string) => string; + headerDescription?: string; + txSummary?: TxSummary[]; + coinSymbol: string; + tooltipContent?: string; + addressToNameMap: Map; + isPopupView?: boolean; + openExternalLink?: () => void; + sendAnalyticsInputs?: () => void; + sendAnalyticsOutputs?: () => void; +} + +const TOAST_DEFAULT_DURATION = 3; + +const CopiableHash = ({ hash, copiedText }: { hash: string; copiedText: string }) => ( + +
+ toast.notify({ + duration: TOAST_DEFAULT_DURATION, + text: copiedText + }) + } + > + {hash} +
+
+); + +// eslint-disable-next-line react/no-multi-comp,complexity +export const TransactionDetails = ({ + hash, + name, + status, + headerDescription, + includedDate = '-', + includedTime = '-', + fee = '-', + deposit, + depositReclaim, + addrInputs, + addrOutputs, + metadata, + amountTransformer, + txSummary = [], + coinSymbol, + pools, + addressToNameMap, + isPopupView, + openExternalLink, + sendAnalyticsInputs, + sendAnalyticsOutputs +}: TransactionDetailsProps): React.ReactElement => { + const { t } = useTranslate(); + const isSending = status === 'sending'; + const isSuccess = status === 'success'; + + const renderDepositValueSection = ({ value, label }: { value: string; label: string }) => ( +
+
{label}
+
+
+ {`${value} ${coinSymbol}`} + {amountTransformer(value)} +
+
+
+ ); + + return ( +
+ +
+
+ {t('package.core.activityDetails.header')} +
+
+
+
+
{t('package.core.activityDetails.transactionHash')}
+
+
+
+ {isSending ? ( + + ) : ( + hash + )} +
+
+
+ +

{t('package.core.activityDetails.summary')}

+ {pools?.length > 0 && ( +
+
{t('package.core.activityDetails.pools')}
+
+ {pools?.map((pool) => ( +
+
+ {pool.name && ( +
+ {pool.name} +
+ )} + {pool.ticker && ( +
+ ({pool.ticker}) +
+ )} +
+ {pool.id && ( +
+ +
+ )} +
+ ))} +
+
+ )} + {txSummary.map((summary, index) => ( +
+
+
{name}
+
+ {summary.assetList?.map((asset, i) => ( +
+ + {asset.amount} {asset.symbol} + + {asset?.fiatBalance && ( + + {asset.fiatBalance} + + )} +
+ ))} +
+ {`${summary.amount} ${coinSymbol}`} + {`${amountTransformer( + summary.amount + )}`} +
+
+
+
+
+ {t(`package.core.activityDetails.${name.toLowerCase() === 'sent' ? 'to' : 'from'}`)} +
+
+ {summary.addr.length > 1 && ( +
+ {t('package.core.activityDetails.multipleAddresses')} +
+ )} + {(summary.addr as string[]).map((addr) => { + const addrName = addressToNameMap?.get(addr); + const address = isPopupView ? ( + + ) : ( + {addr} + ); + return ( +
+ {addrName ? ( +
+ {addrName} + {address} +
+ ) : ( + address + )} +
+ ); + })} +
+
+
+ ))} +
+
{t('package.core.activityDetails.status')}
+ {status && ( +
{`${status.charAt(0).toUpperCase()}${status.slice( + 1 + )}`}
+ )} +
+
+
{t('package.core.activityDetails.timestamp')}
+
+ {includedDate} +  {includedTime} +
+
+ + {fee && fee !== '-' && ( +
+
+
{t('package.core.activityDetails.transactionFee')}
+ + {Info ? ( + + ) : ( + + )} + +
+ +
+
+ {`${fee} ${coinSymbol}`} + + {amountTransformer(fee)} + +
+
+
+ )} + + {deposit && renderDepositValueSection({ value: deposit, label: t('package.core.activityDetails.deposit') })} + {depositReclaim && + renderDepositValueSection({ + value: depositReclaim, + label: t('package.core.activityDetails.depositReclaim') + })} +
+ + {addrInputs?.length > 0 && ( + + )} + {addrOutputs?.length > 0 && ( + + )} + {metadata?.length > 0 && ( +
+
{t('package.core.activityDetails.metadata')}
+
+ {metadata?.map((item) => ( +
+ {/* there are two options here item.value could be an array or string, if it is an array format item.value using displayMetadataMsg, if not just use item.value */} + {Array.isArray(item.value) ? displayMetadataMsg(item.value) : item.value} +
+ ))} +
+
+ )} +
+
+ ); +}; diff --git a/packages/core/src/ui/components/Transactions/TransactionInputOutput.module.scss b/packages/core/src/ui/components/ActivityDetail/TransactionInputOutput.module.scss similarity index 100% rename from packages/core/src/ui/components/Transactions/TransactionInputOutput.module.scss rename to packages/core/src/ui/components/ActivityDetail/TransactionInputOutput.module.scss diff --git a/packages/core/src/ui/components/Transactions/TransactionInputOutput.tsx b/packages/core/src/ui/components/ActivityDetail/TransactionInputOutput.tsx similarity index 100% rename from packages/core/src/ui/components/Transactions/TransactionInputOutput.tsx rename to packages/core/src/ui/components/ActivityDetail/TransactionInputOutput.tsx diff --git a/packages/core/src/ui/components/ActivityDetail/__tests__/RewardsDetails.test.tsx b/packages/core/src/ui/components/ActivityDetail/__tests__/RewardsDetails.test.tsx new file mode 100644 index 0000000000..b00f5723de --- /dev/null +++ b/packages/core/src/ui/components/ActivityDetail/__tests__/RewardsDetails.test.tsx @@ -0,0 +1,62 @@ +/* eslint-disable no-magic-numbers */ +import * as React from 'react'; +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { RewardsDetails, RewardsDetailsProps } from '../RewardsDetails'; +import { ActivityStatus } from '../../Activity/AssetActivityItem'; + +describe('Testing ActivityDetailsBrowser component', () => { + const rewardsDetailsProps: RewardsDetailsProps = { + name: 'Name', + headerDescription: 'Header Description', + status: ActivityStatus.SPENDABLE, + includedDate: 'Date', + includedTime: 'Time', + amountTransformer: (amount) => `${amount} $`, + coinSymbol: 'ADA', + rewards: { + totalAmount: 'Amount', + spendableEpoch: 47, + rewards: [ + { + pool: { name: 'pool1', ticker: 'A', id: '1' }, + amount: '10' + }, + { + pool: { name: 'pool2', ticker: 'B', id: '2' }, + amount: '10' + } + ] + } + }; + + test('should display rewards detail bundle', async () => { + const { findByTestId } = render(); + + const container = await findByTestId('rewards-detail-bundle'); + expect(container).toBeVisible(); + }); + + test('should display rewards pools bundle', async () => { + const { findAllByTestId } = render(); + + const containers = await findAllByTestId('rewards-pool-name'); + + expect(containers).toHaveLength(2); + containers.forEach((c) => expect(c).toBeVisible()); + }); + + test('should display rewards epoch', async () => { + const { findByTestId } = render(); + + const dateContainer = await findByTestId('rewards-epoch'); + expect(dateContainer).toBeVisible(); + }); + + test('should display rewards date and time', async () => { + const { findByTestId } = render(); + + const dateContainer = await findByTestId('rewards-date'); + expect(dateContainer).toBeVisible(); + }); +}); diff --git a/packages/core/src/ui/components/Transactions/__tests__/TransactionDetailBrowser.test.tsx b/packages/core/src/ui/components/ActivityDetail/__tests__/TransactionDetails.test.tsx similarity index 73% rename from packages/core/src/ui/components/Transactions/__tests__/TransactionDetailBrowser.test.tsx rename to packages/core/src/ui/components/ActivityDetail/__tests__/TransactionDetails.test.tsx index 48699f044c..73522e5c24 100644 --- a/packages/core/src/ui/components/Transactions/__tests__/TransactionDetailBrowser.test.tsx +++ b/packages/core/src/ui/components/ActivityDetail/__tests__/TransactionDetails.test.tsx @@ -1,13 +1,14 @@ /* eslint-disable no-magic-numbers */ import * as React from 'react'; import { render, within, fireEvent } from '@testing-library/react'; -import { TransactionDetailBrowser, TransactionDetailBrowserProps } from '../TransactionDetailBrowser'; import '@testing-library/jest-dom'; +import { TransactionDetails, TransactionDetailsProps } from '../TransactionDetails'; const transactionDate = '2021/09/10'; -describe('Testing TransactionDetailsBrowser component', () => { - const addrListProps: TransactionDetailBrowserProps = { +describe('Testing ActivityDetailsBrowser component', () => { + const addrListProps: TransactionDetailsProps = { + name: 'Name', isPopupView: false, hash: '5e58ad7aa10667c05c3ffdb9ae65fe22c77e5145db823715217b775b4344839f', totalOutput: '38038.963341 ADA', @@ -34,26 +35,25 @@ describe('Testing TransactionDetailsBrowser component', () => { ], amountTransformer: (amount) => `${amount} $`, coinSymbol: 'ADA', - type: 'incoming', addressToNameMap: new Map() }; test('should display transaction hash and copy button', async () => { - const { findByTestId } = render(); + const { findByTestId } = render(); const container = await findByTestId('tx-hash'); expect(container).toBeVisible(); }); test('should display transaction date and time', async () => { - const { findByTestId } = render(); + const { findByTestId } = render(); const dateContainer = await findByTestId('tx-date'); expect(dateContainer).toBeVisible(); }); test('should display transaction inputs and outputs list', async () => { - const { findByTestId } = render(); + const { findByTestId } = render(); const inputContainer = await findByTestId('tx-inputs'); const listToggle = await findByTestId('tx-addr-list_toggle'); @@ -65,7 +65,7 @@ describe('Testing TransactionDetailsBrowser component', () => { }); test('should display transaction and fee', async () => { - const { findByTestId } = render(); + const { findByTestId } = render(); const feeContainer = await findByTestId('tx-fee'); expect(feeContainer).toBeVisible(); @@ -73,14 +73,14 @@ describe('Testing TransactionDetailsBrowser component', () => { test('should display transaction metadata if available', async () => { const { findByTestId } = render( - + ); const txMetadata = await findByTestId('tx-metadata'); expect(txMetadata).toBeVisible(); }); test('should not display transaction metadata if not available', async () => { - const { queryByTestId: query } = render(); + const { queryByTestId: query } = render(); expect(query('tx-metadata')).not.toBeInTheDocument(); }); }); diff --git a/packages/core/src/ui/components/ActivityDetail/index.ts b/packages/core/src/ui/components/ActivityDetail/index.ts new file mode 100644 index 0000000000..26ccb7a4b5 --- /dev/null +++ b/packages/core/src/ui/components/ActivityDetail/index.ts @@ -0,0 +1,5 @@ +export * from './TransactionDetails'; +export * from './RewardsDetails'; +export * from './ActivityTypeIcon'; +export * from './TransactionDetailAsset'; +export * from './TransactionInputOutput'; diff --git a/packages/core/src/ui/components/Transactions/RewardDetails.tsx b/packages/core/src/ui/components/Transactions/RewardDetails.tsx deleted file mode 100644 index 857b9c9df4..0000000000 --- a/packages/core/src/ui/components/Transactions/RewardDetails.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import styles from './TransactionDetailBrowser.module.scss'; -import { useTranslate } from '@src/ui/hooks'; -import { TransactionStatus } from '../Activity/AssetActivityItem'; - -export interface RewardDetailsProps { - name: string; - status?: TransactionStatus; - includedDate?: string; - includedTime?: string; - amountTransformer: (amount: string) => string; - coinSymbol: string; - rewards?: string; -} - -export const RewardDetails = ({ - name, - status, - includedDate = '-', - includedTime = '-', - amountTransformer, - coinSymbol, - rewards -}: RewardDetailsProps): React.ReactElement => { - const { t } = useTranslate(); - return ( -
-
{t('package.core.transactionDetailBrowser.header')}
-

{t('package.core.transactionDetailBrowser.summary')}

-
-
-
-
{name}
-
-
- {`${rewards} ${coinSymbol}`} - {`${amountTransformer(rewards)}`} -
-
-
-
- -
-
{t('package.core.transactionDetailBrowser.status')}
- {status && ( -
{`${status.charAt(0).toUpperCase()}${status.slice( - 1 - )}`}
- )} -
-
-
- {t('package.core.transactionDetailBrowser.timestamp')} -
-
- {includedDate} -  {includedTime} -
-
-
-
- ); -}; diff --git a/packages/core/src/ui/components/Transactions/Transaction.tsx b/packages/core/src/ui/components/Transactions/Transaction.tsx deleted file mode 100644 index a3591ee3fc..0000000000 --- a/packages/core/src/ui/components/Transactions/Transaction.tsx +++ /dev/null @@ -1,345 +0,0 @@ -/* eslint-disable no-magic-numbers */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import React from 'react'; -import cn from 'classnames'; -import { InfoCircleOutlined } from '@ant-design/icons'; -import { Tooltip } from 'antd'; -import styles from './TransactionDetailBrowser.module.scss'; -import { TransactionDetailAsset, TxOutputInput, TransactionMetadataProps, TxSummary } from './TransactionDetailAsset'; -import { TransactionStatus } from '../Activity'; -import { Ellipsis, toast } from '@lace/common'; -import { ReactComponent as Info } from '../../assets/icons/info-icon.component.svg'; -import { TransactionInputOutput } from './TransactionInputOutput'; -import { useTranslate } from '@src/ui/hooks'; -import CopyToClipboard from 'react-copy-to-clipboard'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const displayMetadataMsg = (value: any[]): string => value?.find((val: any) => val.hasOwnProperty('msg'))?.msg || ''; - -export interface TransactionProps { - hash: string; - name: string; - status?: TransactionStatus; - /** - * Transaction generation date - */ - includedDate?: string; - /** - * Transaction generation time - */ - includedTime?: string; - assets?: TransactionDetailAsset[]; - /** - * Input address list - */ - addrInputs?: TxOutputInput[]; - /** - * Output address list - */ - addrOutputs?: TxOutputInput[]; - /** - * Transaction total output - */ - totalOutput?: string; - /** - * Transaction fee - */ - fee?: string; - pools?: { name: string; ticker: string; id: string }[]; - /** - * Transaction deposit - */ - deposit?: string; - /** - * Transaction returned deposit - */ - depositReclaim?: string; - /** - * Transaction metadata - */ - metadata?: TransactionMetadataProps['metadata']; - amountTransformer: (amount: string) => string; - headerDescription?: string; - txSummary?: TxSummary[]; - coinSymbol: string; - tooltipContent?: string; - rewards?: string; - addressToNameMap: Map; - isPopupView?: boolean; - openExternalLink?: () => void; - sendAnalyticsInputs?: () => void; - sendAnalyticsOutputs?: () => void; -} - -const TOAST_DEFAULT_DURATION = 3; - -const CopiableHash = ({ hash, copiedText }: { hash: string; copiedText: string }) => ( - -
- toast.notify({ - duration: TOAST_DEFAULT_DURATION, - text: copiedText - }) - } - > - {hash} -
-
-); - -// eslint-disable-next-line react/no-multi-comp,complexity -export const Transaction = ({ - hash, - name, - status, - includedDate = '-', - includedTime = '-', - fee = '-', - deposit, - depositReclaim, - addrInputs, - addrOutputs, - metadata, - amountTransformer, - txSummary = [], - coinSymbol, - pools, - addressToNameMap, - isPopupView, - openExternalLink, - sendAnalyticsInputs, - sendAnalyticsOutputs -}: TransactionProps): React.ReactElement => { - const { t } = useTranslate(); - const isSending = status === 'sending'; - const isSuccess = status === 'success'; - - const renderDepositValueSection = ({ value, label }: { value: string; label: string }) => ( -
-
{label}
-
-
- {`${value} ${coinSymbol}`} - {amountTransformer(value)} -
-
-
- ); - - return ( -
-
- {t('package.core.transactionDetailBrowser.header')} -
-
-
-
-
{t('package.core.transactionDetailBrowser.transactionHash')}
-
-
-
- {isSending ? ( - - ) : ( - hash - )} -
-
-
- -

{t('package.core.transactionDetailBrowser.summary')}

- {pools?.length > 0 && ( -
-
- {t('package.core.transactionDetailBrowser.pools')} -
-
- {pools?.map((pool) => ( -
-
- {pool.name && ( -
- {pool.name} -
- )} - {pool.ticker && ( -
- ({pool.ticker}) -
- )} -
- {pool.id && ( -
- -
- )} -
- ))} -
-
- )} - {txSummary.map((summary, index) => ( -
-
-
{name}
-
- {summary.assetList?.map((asset, i) => ( -
- - {asset.amount} {asset.symbol} - - {asset?.fiatBalance && ( - - {asset.fiatBalance} - - )} -
- ))} -
- {`${summary.amount} ${coinSymbol}`} - {`${amountTransformer( - summary.amount - )}`} -
-
-
-
-
- {t(`package.core.transactionDetailBrowser.${name.toLowerCase() === 'sent' ? 'to' : 'from'}`)} -
-
- {summary.addr.length > 1 && ( -
- {t('package.core.transactionDetailBrowser.multipleAddresses')} -
- )} - {(summary.addr as string[]).map((addr) => { - const addrName = addressToNameMap?.get(addr); - const address = isPopupView ? ( - - ) : ( - {addr} - ); - return ( -
- {addrName ? ( -
- {addrName} - {address} -
- ) : ( - address - )} -
- ); - })} -
-
-
- ))} -
-
{t('package.core.transactionDetailBrowser.status')}
- {status && ( -
{`${status.charAt(0).toUpperCase()}${status.slice( - 1 - )}`}
- )} -
-
-
- {t('package.core.transactionDetailBrowser.timestamp')} -
-
- {includedDate} -  {includedTime} -
-
- - {fee && fee !== '-' && ( -
-
-
{t('package.core.transactionDetailBrowser.transactionFee')}
- - {Info ? ( - - ) : ( - - )} - -
- -
-
- {`${fee} ${coinSymbol}`} - - {amountTransformer(fee)} - -
-
-
- )} - - {deposit && - renderDepositValueSection({ value: deposit, label: t('package.core.transactionDetailBrowser.deposit') })} - {depositReclaim && - renderDepositValueSection({ - value: depositReclaim, - label: t('package.core.transactionDetailBrowser.depositReclaim') - })} -
- - {addrInputs?.length > 0 && ( - - )} - {addrOutputs?.length > 0 && ( - - )} - {metadata?.length > 0 && ( -
-
{t('package.core.transactionDetailBrowser.metadata')}
-
- {metadata?.map((item) => ( -
- {/* there are two options here item.value could be an array or string, if it is an array format item.value using displayMetadataMsg, if not just use item.value */} - {Array.isArray(item.value) ? displayMetadataMsg(item.value) : item.value} -
- ))} -
-
- )} -
- ); -}; diff --git a/packages/core/src/ui/components/Transactions/TransactionDetailBrowser.tsx b/packages/core/src/ui/components/Transactions/TransactionDetailBrowser.tsx deleted file mode 100644 index e60c0b3cbb..0000000000 --- a/packages/core/src/ui/components/Transactions/TransactionDetailBrowser.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import styles from './TransactionDetailBrowser.module.scss'; -import { TransactionDetailHeaderBrowser } from './TransactionDetailHeaderBrowser'; -import { TransactionStatus } from '../Activity/AssetActivityItem'; -import { RewardDetailsProps } from './RewardDetails'; -import { Transaction, TransactionProps } from './Transaction'; -import { TransactionType } from './TransactionType'; -import { useTranslate } from '@src/ui/hooks'; - -const getTypeLabel = (type: TransactionType, t: ReturnType['t']) => { - if (type === 'rewards') return t('package.core.transactionDetailBrowser.rewards'); - if (type === 'delegation') return t('package.core.transactionDetailBrowser.delegation'); - if (type === 'delegationRegistration') return t('package.core.transactionDetailBrowser.registration'); - if (type === 'delegationDeregistration') return t('package.core.transactionDetailBrowser.deregistration'); - if (type === 'incoming') return t('package.core.transactionDetailBrowser.received'); - return t('package.core.transactionDetailBrowser.sent'); -}; - -export type TransactionDetailBrowserProps = Omit & - Omit & { - headerDescription?: string; - type?: TransactionType; - addressToNameMap: Map; - isPopupView?: boolean; - }; - -export const TransactionDetailBrowser = ({ - status, - headerDescription, - includedDate, - includedTime, - amountTransformer, - coinSymbol, - type, - isPopupView, - ...props -}: TransactionDetailBrowserProps): React.ReactElement => { - const { t } = useTranslate(); - - const name = - status === TransactionStatus.PENDING ? t('package.core.transactionDetailBrowser.sending') : getTypeLabel(type, t); - const tooltipContent = type === 'rewards' ? t('package.core.transactionDetailBrowser.rewardsDescription') : undefined; - - const transactionProps: TransactionProps = { - ...props, - includedDate, - includedTime, - status, - name, - amountTransformer, - coinSymbol, - isPopupView - }; - - // temporarily unused as part of (LW-8315), rewards to be reintroduced with LW-8751 - /* - const rewardProps: RewardDetailsProps = { - name, - status, - includedDate, - includedTime, - amountTransformer, - coinSymbol, - rewards - }; - */ - - return ( -
- - {status === TransactionStatus.SPENDABLE ? ( - // temporarily unused as part of (LW-8315), rewards to be reintroduced with LW-8751 - // - <> - ) : ( - - )} -
- ); -}; diff --git a/packages/core/src/ui/components/Transactions/TransactionType.ts b/packages/core/src/ui/components/Transactions/TransactionType.ts deleted file mode 100644 index 78a878580c..0000000000 --- a/packages/core/src/ui/components/Transactions/TransactionType.ts +++ /dev/null @@ -1,8 +0,0 @@ -export type TransactionType = - | 'outgoing' - | 'incoming' - | 'delegation' - | 'delegationRegistration' - | 'delegationDeregistration' - | 'rewards' - | 'self'; diff --git a/packages/core/src/ui/components/Transactions/index.ts b/packages/core/src/ui/components/Transactions/index.ts deleted file mode 100644 index d5e9a348d2..0000000000 --- a/packages/core/src/ui/components/Transactions/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './TransactionDetailBrowser'; -export * from './TransactionType'; -export * from './TransactionTypeIcon'; -export * from './TransactionDetailAsset'; -export * from './TransactionInputOutput'; diff --git a/packages/core/src/ui/lib/translations/en.json b/packages/core/src/ui/lib/translations/en.json index 705ebbb013..c73e862cbd 100644 --- a/packages/core/src/ui/lib/translations/en.json +++ b/packages/core/src/ui/lib/translations/en.json @@ -54,7 +54,7 @@ "usedAllAssets": "All gone! You've already selected everything", "noMatchingResult": "No results matching your search" }, - "transactionDetailBrowser": { + "activityDetails": { "address": "Address", "sent": "Sent", "sending": "Sending", @@ -79,7 +79,8 @@ "rewards": "Rewards", "rewardsDescription": "When available, your rewards will be withdrawn automatically every time you send Tokens.", "copiedToClipboard": "Copied to clipboard", - "pools": "Pool(s)" + "pools": "Pool(s)", + "epoch": "Epoch" }, "authorizeDapp": { "title": "Allow this site to", diff --git a/packages/e2e-tests/src/assert/transactionDetailsAssert.ts b/packages/e2e-tests/src/assert/transactionDetailsAssert.ts index 7c2db0db70..2bf33e7f5b 100644 --- a/packages/e2e-tests/src/assert/transactionDetailsAssert.ts +++ b/packages/e2e-tests/src/assert/transactionDetailsAssert.ts @@ -5,7 +5,7 @@ import testContext from '../utils/testContext'; import { browser } from '@wdio/globals'; import { t } from '../utils/translationService'; -export type ExpectedTransactionDetails = { +export type ExpectedActivityDetails = { transactionDescription: string; hash?: string; transactionData?: transactionData[]; @@ -29,39 +29,39 @@ class TransactionsDetailsAssert { }); }; - async assertSeeTransactionDetailsDrawer(shouldBeDisplayed: boolean) { + async assertSeeActivityDetailsDrawer(shouldBeDisplayed: boolean) { await TransactionDetailsPage.transactionDetails.waitForDisplayed({ reverse: !shouldBeDisplayed }); await TransactionDetailsPage.transactionHeader.waitForDisplayed({ reverse: !shouldBeDisplayed }); if (shouldBeDisplayed) { expect(await TransactionDetailsPage.transactionHeader.getText()).to.equal( - await t('package.core.transactionDetailBrowser.header') + await t('package.core.activityDetails.header') ); } } - async assertSeeTransactionDetails(expectedTransactionDetails: ExpectedTransactionDetails) { + async assertSeeActivityDetails(expectedActivityDetails: ExpectedActivityDetails) { await TransactionDetailsPage.transactionDetailsDescription.waitForClickable({ timeout: 15_000 }); expect(await TransactionDetailsPage.transactionDetailsDescription.getText()).contains( - `${expectedTransactionDetails.transactionDescription}` + `${expectedActivityDetails.transactionDescription}` ); - if (expectedTransactionDetails.hash) { + if (expectedActivityDetails.hash) { expect(await TransactionDetailsPage.transactionDetailsHash.getText()).to.equal( - String(expectedTransactionDetails.hash) + String(expectedActivityDetails.hash) ); } - expect(await TransactionDetailsPage.transactionDetailsStatus.getText()).to.equal(expectedTransactionDetails.status); - if (expectedTransactionDetails.transactionData) { - for (let i = 0; i < expectedTransactionDetails.transactionData.length; i++) { - if (expectedTransactionDetails.transactionData[i].assets) { + expect(await TransactionDetailsPage.transactionDetailsStatus.getText()).to.equal(expectedActivityDetails.status); + if (expectedActivityDetails.transactionData) { + for (let i = 0; i < expectedActivityDetails.transactionData.length; i++) { + if (expectedActivityDetails.transactionData[i].assets) { const actualAssets = await TransactionDetailsPage.getTransactionSentTokensForBundle(i); - expect(actualAssets.toString()).to.equal(String(expectedTransactionDetails.transactionData[i].assets)); + expect(actualAssets.toString()).to.equal(String(expectedActivityDetails.transactionData[i].assets)); } expect(await TransactionDetailsPage.transactionDetailsSentAda(i).getText()).to.equal( - expectedTransactionDetails.transactionData[i].ada + expectedActivityDetails.transactionData[i].ada ); - const expectedAddress = expectedTransactionDetails.transactionData[i].address; + const expectedAddress = expectedActivityDetails.transactionData[i].address; const actualAddressSplit = (await TransactionDetailsPage.transactionDetailsToAddress(i).getText()).split('...'); if (actualAddressSplit.length === 1) { expect(expectedAddress).to.equal(actualAddressSplit[0]); @@ -71,24 +71,24 @@ class TransactionsDetailsAssert { } } } - if (expectedTransactionDetails.poolName) { + if (expectedActivityDetails.poolName) { expect(await TransactionDetailsPage.transactionDetailsStakepoolName.getText()).to.equal( - expectedTransactionDetails.poolName + expectedActivityDetails.poolName ); } - if (expectedTransactionDetails.poolTicker) { + if (expectedActivityDetails.poolTicker) { expect(await TransactionDetailsPage.transactionDetailsStakepoolTicker.getText()).to.equal( - `(${expectedTransactionDetails.poolTicker})` + `(${expectedActivityDetails.poolTicker})` ); } - if (expectedTransactionDetails.poolID) { + if (expectedActivityDetails.poolID) { expect(await TransactionDetailsPage.transactionDetailsStakePoolId.getText()).to.equal( - expectedTransactionDetails.poolID + expectedActivityDetails.poolID ); } } - async assertSeeTransactionDetailsUnfolded(mode: 'extended' | 'popup') { + async assertSeeActivityDetailsUnfolded(mode: 'extended' | 'popup') { await this.waitForTransactionsLoaded(); const rowsNumber = (await TransactionsPage.rows).length; @@ -109,11 +109,11 @@ class TransactionsDetailsAssert { await TransactionDetailsPage.transactionDetailsStakePoolId.waitForDisplayed(); } - await TransactionDetailsPage.closeTransactionDetails(mode); + await TransactionDetailsPage.closeActivityDetails(mode); } } - async assertSeeTransactionDetailsInputAndOutputs(mode: 'extended' | 'popup') { + async assertSeeActivityDetailsInputAndOutputs(mode: 'extended' | 'popup') { await this.waitForTransactionsLoaded(); const rowsNumber = (await TransactionsPage.rows).length; @@ -132,7 +132,7 @@ class TransactionsDetailsAssert { await TransactionDetailsPage.transactionDetailsOutputFiatAmount.waitForDisplayed(); // await TransactionDetailsPage.transactionDetailsOutputTokens.waitForDisplayed(); - await TransactionDetailsPage.closeTransactionDetails(mode); + await TransactionDetailsPage.closeActivityDetails(mode); } } @@ -170,11 +170,11 @@ class TransactionsDetailsAssert { expect(txDetailsFeeADAValue).to.be.greaterThan(0); expect(txDetailsFeeFiatValue).to.be.greaterThan(0); - await TransactionDetailsPage.closeTransactionDetails(mode); + await TransactionDetailsPage.closeActivityDetails(mode); } } - async assertSeeTransactionDetailsSummary(mode: 'extended' | 'popup') { + async assertSeeActivityDetailsSummary(mode: 'extended' | 'popup') { await this.waitForTransactionsLoaded(); const rowsNumber = (await TransactionsPage.rows).length; @@ -191,11 +191,11 @@ class TransactionsDetailsAssert { await TransactionDetailsPage.transactionDetailsToAddress(0).waitForDisplayed(); } - await TransactionDetailsPage.closeTransactionDetails(mode); + await TransactionDetailsPage.closeActivityDetails(mode); } } - async assertSeeTransactionDetailsSummaryAmounts(mode: 'extended' | 'popup') { + async assertSeeActivityDetailsSummaryAmounts(mode: 'extended' | 'popup') { await this.waitForTransactionsLoaded(); const rowsNumber = (await TransactionsPage.rows).length; @@ -213,7 +213,7 @@ class TransactionsDetailsAssert { expect(tokensAmountSummary).to.equal(Number(tokensDescriptionAmount)); } - await TransactionDetailsPage.closeTransactionDetails(mode); + await TransactionDetailsPage.closeActivityDetails(mode); } } } diff --git a/packages/e2e-tests/src/elements/transactionDetails.ts b/packages/e2e-tests/src/elements/transactionDetails.ts index 54c69981a4..eb3fd6cfba 100644 --- a/packages/e2e-tests/src/elements/transactionDetails.ts +++ b/packages/e2e-tests/src/elements/transactionDetails.ts @@ -2,7 +2,7 @@ import { ChainablePromiseElement } from 'webdriverio'; import CommonDrawerElements from './CommonDrawerElements'; -class TransactionDetailsPage extends CommonDrawerElements { +class ActivityDetailsPage extends CommonDrawerElements { protected CONTAINER = '[data-testid="custom-drawer"]'; private TRANSACTION_DETAILS = '[data-testid="transaction-detail"]'; private TRANSACTION_DETAILS_SKELETON = '.ant-drawer-body .ant-skeleton'; @@ -172,7 +172,7 @@ class TransactionDetailsPage extends CommonDrawerElements { return [...new Set(await arr)]; } - async closeTransactionDetails(mode: 'extended' | 'popup'): Promise { + async closeActivityDetails(mode: 'extended' | 'popup'): Promise { mode === 'popup' ? await this.clickHeaderBackButton() : await this.clickHeaderCloseButton(); } @@ -185,4 +185,4 @@ class TransactionDetailsPage extends CommonDrawerElements { } } -export default new TransactionDetailsPage(); +export default new ActivityDetailsPage(); diff --git a/packages/e2e-tests/src/features/e2e/SendTransactionBundlesE2E.feature b/packages/e2e-tests/src/features/e2e/SendTransactionBundlesE2E.feature index 74c070c9c7..863d332e67 100644 --- a/packages/e2e-tests/src/features/e2e/SendTransactionBundlesE2E.feature +++ b/packages/e2e-tests/src/features/e2e/SendTransactionBundlesE2E.feature @@ -34,7 +34,7 @@ Feature: Send Transaction bundles - E2E When I navigate to Transactions extended page Then the Sent transaction is displayed with value: "4.50 tADA, 0.2333 LaceCoin3, 3 LaceCoin , +1" and tokens count 4 And I click and open recent transactions details until find transaction with correct hash - Then The Tx details are displayed as "package.core.transactionDetailBrowser.sent" for 4 tokens with following details: + Then The Tx details are displayed as "package.core.activityDetails.sent" for 4 tokens with following details: | address | ada | assets | | WalletFirstReceiveBundlesTransactionE2E | 1.34 tADA | 0.2333 LaceCoin3,1 LaceCoin | | WalletSecondReceiveBundlesTransactionE2E | 2.00 tADA | 2 LaceCoin | @@ -45,7 +45,7 @@ Feature: Send Transaction bundles - E2E And I navigate to Transactions extended page Then the Received transaction is displayed with value: "1.34 tADA, 0.2333 LaceCoin3, 1 LaceCoin" and tokens count 3 And I click and open recent transactions details until find transaction with correct hash - Then The Tx details are displayed as "package.core.transactionDetailBrowser.received" for 3 tokens with following details: + Then The Tx details are displayed as "package.core.activityDetails.received" for 3 tokens with following details: | address | ada | assets | | WalletSendBundlesTransactionE2E | 1.34 tADA | 0.2333 LaceCoin3,1 LaceCoin | When I open wallet: "WalletSecondReceiveBundlesTransactionE2E" in: extended mode @@ -53,7 +53,7 @@ Feature: Send Transaction bundles - E2E And I navigate to Transactions extended page Then the Received transaction is displayed with value: "3.16 tADA, 2 LaceCoin, 1 LaceCoin2" and tokens count 3 And I click and open recent transactions details until find transaction with correct hash - Then The Tx details are displayed as "package.core.transactionDetailBrowser.received" for 3 tokens with following details: + Then The Tx details are displayed as "package.core.activityDetails.received" for 3 tokens with following details: | address | ada | assets | | WalletSendBundlesTransactionE2E | 2.00 tADA | 2 LaceCoin | | WalletSendBundlesTransactionE2E | 1.16 tADA | 1 LaceCoin2 | diff --git a/packages/e2e-tests/src/features/e2e/SendTransactionDappE2E.feature b/packages/e2e-tests/src/features/e2e/SendTransactionDappE2E.feature index 717b962a34..da2477a5d3 100644 --- a/packages/e2e-tests/src/features/e2e/SendTransactionDappE2E.feature +++ b/packages/e2e-tests/src/features/e2e/SendTransactionDappE2E.feature @@ -24,13 +24,13 @@ Feature: Send Transactions from Dapp - E2E When I navigate to Transactions extended page Then the Sent transaction is displayed with value: "3.00 tADA" and tokens count 1 And I click on a transaction: 1 - Then The Tx details are displayed as "package.core.transactionDetailBrowser.sent" for ADA with value: 3.00 and wallet: "WalletReceiveSimpleTransactionE2E" address + Then The Tx details are displayed as "package.core.activityDetails.sent" for ADA with value: 3.00 and wallet: "WalletReceiveSimpleTransactionE2E" address When I open wallet: "WalletReceiveSimpleTransactionE2E" in: extended mode And Wallet is synced And I navigate to Transactions extended page Then the Received transaction is displayed with value: "3.00 tADA" and tokens count 1 And I click on a transaction: 1 - Then The Tx details are displayed as "package.core.transactionDetailBrowser.received" for ADA with value: 3.00 and wallet: "WalletSendSimpleTransactionE2E" address + Then The Tx details are displayed as "package.core.activityDetails.received" for ADA with value: 3.00 and wallet: "WalletSendSimpleTransactionE2E" address @LW-6797 @Testnet Scenario: Send Token from DApp E2E @@ -53,10 +53,10 @@ Feature: Send Transactions from Dapp - E2E When I navigate to Transactions extended page Then the Sent transaction is displayed with value: "1.38 tADA, 2 LaceCoin2" and tokens count 2 And I click on a transaction: 1 - Then The Tx details are displayed as "package.core.transactionDetailBrowser.sent" for ADA with value: "1.38" and LaceCoin2 with value: "2" and wallet: "WalletReceiveSimpleTransactionE2E" address + Then The Tx details are displayed as "package.core.activityDetails.sent" for ADA with value: "1.38" and LaceCoin2 with value: "2" and wallet: "WalletReceiveSimpleTransactionE2E" address When I open wallet: "WalletReceiveSimpleTransactionE2E" in: extended mode And Wallet is synced And I navigate to Transactions extended page Then the Received transaction is displayed with value: "1.38 tADA" and tokens count 2 And I click on a transaction: 1 - Then The Tx details are displayed as "package.core.transactionDetailBrowser.received" for ADA with value: "1.38" and LaceCoin2 with value: "2" and wallet: "WalletSendSimpleTransactionE2E" address + Then The Tx details are displayed as "package.core.activityDetails.received" for ADA with value: "1.38" and LaceCoin2 with value: "2" and wallet: "WalletSendSimpleTransactionE2E" address diff --git a/packages/e2e-tests/src/features/e2e/SendTransactionSimpleExtendedE2E.feature b/packages/e2e-tests/src/features/e2e/SendTransactionSimpleExtendedE2E.feature index bb7b8d1890..988d0df768 100644 --- a/packages/e2e-tests/src/features/e2e/SendTransactionSimpleExtendedE2E.feature +++ b/packages/e2e-tests/src/features/e2e/SendTransactionSimpleExtendedE2E.feature @@ -22,13 +22,13 @@ Feature: Send Simple Transactions - Extended view - E2E When I navigate to Transactions extended page Then the Sent transaction is displayed with value: "1.12 tADA" and tokens count 1 And I click and open recent transactions details until find transaction with correct hash - Then The Tx details are displayed as "package.core.transactionDetailBrowser.sent" for ADA with value: 1.12 and wallet: "WalletReceiveSimpleTransactionE2E" address + Then The Tx details are displayed as "package.core.activityDetails.sent" for ADA with value: 1.12 and wallet: "WalletReceiveSimpleTransactionE2E" address When I open wallet: "WalletReceiveSimpleTransactionE2E" in: extended mode And Wallet is synced And I navigate to Transactions extended page Then the Received transaction is displayed with value: "1.12 tADA" and tokens count 1 And I click and open recent transactions details until find transaction with correct hash - Then The Tx details are displayed as "package.core.transactionDetailBrowser.received" for ADA with value: 1.12 and wallet: "WalletSendSimpleTransactionE2E" address + Then The Tx details are displayed as "package.core.activityDetails.received" for ADA with value: 1.12 and wallet: "WalletSendSimpleTransactionE2E" address @LW-4677 Scenario: Extended-view - Self Transaction E2E diff --git a/packages/e2e-tests/src/features/e2e/SendTransactionSimplePopupE2E.feature b/packages/e2e-tests/src/features/e2e/SendTransactionSimplePopupE2E.feature index 1a2c609b0d..a177778dd2 100644 --- a/packages/e2e-tests/src/features/e2e/SendTransactionSimplePopupE2E.feature +++ b/packages/e2e-tests/src/features/e2e/SendTransactionSimplePopupE2E.feature @@ -22,13 +22,13 @@ Feature: Send Simple Transactions - Popup view - E2E When I navigate to Transactions popup page Then the Sent transaction is displayed with value: "1.12 tADA" and tokens count 1 And I click and open recent transactions details until find transaction with correct hash - Then The Tx details are displayed as "package.core.transactionDetailBrowser.sent" for ADA with value: 1.12 and wallet: "WalletReceiveSimpleTransactionE2E" address + Then The Tx details are displayed as "package.core.activityDetails.sent" for ADA with value: 1.12 and wallet: "WalletReceiveSimpleTransactionE2E" address When I open wallet: "WalletReceiveSimpleTransactionE2E" in: popup mode And Wallet is synced And I navigate to Transactions popup page Then the Received transaction is displayed with value: "1.12 tADA" and tokens count 1 And I click and open recent transactions details until find transaction with correct hash - Then The Tx details are displayed as "package.core.transactionDetailBrowser.received" for ADA with value: 1.12 and wallet: "WalletSendSimpleTransactionE2E" address + Then The Tx details are displayed as "package.core.activityDetails.received" for ADA with value: 1.12 and wallet: "WalletSendSimpleTransactionE2E" address @LW-4678 Scenario: Popup-view - Self Transaction E2E diff --git a/packages/e2e-tests/src/steps/multidelegationSteps.ts b/packages/e2e-tests/src/steps/multidelegationSteps.ts index 8b61976021..2e726c8a96 100644 --- a/packages/e2e-tests/src/steps/multidelegationSteps.ts +++ b/packages/e2e-tests/src/steps/multidelegationSteps.ts @@ -165,7 +165,7 @@ Then(/^I click "Close" button on staking success drawer$/, async () => { Then( /^the transaction details are displayed for staking (with|without) metadata$/, async (metadata: 'with' | 'without') => { - const expectedTransactionDetails = + const expectedActivityDetails = metadata === 'with' ? { transactionDescription: 'Delegation\n1 token', @@ -180,7 +180,7 @@ Then( poolID: testContext.load('poolID') as string }; - await transactionDetailsAssert.assertSeeTransactionDetails(expectedTransactionDetails); + await transactionDetailsAssert.assertSeeActivityDetails(expectedActivityDetails); } ); diff --git a/packages/e2e-tests/src/steps/nftsCommonSteps.ts b/packages/e2e-tests/src/steps/nftsCommonSteps.ts index b26100aab9..92bf22a7f0 100644 --- a/packages/e2e-tests/src/steps/nftsCommonSteps.ts +++ b/packages/e2e-tests/src/steps/nftsCommonSteps.ts @@ -64,9 +64,9 @@ Then( /^The Tx details are displayed as (sent|received) for NFT with name: "([^"]*)" and wallet: "([^"]*)" address$/, async (type: string, nftName: string, walletName: string) => { const typeTranslationKey = - type === 'sent' ? 'package.core.transactionDetailBrowser.sent' : 'package.core.transactionDetailBrowser.received'; + type === 'sent' ? 'package.core.activityDetails.sent' : 'package.core.activityDetails.received'; - const expectedTransactionDetails = { + const expectedActivityDetails = { transactionDescription: `${await t(typeTranslationKey)}\n(2)`, hash: String(testContext.load('txHashValue')), sentAssets: [`1 ${nftName}`], @@ -74,7 +74,7 @@ Then( recipientAddress: getTestWallet(walletName).address, status: 'Success' }; - await transactionDetailsAssert.assertSeeTransactionDetails(expectedTransactionDetails); + await transactionDetailsAssert.assertSeeActivityDetails(expectedActivityDetails); } ); diff --git a/packages/e2e-tests/src/steps/sendTransactionSimpleSteps.ts b/packages/e2e-tests/src/steps/sendTransactionSimpleSteps.ts index 785020d1c7..b76ed7a104 100644 --- a/packages/e2e-tests/src/steps/sendTransactionSimpleSteps.ts +++ b/packages/e2e-tests/src/steps/sendTransactionSimpleSteps.ts @@ -23,7 +23,7 @@ import indexedDB from '../fixture/indexedDB'; import transactionBundleAssert from '../assert/transaction/transactionBundleAssert'; import { getTestWallet, TestWalletName } from '../support/walletConfiguration'; import testContext from '../utils/testContext'; -import transactionDetailsAssert, { ExpectedTransactionDetails } from '../assert/transactionDetailsAssert'; +import transactionDetailsAssert, { ExpectedActivityDetails } from '../assert/transactionDetailsAssert'; import { t } from '../utils/translationService'; import nftsPageObject from '../pageobject/nftsPageObject'; import transactionsPageObject from '../pageobject/transactionsPageObject'; @@ -376,7 +376,7 @@ When(/^I save fee value$/, async () => { Then( /^The Tx details are displayed as "([^"]*)" for ADA with value: ([^"]*) and wallet: "([^"]*)" address$/, async (type: string, adaValue: string, walletName: string) => { - const expectedTransactionDetails: ExpectedTransactionDetails = { + const expectedActivityDetails: ExpectedActivityDetails = { transactionDescription: `${await t(type)}\n(1)`, hash: testContext.load('txHashValue'), transactionData: [ @@ -384,14 +384,14 @@ Then( ], status: 'Success' }; - await transactionDetailsAssert.assertSeeTransactionDetails(expectedTransactionDetails); + await transactionDetailsAssert.assertSeeActivityDetails(expectedActivityDetails); } ); Then( /^The Tx details are displayed as "([^"]*)" for ADA with value: "([^"]*)" and LaceCoin2 with value: "([^"]*)" and wallet: "([^"]*)" address$/, async (type: string, adaValue: string, laceCoin2Value: string, walletName: string) => { - const expectedTransactionDetails: ExpectedTransactionDetails = { + const expectedActivityDetails: ExpectedActivityDetails = { transactionDescription: `${await t(type)}\n(2)`, hash: testContext.load('txHashValue'), transactionData: [ @@ -403,7 +403,7 @@ Then( ], status: 'Success' }; - await transactionDetailsAssert.assertSeeTransactionDetails(expectedTransactionDetails); + await transactionDetailsAssert.assertSeeActivityDetails(expectedActivityDetails); } ); @@ -415,13 +415,13 @@ Then( entry.address = getTestWallet(entry.address).address; entry.assets = entry.assets.split(','); } - const expectedTransactionDetails = { + const expectedActivityDetails = { transactionDescription: `${await t(type)}\n(${numberOfTokens})`, hash: String(testContext.load('txHashValue')), transactionData: txData, status: 'Success' }; - await transactionDetailsAssert.assertSeeTransactionDetails(expectedTransactionDetails); + await transactionDetailsAssert.assertSeeActivityDetails(expectedActivityDetails); } ); diff --git a/packages/e2e-tests/src/steps/stakingSteps.ts b/packages/e2e-tests/src/steps/stakingSteps.ts index d30c35cef3..044fef4a89 100644 --- a/packages/e2e-tests/src/steps/stakingSteps.ts +++ b/packages/e2e-tests/src/steps/stakingSteps.ts @@ -163,7 +163,7 @@ Then(/^Each stake pool list item contains:$/, async (_ignored: string) => { }); Then(/^The Tx details are displayed for Staking (with|without) metadata$/, async (metadata: 'with' | 'without') => { - const expectedTransactionDetails = + const expectedActivityDetails = metadata === 'with' ? { transactionDescription: 'Delegation\n1 token', @@ -178,7 +178,7 @@ Then(/^The Tx details are displayed for Staking (with|without) metadata$/, async poolID: testContext.load('poolID') as string }; - await transactionDetailsAssert.assertSeeTransactionDetails(expectedTransactionDetails); + await transactionDetailsAssert.assertSeeActivityDetails(expectedActivityDetails); }); Then( diff --git a/packages/e2e-tests/src/steps/transactionsSteps.ts b/packages/e2e-tests/src/steps/transactionsSteps.ts index 38b57fd3b0..d83f24b101 100644 --- a/packages/e2e-tests/src/steps/transactionsSteps.ts +++ b/packages/e2e-tests/src/steps/transactionsSteps.ts @@ -99,7 +99,7 @@ Then( /^a side drawer is displayed showing the following information in (extended|popup) mode$/, // eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars async (mode: 'extended' | 'popup', _item: DataTable) => { - await transactionDetailsAssert.assertSeeTransactionDetailsUnfolded(mode); + await transactionDetailsAssert.assertSeeActivityDetailsUnfolded(mode); } ); @@ -118,7 +118,7 @@ When(/^I click on outputs dropdowns$/, async () => { Then( /^all inputs and outputs of the transactions are displayed in (extended|popup) mode$/, async (mode: 'extended' | 'popup') => { - await transactionDetailsAssert.assertSeeTransactionDetailsInputAndOutputs(mode); + await transactionDetailsAssert.assertSeeActivityDetailsInputAndOutputs(mode); } ); @@ -137,7 +137,7 @@ Then(/none of the input and output values is zero in (extended|popup) mode$/, as Then( /the amounts sent or received are displayed below the Tx hash in (extended|popup) mode$/, async (mode: 'extended' | 'popup') => { - await transactionDetailsAssert.assertSeeTransactionDetailsSummary(mode); + await transactionDetailsAssert.assertSeeActivityDetailsSummary(mode); } ); @@ -148,7 +148,7 @@ Then(/the Sender or Receiver is displayed$/, async () => { Then( /the amounts summary shows as many rows as assets sent or received minus 1 -ADA- in (extended|popup) mode$/, async (mode: 'extended' | 'popup') => { - await transactionDetailsAssert.assertSeeTransactionDetailsSummaryAmounts(mode); + await transactionDetailsAssert.assertSeeActivityDetailsSummaryAmounts(mode); } ); @@ -201,15 +201,15 @@ Then( /^I can see transaction (\d) has type "([^"]*)" and value "([^"]*)"$/, async (txIndex: number, txType: string, txAdaValue: string) => { await transactionsPageAssert.assertTableItemDetails(txIndex - 1, txType); - const expectedTransactionDetails: ExpectedTransactionRowAssetDetails = { + const expectedActivityDetails: ExpectedTransactionRowAssetDetails = { type: txType, tokensAmount: txAdaValue, tokensCount: 0 }; - await transactionsPageAssert.assertSeeTransactionRowWithAssetDetails(txIndex - 1, expectedTransactionDetails); + await transactionsPageAssert.assertSeeTransactionRowWithAssetDetails(txIndex - 1, expectedActivityDetails); } ); Then(/^Transaction details drawer (is|is not) displayed$/, async (state: 'is' | 'is not') => { - await transactionDetailsAssert.assertSeeTransactionDetailsDrawer(state === 'is'); + await transactionDetailsAssert.assertSeeActivityDetailsDrawer(state === 'is'); }); diff --git a/packages/staking/src/features/overview/helpers/hasPendingDelegationTransaction.ts b/packages/staking/src/features/overview/helpers/hasPendingDelegationTransaction.ts index 6087654249..509a8073d1 100644 --- a/packages/staking/src/features/overview/helpers/hasPendingDelegationTransaction.ts +++ b/packages/staking/src/features/overview/helpers/hasPendingDelegationTransaction.ts @@ -1,7 +1,7 @@ -import { AssetActivityListProps, TransactionType } from '@lace/core'; import flatMap from 'lodash/flatMap'; +import type { ActivityType, AssetActivityListProps } from '@lace/core'; -const DelegationTransactionTypes: Set = new Set([ +const DelegationTransactionTypes: Set = new Set([ 'delegation', 'delegationRegistration', 'delegationDeregistration',