@dynamic-labs/sdk-react-core 4.83.2-alpha.0 → 4.84.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/package.cjs +1 -1
  3. package/package.js +1 -1
  4. package/package.json +12 -12
  5. package/src/index.cjs +4 -0
  6. package/src/index.d.ts +2 -2
  7. package/src/index.js +2 -0
  8. package/src/lib/components/SendBalanceForm/SendBalanceForm.cjs +26 -1
  9. package/src/lib/components/SendBalanceForm/SendBalanceForm.js +26 -1
  10. package/src/lib/components/SendBalancePageLayout/SendBalancePageLayout.cjs +6 -1
  11. package/src/lib/components/SendBalancePageLayout/SendBalancePageLayout.js +6 -1
  12. package/src/lib/main.global.cjs +1 -1
  13. package/src/lib/main.global.js +1 -1
  14. package/src/lib/utils/hooks/index.d.ts +2 -0
  15. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.cjs +24 -4
  16. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.d.ts +10 -2
  17. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.js +24 -4
  18. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.cjs +14 -3
  19. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.d.ts +5 -1
  20. package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.js +14 -3
  21. package/src/lib/utils/hooks/useAleoShieldedBalances/useAleoShieldedBalances.cjs +14 -0
  22. package/src/lib/utils/hooks/useAleoShieldedBalances/useAleoShieldedBalances.js +14 -0
  23. package/src/lib/utils/hooks/usePrivateTokenBalances/index.d.ts +2 -0
  24. package/src/lib/utils/hooks/usePrivateTokenBalances/usePrivateTokenBalances.cjs +19 -0
  25. package/src/lib/utils/hooks/usePrivateTokenBalances/usePrivateTokenBalances.d.ts +9 -0
  26. package/src/lib/utils/hooks/usePrivateTokenBalances/usePrivateTokenBalances.js +15 -0
  27. package/src/lib/utils/hooks/useSyncDeviceRegistrationFlow/useSyncDeviceRegistrationFlow.cjs +8 -0
  28. package/src/lib/utils/hooks/useSyncDeviceRegistrationFlow/useSyncDeviceRegistrationFlow.js +9 -1
  29. package/src/lib/utils/hooks/useWalletDelegation/useWalletDelegation.cjs +19 -16
  30. package/src/lib/utils/hooks/useWalletDelegation/useWalletDelegation.d.ts +8 -0
  31. package/src/lib/utils/hooks/useWalletDelegation/useWalletDelegation.js +19 -17
  32. package/src/lib/utils/hooks/useWalletPassword/useWalletPassword.d.ts +4 -4
  33. package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/ActiveWalletBalance.cjs +138 -21
  34. package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/ActiveWalletBalance.js +139 -22
  35. package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/optimisticShield.cjs +134 -0
  36. package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/optimisticShield.d.ts +69 -0
  37. package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/optimisticShield.js +127 -0
  38. package/src/lib/widgets/DynamicWidget/views/ManageTrustedDevicesView/ManageTrustedDevicesView.cjs +12 -8
  39. package/src/lib/widgets/DynamicWidget/views/ManageTrustedDevicesView/ManageTrustedDevicesView.js +8 -4
@@ -0,0 +1,69 @@
1
+ import { TokenBalance } from '@dynamic-labs/sdk-api-core';
2
+ /**
3
+ * In-memory record of a shield operation that has been accepted by the
4
+ * prover/relay but not yet observed on-chain by either the public
5
+ * balance mapping (Provable's `/mapping/balances/...` endpoint) or
6
+ * the RecordScanner index. Until reconciliation drops it, the entry
7
+ * forces the widget to display the post-shield state — unshielded
8
+ * rawBalance reduced by `amount`, shielded rawBalance increased by
9
+ * the same — so the Shield Manually CTA disappears the moment the
10
+ * shield is broadcast instead of flickering back into view during
11
+ * the indexing tail and tempting the user into a second shield.
12
+ */
13
+ export type OptimisticShieldEntry = {
14
+ tokenAddress: string;
15
+ isNative: boolean;
16
+ amount: bigint;
17
+ preShieldUnshieldedRaw: bigint;
18
+ preShieldShieldedRaw: bigint;
19
+ symbol: string;
20
+ name: string;
21
+ decimals: number;
22
+ logoURI: string;
23
+ price?: number;
24
+ createdAtMs: number;
25
+ };
26
+ export declare const OPTIMISTIC_SHIELD_STALE_MS = 180000;
27
+ /**
28
+ * Reduce each unshielded token's `rawBalance` by the sum of pending
29
+ * optimistic shields for the same `(address, isNative)`, clamped to 0.
30
+ * `balance` and `marketValue` scale by the same ratio so the fiat
31
+ * column doesn't disagree with the optimistic raw balance.
32
+ *
33
+ * Tokens with no pending optimistic shield pass through by reference
34
+ * (no copy) so downstream memos / identity comparisons don't churn on
35
+ * every render.
36
+ */
37
+ export declare const applyOptimisticUnshieldedDeductions: (balances: TokenBalance[], optimisticShields: ReadonlyArray<OptimisticShieldEntry>) => TokenBalance[];
38
+ /**
39
+ * Apply optimistic shield additions to the shielded balance list. For
40
+ * each optimistic shield with a matching `(address, isNative)` entry
41
+ * already in `shielded`, bump that entry; otherwise prepend a fresh
42
+ * synthesized row built from the entry's captured metadata.
43
+ *
44
+ * Returns a fresh array only when at least one optimistic shield
45
+ * applies. When `optimisticShields` is empty we pass through the
46
+ * input array by reference so consumers that compare by identity
47
+ * don't churn.
48
+ */
49
+ export declare const applyOptimisticShieldedAdditions: (shielded: TokenBalance[], optimisticShields: ReadonlyArray<OptimisticShieldEntry>) => TokenBalance[];
50
+ /**
51
+ * Filter the optimistic shield list down to entries that are still
52
+ * relevant. An entry is dropped when any of:
53
+ *
54
+ * - the unshielded server feed reports a `rawBalance` strictly less
55
+ * than `preShieldUnshieldedRaw` for that token (the burn was
56
+ * observed on-chain),
57
+ * - the shielded server feed reports a `rawBalance` strictly greater
58
+ * than `preShieldShieldedRaw` for that token (the new record was
59
+ * indexed),
60
+ * - the unshielded server no longer lists the token at all (rare,
61
+ * but means the public balance is effectively zero — our
62
+ * optimistic deduct is moot),
63
+ * - the entry is older than `staleMs` (safety net for shields whose
64
+ * on-chain effect is masked by parallel inbound credits).
65
+ *
66
+ * Pure / module-scope so it's trivially unit-testable; the widget
67
+ * just calls it on every server-side balance update.
68
+ */
69
+ export declare const reconcileOptimisticShields: (optimisticShields: ReadonlyArray<OptimisticShieldEntry>, unshieldedTokenBalances: TokenBalance[], shieldedTokenBalances: TokenBalance[], nowMs: number, staleMs?: number) => OptimisticShieldEntry[];
@@ -0,0 +1,127 @@
1
+ 'use client'
2
+ // Default safety-net stale window for optimistic entries. Tuned to
3
+ // comfortably outlast the post-shield polling schedule (~106s) so
4
+ // reconciliation has a chance to fire on the real server-side state
5
+ // before the safety net trips.
6
+ const OPTIMISTIC_SHIELD_STALE_MS = 180000;
7
+ const matches = (entry, token) => {
8
+ if (entry.isNative)
9
+ return Boolean(token.isNative);
10
+ return !token.isNative && entry.tokenAddress === token.address;
11
+ };
12
+ /**
13
+ * Reduce each unshielded token's `rawBalance` by the sum of pending
14
+ * optimistic shields for the same `(address, isNative)`, clamped to 0.
15
+ * `balance` and `marketValue` scale by the same ratio so the fiat
16
+ * column doesn't disagree with the optimistic raw balance.
17
+ *
18
+ * Tokens with no pending optimistic shield pass through by reference
19
+ * (no copy) so downstream memos / identity comparisons don't churn on
20
+ * every render.
21
+ */
22
+ const applyOptimisticUnshieldedDeductions = (balances, optimisticShields) => {
23
+ if (optimisticShields.length === 0)
24
+ return balances;
25
+ return balances.map((token) => {
26
+ var _a, _b;
27
+ const adjustments = optimisticShields.filter((o) => matches(o, token));
28
+ if (adjustments.length === 0)
29
+ return token;
30
+ const currentRaw = BigInt(Math.round((_a = token.rawBalance) !== null && _a !== void 0 ? _a : 0));
31
+ const totalAdjustment = adjustments.reduce((sum, o) => sum + o.amount, BigInt(0));
32
+ const newRaw = currentRaw > totalAdjustment ? currentRaw - totalAdjustment : BigInt(0);
33
+ // Ratio-scale display-units balance + fiat so they stay consistent
34
+ // with the optimistic raw value. `currentRaw === 0n` happens when
35
+ // the server feed has already caught up to zero but reconciliation
36
+ // hasn't fired yet; treat ratio as 0 to keep display at 0.
37
+ const ratio = currentRaw > BigInt(0) ? Number(newRaw) / Number(currentRaw) : 0;
38
+ return Object.assign(Object.assign({}, token), { balance: ((_b = token.balance) !== null && _b !== void 0 ? _b : 0) * ratio, marketValue: token.marketValue !== undefined ? token.marketValue * ratio : undefined, rawBalance: Number(newRaw), rawBalanceString: newRaw.toString() });
39
+ });
40
+ };
41
+ /**
42
+ * Apply optimistic shield additions to the shielded balance list. For
43
+ * each optimistic shield with a matching `(address, isNative)` entry
44
+ * already in `shielded`, bump that entry; otherwise prepend a fresh
45
+ * synthesized row built from the entry's captured metadata.
46
+ *
47
+ * Returns a fresh array only when at least one optimistic shield
48
+ * applies. When `optimisticShields` is empty we pass through the
49
+ * input array by reference so consumers that compare by identity
50
+ * don't churn.
51
+ */
52
+ const applyOptimisticShieldedAdditions = (shielded, optimisticShields) => {
53
+ if (optimisticShields.length === 0)
54
+ return shielded;
55
+ const consumedIndices = new Set();
56
+ const merged = shielded.map((token) => {
57
+ var _a, _b;
58
+ const adjustments = optimisticShields.filter((o, idx) => {
59
+ if (!matches(o, token))
60
+ return false;
61
+ consumedIndices.add(idx);
62
+ return true;
63
+ });
64
+ if (adjustments.length === 0)
65
+ return token;
66
+ const totalAtomic = adjustments.reduce((sum, o) => sum + o.amount, BigInt(0));
67
+ const newRaw = BigInt(Math.round((_a = token.rawBalance) !== null && _a !== void 0 ? _a : 0)) + totalAtomic;
68
+ const decimals = (_b = token.decimals) !== null && _b !== void 0 ? _b : 0;
69
+ const denominator = Math.pow(10, decimals);
70
+ const newDisplay = Number(newRaw) / denominator;
71
+ return Object.assign(Object.assign({}, token), { balance: newDisplay, marketValue: token.price !== undefined
72
+ ? newDisplay * token.price
73
+ : token.marketValue, rawBalance: Number(newRaw), rawBalanceString: newRaw.toString() });
74
+ });
75
+ // Prepend synthesized rows for any optimistic shield that didn't
76
+ // fold into an existing shielded row above. New rows go to the top
77
+ // of the list so the just-shielded token is immediately visible.
78
+ const synthesized = [];
79
+ optimisticShields.forEach((entry, idx) => {
80
+ if (consumedIndices.has(idx))
81
+ return;
82
+ const denominator = Math.pow(10, entry.decimals);
83
+ const display = Number(entry.amount) / denominator;
84
+ const marketValue = entry.price !== undefined ? display * entry.price : undefined;
85
+ synthesized.push(Object.assign(Object.assign({ address: entry.tokenAddress, balance: display, decimals: entry.decimals, isNative: entry.isNative, logoURI: entry.logoURI, name: entry.name, rawBalance: Number(entry.amount), rawBalanceString: entry.amount.toString(), symbol: entry.symbol }, (entry.price !== undefined ? { price: entry.price } : {})), (marketValue !== undefined ? { marketValue } : {})));
86
+ });
87
+ return [...synthesized, ...merged];
88
+ };
89
+ /**
90
+ * Filter the optimistic shield list down to entries that are still
91
+ * relevant. An entry is dropped when any of:
92
+ *
93
+ * - the unshielded server feed reports a `rawBalance` strictly less
94
+ * than `preShieldUnshieldedRaw` for that token (the burn was
95
+ * observed on-chain),
96
+ * - the shielded server feed reports a `rawBalance` strictly greater
97
+ * than `preShieldShieldedRaw` for that token (the new record was
98
+ * indexed),
99
+ * - the unshielded server no longer lists the token at all (rare,
100
+ * but means the public balance is effectively zero — our
101
+ * optimistic deduct is moot),
102
+ * - the entry is older than `staleMs` (safety net for shields whose
103
+ * on-chain effect is masked by parallel inbound credits).
104
+ *
105
+ * Pure / module-scope so it's trivially unit-testable; the widget
106
+ * just calls it on every server-side balance update.
107
+ */
108
+ const reconcileOptimisticShields = (optimisticShields, unshieldedTokenBalances, shieldedTokenBalances, nowMs, staleMs = OPTIMISTIC_SHIELD_STALE_MS) => optimisticShields.filter((entry) => {
109
+ var _a, _b;
110
+ if (nowMs - entry.createdAtMs > staleMs)
111
+ return false;
112
+ const unshielded = unshieldedTokenBalances.find((t) => matches(entry, t));
113
+ if (!unshielded)
114
+ return false;
115
+ const serverUnshielded = BigInt(Math.round((_a = unshielded.rawBalance) !== null && _a !== void 0 ? _a : 0));
116
+ if (serverUnshielded < entry.preShieldUnshieldedRaw)
117
+ return false;
118
+ const shielded = shieldedTokenBalances.find((t) => matches(entry, t));
119
+ const serverShielded = shielded
120
+ ? BigInt(Math.round((_b = shielded.rawBalance) !== null && _b !== void 0 ? _b : 0))
121
+ : BigInt(0);
122
+ if (serverShielded > entry.preShieldShieldedRaw)
123
+ return false;
124
+ return true;
125
+ });
126
+
127
+ export { OPTIMISTIC_SHIELD_STALE_MS, applyOptimisticShieldedAdditions, applyOptimisticUnshieldedDeductions, reconcileOptimisticShields };
@@ -7,7 +7,8 @@ var _tslib = require('../../../../../../_virtual/_tslib.cjs');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
8
  var React = require('react');
9
9
  var reactI18next = require('react-i18next');
10
- var client = require('@dynamic-labs-sdk/client');
10
+ var client$1 = require('@dynamic-labs-sdk/client');
11
+ var client = require('../../../../client/client.cjs');
11
12
  var IconButton = require('../../../../components/IconButton/IconButton.cjs');
12
13
  var ModalHeader = require('../../../../components/ModalHeader/ModalHeader.cjs');
13
14
  var Skeleton = require('../../../../components/Skeleton/Skeleton.cjs');
@@ -26,24 +27,27 @@ const ManageTrustedDevicesView = () => {
26
27
  var _a, _b;
27
28
  const { t } = reactI18next.useTranslation();
28
29
  const { setDynamicWidgetView } = DynamicWidgetContext.useWidgetContext();
30
+ const dynamicClient = client.useDynamicClient();
29
31
  const [deviceIdToRemove, setDeviceIdToRemove] = React.useState(null);
30
32
  const [showRemoveAll, setShowRemoveAll] = React.useState(false);
31
- const { data: devices, isLoading, retrigger, } = usePromise.usePromise(() => client.getRegisteredDevices(), {
33
+ const { data: devices, isLoading, retrigger, } = usePromise.usePromise(() => client$1.getRegisteredDevices(), {
32
34
  initialData: [],
33
35
  });
34
36
  const handleBackClick = React.useCallback(() => setDynamicWidgetView('account-and-security-settings'), [setDynamicWidgetView]);
35
37
  const handleConfirmRemoveDevice = React.useCallback(() => _tslib.__awaiter(void 0, void 0, void 0, function* () {
36
38
  if (!deviceIdToRemove)
37
39
  return;
38
- yield client.revokeRegisteredDevice({ deviceRegistrationId: deviceIdToRemove });
40
+ yield client$1.revokeRegisteredDevice({ deviceRegistrationId: deviceIdToRemove });
39
41
  setDeviceIdToRemove(null);
40
- retrigger();
41
- }), [deviceIdToRemove, retrigger]);
42
+ // Revoking the current device logs the user out; skip the refetch to avoid a 401.
43
+ if (dynamicClient.user)
44
+ retrigger();
45
+ }), [deviceIdToRemove, dynamicClient, retrigger]);
42
46
  const handleConfirmRemoveAll = React.useCallback(() => _tslib.__awaiter(void 0, void 0, void 0, function* () {
43
- yield client.revokeAllRegisteredDevices();
47
+ yield client$1.revokeAllRegisteredDevices();
44
48
  setShowRemoveAll(false);
45
- retrigger();
46
- }), [retrigger]);
49
+ // revokeAllRegisteredDevices always logs the user out; no refetch needed.
50
+ }), []);
47
51
  const backButton = (jsxRuntime.jsx(IconButton.IconButton, { type: 'button', onClick: handleBackClick, "data-testid": 'back-button', children: jsxRuntime.jsx(chevronLeft.ReactComponent, {}) }));
48
52
  return (jsxRuntime.jsxs("div", { className: 'manage-trusted-devices-view', children: [jsxRuntime.jsx(ModalHeader.ModalHeader, { leading: backButton, children: jsxRuntime.jsx("div", { className: 'send-balance-page-layout__header-content', children: jsxRuntime.jsx(Typography.Typography, { variant: 'title', color: 'primary', copykey: 'dyn_device_management.manage_title', children: t('dyn_device_management.manage_title') }) }) }), jsxRuntime.jsxs("div", { className: 'manage-trusted-devices-view__scroll-container', children: [isLoading && (jsxRuntime.jsx(Skeleton.Skeleton, { count: 3, className: 'manage-trusted-devices-view__skeleton', dataTestId: 'trusted-devices-skeleton' })), !isLoading && devices.length === 0 && (jsxRuntime.jsx(Typography.Typography, { className: 'manage-trusted-devices-view__no-devices', variant: 'body_normal', color: 'secondary', copykey: 'dyn_device_management.no_devices', children: t('dyn_device_management.no_devices') })), !isLoading &&
49
53
  devices.map((device) => device.userAgent ? (jsxRuntime.jsx(TrustedDeviceTile.TrustedDeviceTile, { userAgent: device.userAgent, createdAt: device.createdAt, isCurrentDevice: device.isCurrentDevice, onClickDelete: () => setDeviceIdToRemove(device.id) }, device.id)) : null)] }), !isLoading && devices.length > 0 && (jsxRuntime.jsx("div", { className: 'manage-trusted-devices-view__footer', children: jsxRuntime.jsx(TextButton.TextButton, { className: 'manage-trusted-devices-view__remove-all-button', onClick: () => setShowRemoveAll(true), "data-testid": 'remove-all-devices-button', children: t('dyn_device_management.remove_all_devices') }) })), deviceIdToRemove !== null && (jsxRuntime.jsx(RemoveDevicePrompt.RemoveDevicePrompt, { deviceUserAgent: (_b = (_a = devices.find((d) => d.id === deviceIdToRemove)) === null || _a === void 0 ? void 0 : _a.userAgent) !== null && _b !== void 0 ? _b : '', onConfirm: handleConfirmRemoveDevice, onCancel: () => setDeviceIdToRemove(null) })), showRemoveAll && (jsxRuntime.jsx(RemoveAllDevicesPrompt.RemoveAllDevicesPrompt, { onConfirm: handleConfirmRemoveAll, onCancel: () => setShowRemoveAll(false) }))] }));
@@ -4,6 +4,7 @@ import { jsx, jsxs } from 'react/jsx-runtime';
4
4
  import { useState, useCallback } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { getRegisteredDevices, revokeRegisteredDevice, revokeAllRegisteredDevices } from '@dynamic-labs-sdk/client';
7
+ import { useDynamicClient } from '../../../../client/client.js';
7
8
  import { IconButton } from '../../../../components/IconButton/IconButton.js';
8
9
  import { ModalHeader } from '../../../../components/ModalHeader/ModalHeader.js';
9
10
  import { Skeleton } from '../../../../components/Skeleton/Skeleton.js';
@@ -22,6 +23,7 @@ const ManageTrustedDevicesView = () => {
22
23
  var _a, _b;
23
24
  const { t } = useTranslation();
24
25
  const { setDynamicWidgetView } = useWidgetContext();
26
+ const dynamicClient = useDynamicClient();
25
27
  const [deviceIdToRemove, setDeviceIdToRemove] = useState(null);
26
28
  const [showRemoveAll, setShowRemoveAll] = useState(false);
27
29
  const { data: devices, isLoading, retrigger, } = usePromise(() => getRegisteredDevices(), {
@@ -33,13 +35,15 @@ const ManageTrustedDevicesView = () => {
33
35
  return;
34
36
  yield revokeRegisteredDevice({ deviceRegistrationId: deviceIdToRemove });
35
37
  setDeviceIdToRemove(null);
36
- retrigger();
37
- }), [deviceIdToRemove, retrigger]);
38
+ // Revoking the current device logs the user out; skip the refetch to avoid a 401.
39
+ if (dynamicClient.user)
40
+ retrigger();
41
+ }), [deviceIdToRemove, dynamicClient, retrigger]);
38
42
  const handleConfirmRemoveAll = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
39
43
  yield revokeAllRegisteredDevices();
40
44
  setShowRemoveAll(false);
41
- retrigger();
42
- }), [retrigger]);
45
+ // revokeAllRegisteredDevices always logs the user out; no refetch needed.
46
+ }), []);
43
47
  const backButton = (jsx(IconButton, { type: 'button', onClick: handleBackClick, "data-testid": 'back-button', children: jsx(SvgChevronLeft, {}) }));
44
48
  return (jsxs("div", { className: 'manage-trusted-devices-view', children: [jsx(ModalHeader, { leading: backButton, children: jsx("div", { className: 'send-balance-page-layout__header-content', children: jsx(Typography, { variant: 'title', color: 'primary', copykey: 'dyn_device_management.manage_title', children: t('dyn_device_management.manage_title') }) }) }), jsxs("div", { className: 'manage-trusted-devices-view__scroll-container', children: [isLoading && (jsx(Skeleton, { count: 3, className: 'manage-trusted-devices-view__skeleton', dataTestId: 'trusted-devices-skeleton' })), !isLoading && devices.length === 0 && (jsx(Typography, { className: 'manage-trusted-devices-view__no-devices', variant: 'body_normal', color: 'secondary', copykey: 'dyn_device_management.no_devices', children: t('dyn_device_management.no_devices') })), !isLoading &&
45
49
  devices.map((device) => device.userAgent ? (jsx(TrustedDeviceTile, { userAgent: device.userAgent, createdAt: device.createdAt, isCurrentDevice: device.isCurrentDevice, onClickDelete: () => setDeviceIdToRemove(device.id) }, device.id)) : null)] }), !isLoading && devices.length > 0 && (jsx("div", { className: 'manage-trusted-devices-view__footer', children: jsx(TextButton, { className: 'manage-trusted-devices-view__remove-all-button', onClick: () => setShowRemoveAll(true), "data-testid": 'remove-all-devices-button', children: t('dyn_device_management.remove_all_devices') }) })), deviceIdToRemove !== null && (jsx(RemoveDevicePrompt, { deviceUserAgent: (_b = (_a = devices.find((d) => d.id === deviceIdToRemove)) === null || _a === void 0 ? void 0 : _a.userAgent) !== null && _b !== void 0 ? _b : '', onConfirm: handleConfirmRemoveDevice, onCancel: () => setDeviceIdToRemove(null) })), showRemoveAll && (jsx(RemoveAllDevicesPrompt, { onConfirm: handleConfirmRemoveAll, onCancel: () => setShowRemoveAll(false) }))] }));