@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.
- package/CHANGELOG.md +36 -1
- package/package.cjs +1 -1
- package/package.js +1 -1
- package/package.json +12 -12
- package/src/index.cjs +4 -0
- package/src/index.d.ts +2 -2
- package/src/index.js +2 -0
- package/src/lib/components/SendBalanceForm/SendBalanceForm.cjs +26 -1
- package/src/lib/components/SendBalanceForm/SendBalanceForm.js +26 -1
- package/src/lib/components/SendBalancePageLayout/SendBalancePageLayout.cjs +6 -1
- package/src/lib/components/SendBalancePageLayout/SendBalancePageLayout.js +6 -1
- package/src/lib/main.global.cjs +1 -1
- package/src/lib/main.global.js +1 -1
- package/src/lib/utils/hooks/index.d.ts +2 -0
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.cjs +24 -4
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.d.ts +10 -2
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.js +24 -4
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.cjs +14 -3
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.d.ts +5 -1
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.js +14 -3
- package/src/lib/utils/hooks/useAleoShieldedBalances/useAleoShieldedBalances.cjs +14 -0
- package/src/lib/utils/hooks/useAleoShieldedBalances/useAleoShieldedBalances.js +14 -0
- package/src/lib/utils/hooks/usePrivateTokenBalances/index.d.ts +2 -0
- package/src/lib/utils/hooks/usePrivateTokenBalances/usePrivateTokenBalances.cjs +19 -0
- package/src/lib/utils/hooks/usePrivateTokenBalances/usePrivateTokenBalances.d.ts +9 -0
- package/src/lib/utils/hooks/usePrivateTokenBalances/usePrivateTokenBalances.js +15 -0
- package/src/lib/utils/hooks/useSyncDeviceRegistrationFlow/useSyncDeviceRegistrationFlow.cjs +8 -0
- package/src/lib/utils/hooks/useSyncDeviceRegistrationFlow/useSyncDeviceRegistrationFlow.js +9 -1
- package/src/lib/utils/hooks/useWalletDelegation/useWalletDelegation.cjs +19 -16
- package/src/lib/utils/hooks/useWalletDelegation/useWalletDelegation.d.ts +8 -0
- package/src/lib/utils/hooks/useWalletDelegation/useWalletDelegation.js +19 -17
- package/src/lib/utils/hooks/useWalletPassword/useWalletPassword.d.ts +4 -4
- package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/ActiveWalletBalance.cjs +138 -21
- package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/ActiveWalletBalance.js +139 -22
- package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/optimisticShield.cjs +134 -0
- package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/optimisticShield.d.ts +69 -0
- package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/optimisticShield.js +127 -0
- package/src/lib/widgets/DynamicWidget/views/ManageTrustedDevicesView/ManageTrustedDevicesView.cjs +12 -8
- 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 };
|
package/src/lib/widgets/DynamicWidget/views/ManageTrustedDevicesView/ManageTrustedDevicesView.cjs
CHANGED
|
@@ -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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
}), [
|
|
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) }))] }));
|
package/src/lib/widgets/DynamicWidget/views/ManageTrustedDevicesView/ManageTrustedDevicesView.js
CHANGED
|
@@ -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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
}), [
|
|
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) }))] }));
|