• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

input-output-hk / lace / 8521699122

02 Apr 2024 11:02AM UTC coverage: 53.437% (-0.4%) from 53.839%
8521699122

push

github

f055f2
pczeglik-iohk
chore: cardano services point out live envs

2293 of 5275 branches covered (43.47%)

Branch coverage included in aggregate %.

5239 of 8820 relevant lines covered (59.4%)

54.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.05
/packages/core/src/ui/components/Activity/AssetActivityItem.tsx
1
/* eslint-disable unicorn/consistent-function-scoping */
2
/* eslint-disable react/no-multi-comp */
3
import React, { useMemo, useRef, useEffect, useCallback } from 'react';
10✔
4
import debounce from 'lodash/debounce';
10✔
5
import { Image, Tooltip } from 'antd';
10✔
6
import cn from 'classnames';
10✔
7
import Icon from '@ant-design/icons';
10✔
8
import { getTextWidth } from '@lace/common';
10✔
9
import { ReactComponent as PendingIcon } from '../../assets/icons/pending.component.svg';
10✔
10
import { ReactComponent as ErrorIcon } from '../../assets/icons/error.component.svg';
10✔
11
import pluralize from 'pluralize';
10✔
12
import { txIconSize } from '@src/ui/utils/icon-size';
10✔
13
import { useTranslate } from '@src/ui/hooks';
10✔
14
import { DelegationActivityType, TransactionActivityType } from '../ActivityDetail/types';
10✔
15
import type { ActivityType } from '../ActivityDetail/types';
16
import styles from './AssetActivityItem.module.scss';
10✔
17
import { ActivityTypeIcon } from '../ActivityDetail/ActivityTypeIcon';
10✔
18

19
export type ActivityAssetInfo = { ticker: string };
20
export type ActivityAssetProp = { id: string; val: string; info?: ActivityAssetInfo };
21

22
export enum ActivityStatus {
10✔
23
  SUCCESS = 'success',
10✔
24
  PENDING = 'sending',
10✔
25
  ERROR = 'error',
10✔
26
  SPENDABLE = 'spendable'
10✔
27
}
28

29
const DEFAULT_DEBOUNCE = 200;
10✔
30

31
export interface AssetActivityItemProps {
32
  id?: string;
33
  /**
34
   * Amount formated with symbol (e.g. 50 ADA)
35
   */
36
  amount: string;
37
  /**
38
   * Amount in Fiat currency (e.g. 125$)
39
   */
40
  fiatAmount: string;
41
  /**
42
   * Action to be executed when clicking on the item
43
   */
44
  onClick?: () => unknown;
45
  /**
46
   * Activity status: `sending` | `success` | 'error
47
   */
48
  status?: ActivityStatus;
49
  /**
50
   * Activity or asset custom icon
51
   */
52
  customIcon?: string;
53
  /**
54
   * Activity type
55
   */
56
  type?: ActivityType;
57
  /**
58
   * Number of assets (default: 1)
59
   */
60
  assetsNumber?: number;
61
  formattedTimestamp: string;
62
  /**
63
   * assets details
64
   */
65
  assets?: ActivityAssetProp[];
66
}
67

68
const DELEGATION_ASSET_NUMBER = 1;
10✔
69

70
interface ActivityStatusIconProps {
71
  status: ActivityStatus;
72
  type: ActivityType;
73
}
74

75
const offsetMargin = 10;
10✔
76

77
const ActivityStatusIcon = ({ status, type }: ActivityStatusIconProps) => {
10✔
78
  const iconStyle = { fontSize: txIconSize() };
110✔
79
  switch (status) {
110✔
80
    case ActivityStatus.SUCCESS:
96!
81
      return <ActivityTypeIcon type={type} />;
4✔
82
    case ActivityStatus.SPENDABLE:
83
      return <ActivityTypeIcon type={TransactionActivityType.rewards} />;
×
84
    case ActivityStatus.PENDING:
85
      return <Icon component={PendingIcon} style={iconStyle} data-testid="activity-status" />;
×
86
    case ActivityStatus.ERROR:
87
    default:
88
      return <Icon component={ErrorIcon} style={iconStyle} data-testid="activity-status" />;
106✔
89
  }
90
};
91

92
const negativeBalanceStyling: Set<Partial<ActivityType>> = new Set([
10✔
93
  TransactionActivityType.outgoing,
94
  DelegationActivityType.delegationRegistration,
95
  TransactionActivityType.self,
96
  DelegationActivityType.delegation
97
]);
98

99
// TODO: Handle pluralization and i18n of assetsNumber when we will have more than Ada.
100
export const AssetActivityItem = ({
10✔
101
  amount,
102
  fiatAmount,
103
  onClick,
104
  status,
105
  customIcon,
106
  type,
107
  assetsNumber = 1,
24✔
108
  assets,
109
  formattedTimestamp
110
}: AssetActivityItemProps): React.ReactElement => {
111
  const { t } = useTranslate();
110✔
112
  const ref = useRef<HTMLHeadingElement>(null);
110✔
113
  const [assetsToShow, setAssetsToShow] = React.useState<number>(0);
110✔
114

115
  const getText = useCallback(
110✔
116
    (items: number): { text: string; suffix: string } => {
117
      if (type in DelegationActivityType || type === TransactionActivityType.self) return { text: amount, suffix: '' };
68✔
118

119
      const assetsIdsText = assets
56!
120
        ?.slice(0, items)
121
        .map(({ val, info }) => `${val} ${info?.ticker || '"?"'}`)
8!
122
        .join(', ');
123
      const suffix = assets?.length - items > 0 ? `, +${assets.length - items}` : '';
56!
124
      const appendedAssetId = assetsIdsText ? `, ${assetsIdsText}` : '';
56✔
125
      return {
56✔
126
        text: `${amount}${appendedAssetId}`,
127
        suffix: suffix && ` ${suffix}`
52✔
128
      };
129
    },
130
    [assets, amount, type]
131
  );
132

133
  const setText = useCallback(() => {
110✔
134
    if (!assets || assets.length === 0) return;
12!
135
    let items = assets.length;
12✔
136
    let text = getText(items);
12✔
137
    while (
12✔
138
      ref &&
14✔
139
      ref.current &&
140
      items > 0 &&
141
      getTextWidth(`${text.text}${text.suffix}`, ref.current) > ref.current.offsetWidth - offsetMargin
142
    ) {
143
      items -= 1;
×
144
      text = getText(items);
×
145
    }
146
    if (ref?.current) setAssetsToShow(items);
12!
147
  }, [assets, getText]);
148

149
  const debouncedSetText = useMemo(() => debounce(setText, DEFAULT_DEBOUNCE), [setText]);
110✔
150

151
  useEffect(() => {
110✔
152
    window.addEventListener('resize', debouncedSetText);
54✔
153
    debouncedSetText();
54✔
154
    return () => {
54✔
155
      window.removeEventListener('resize', debouncedSetText);
54✔
156
    };
157
  }, [debouncedSetText]);
158

159
  const isPendingTx = status === ActivityStatus.PENDING;
110✔
160
  const assetsText = useMemo(() => getText(assetsToShow), [getText, assetsToShow]);
110✔
161

162
  const assetAmountContent =
163
    type in DelegationActivityType ? (
110✔
164
      <p data-testid="tokens-amount" className={styles.description}>
55✔
165
        {DELEGATION_ASSET_NUMBER} {t('package.core.assetActivityItem.entry.token')}
166
      </p>
167
    ) : (
168
      <p data-testid="tokens-amount" className={styles.description}>
169
        {pluralize('package.core.assetActivityItem.entry.token', assetsNumber, true)}
170
      </p>
171
    );
172
  const descriptionContent = formattedTimestamp ? (
110✔
173
    <p data-testid="timestamp" className={styles.description}>
55!
174
      {formattedTimestamp}
175
    </p>
176
  ) : (
177
    assetAmountContent
178
  );
179

180
  const isNegativeBalance = negativeBalanceStyling.has(type);
110✔
181

182
  return (
110✔
183
    <div data-testid="asset-activity-item" onClick={onClick} className={styles.assetActivityItem}>
184
      <div className={styles.leftSide}>
185
        <div data-testid="asset-icon" className={styles.iconWrapper}>
186
          {customIcon ? (
187
            <Image src={customIcon} className={styles.icon} preview={false} alt="asset image" />
55!
188
          ) : (
189
            <ActivityStatusIcon status={status} type={type} />
190
          )}
191
        </div>
192
        <div data-testid="asset-info" className={styles.info}>
193
          <h6 data-testid="transaction-type" className={styles.title}>
194
            {isPendingTx && type !== TransactionActivityType.self && !(type in DelegationActivityType)
55!
195
              ? t('package.core.assetActivityItem.entry.name.sending')
55!
196
              : t(`package.core.assetActivityItem.entry.name.${type}`)}
197
          </h6>
198
          {descriptionContent}
199
        </div>
200
      </div>
201
      <div data-testid="asset-amount" className={styles.rightSide}>
202
        <h6
203
          data-testid="total-amount"
204
          className={cn(styles.title, {
205
            [styles.negativeBalance]: isNegativeBalance,
206
            [styles.positiveBalance]: !isNegativeBalance
207
          })}
208
          ref={ref}
209
        >
210
          <span>
211
            <span data-testid="balance">
212
              {isNegativeBalance ? '-' : ''}
55✔
213
              {assetsText.text}
214
            </span>
215
            {assetsText.suffix && (
103✔
216
              <Tooltip
217
                overlayClassName={styles.tooltip}
218
                title={
219
                  <>
220
                    {assets?.slice(assetsToShow, assets.length).map(({ id, val, info }) => (
144!
221
                      <div key={id} className={styles.tooltipItem}>{`${val} ${info?.ticker || '?'}`}</div>
96✔
222
                    ))}
223
                  </>
224
                }
225
              >
226
                {assetsText.suffix}
227
              </Tooltip>
228
            )}
229
          </span>
230
        </h6>
231
        <p data-testid="fiat-amount" className={styles.description}>
232
          {fiatAmount}
233
        </p>
234
      </div>
235
    </div>
236
  );
237
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc