@turnkey/react-wallet-kit
Version:
The easiest and most powerful way to integrate Turnkey's Embedded Wallets into your React applications.
300 lines (297 loc) • 12.1 kB
JavaScript
import { slicedToArray as _slicedToArray, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator, objectSpread2 as _objectSpread2 } from '../../../_virtual/_rollupPluginBabelHelpers.mjs';
import { jsx, jsxs } from 'react/jsx-runtime';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faQrcode } from '@fortawesome/free-solid-svg-icons';
import { useState, useRef, useMemo, useCallback, useEffect } from 'react';
import clsx from 'clsx';
import { Chain } from '@turnkey/core';
import { useModal } from '../../../providers/modal/Hook.mjs';
import { useTurnkey } from '../../../providers/client/Hook.mjs';
import { useWalletConnect } from '../../../providers/WalletConnectProvider.mjs';
import { useDebouncedCallback, findWalletConnectProvider } from '../../../utils/utils.mjs';
import { WalletButton } from './WalletButton.mjs';
import { WalletConnectAppChainSelector } from './WalletConnectAppChainSelector.mjs';
import { SearchInputBox } from '../../design/Inputs.mjs';
import { Spinner } from '../../design/Spinners.mjs';
import { INITIAL_VISIBLE_ROWS, ROW_STRIDE, BUFFER_SIZE } from './constants.mjs';
import { WalletButtonSkeleton } from './WalletButtonSkeleton.mjs';
var sameRange = function sameRange(a, b) {
return a.start === b.start && a.end === b.end;
};
function ShowAllWalletsScreen(props) {
var onSelect = props.onSelect,
onSelectQRCode = props.onSelectQRCode;
var _useTurnkey = useTurnkey(),
walletProviders = _useTurnkey.walletProviders,
disconnectWalletAccount = _useTurnkey.disconnectWalletAccount;
var _useWalletConnect = useWalletConnect(),
walletConnectApps = _useWalletConnect.walletConnectApps,
isLoadingApps = _useWalletConnect.isLoadingApps;
var _useModal = useModal(),
isMobile = _useModal.isMobile,
pushPage = _useModal.pushPage,
popPage = _useModal.popPage;
var _useState = useState(""),
_useState2 = _slicedToArray(_useState, 2),
searchQuery = _useState2[0],
setSearchQuery = _useState2[1];
// renderRange (buffered, debounced) governs which real WalletButtons are mounted.
// viewportRange (unbuffered, rAF-synchronous) governs the skeleton layer behind them.
var _useState3 = useState({
start: 0,
end: INITIAL_VISIBLE_ROWS
}),
_useState4 = _slicedToArray(_useState3, 2),
renderRange = _useState4[0],
setRenderRange = _useState4[1];
var _useState5 = useState({
start: 0,
end: INITIAL_VISIBLE_ROWS
}),
_useState6 = _slicedToArray(_useState5, 2),
viewportRange = _useState6[0],
setViewportRange = _useState6[1];
var scrollContainerRef = useRef(null);
var rafIdRef = useRef(null);
// Group wallet apps by id (same wallet across different chains)
var grouped = useMemo(function () {
return walletConnectApps.reduce(function (acc, app) {
if (!acc[app.id]) acc[app.id] = [];
acc[app.id].push(app);
return acc;
}, {});
}, [walletConnectApps]);
// Filter by search query
var walletEntries = useMemo(function () {
var entries = Object.entries(grouped);
if (!searchQuery.trim()) return entries;
var query = searchQuery.toLowerCase();
return entries.filter(function (_ref) {
var _group$0$name$toLower, _group$, _group$$name;
var _ref2 = _slicedToArray(_ref, 2);
_ref2[0];
var group = _ref2[1];
var walletName = (_group$0$name$toLower = (_group$ = group[0]) === null || _group$ === void 0 ? void 0 : (_group$$name = _group$.name) === null || _group$$name === void 0 ? void 0 : _group$$name.toLowerCase()) !== null && _group$0$name$toLower !== void 0 ? _group$0$name$toLower : "";
return walletName.includes(query);
});
}, [grouped, searchQuery]);
// The QR-code row takes the slot at global row 0 when not searching.
// renderRange/viewportRange are in *global* row indices, so wallet entry i
// lives at global row (qrRowCount + i).
var qrRowCount = searchQuery ? 0 : 1;
var totalRows = walletEntries.length + qrRowCount;
/**
* Merges WalletConnectAppEntry (display info) with the actual WalletProvider
* and triggers selection. Disconnects any existing session first.
*/
var selectApp = useCallback(/*#__PURE__*/function () {
var _ref3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(targetApp) {
var provider, mergedProvider;
return _regenerator().w(function (_context) {
while (1) switch (_context.n) {
case 0:
provider = findWalletConnectProvider(walletProviders, targetApp.chain);
if (provider) {
_context.n = 1;
break;
}
// WalletConnect provider not ready - go back
popPage();
return _context.a(2);
case 1:
if (!(provider.connectedAddresses.length > 0)) {
_context.n = 2;
break;
}
_context.n = 2;
return disconnectWalletAccount(provider);
case 2:
// Merge app display info with the actual provider
mergedProvider = _objectSpread2(_objectSpread2({}, provider), {}, {
uri: targetApp.uri,
info: _objectSpread2(_objectSpread2({}, provider.info), {}, {
name: targetApp.name,
icon: targetApp.icon
})
});
_context.n = 3;
return onSelect(mergedProvider);
case 3:
return _context.a(2);
}
}, _callee);
}));
return function (_x) {
return _ref3.apply(this, arguments);
};
}(), [walletProviders, disconnectWalletAccount, onSelect, popPage]);
var handleSelectGroup = useCallback(function (group) {
if (group.length === 1) {
selectApp(group[0]);
} else {
pushPage({
key: "Select chain",
content: jsx(WalletConnectAppChainSelector, {
apps: group,
onSelect: selectApp
})
});
}
}, [selectApp, pushPage]);
var computeRange = useCallback(function (buffer) {
if (!scrollContainerRef.current) return null;
var _scrollContainerRef$c = scrollContainerRef.current,
scrollTop = _scrollContainerRef$c.scrollTop,
clientHeight = _scrollContainerRef$c.clientHeight;
return {
start: Math.max(0, Math.floor(scrollTop / ROW_STRIDE) - buffer),
end: Math.min(totalRows, Math.ceil((scrollTop + clientHeight) / ROW_STRIDE) + buffer)
};
}, [totalRows]);
var handleScroll = useDebouncedCallback(function () {
var next = computeRange(BUFFER_SIZE);
if (next) setRenderRange(function (prev) {
return sameRange(prev, next) ? prev : next;
});
}, 50);
var onScroll = useCallback(function () {
if (rafIdRef.current === null) {
rafIdRef.current = requestAnimationFrame(function () {
rafIdRef.current = null;
var next = computeRange(0);
if (next) setViewportRange(function (prev) {
return sameRange(prev, next) ? prev : next;
});
});
}
handleScroll();
}, [computeRange, handleScroll]);
useEffect(function () {
return function () {
if (rafIdRef.current !== null) {
cancelAnimationFrame(rafIdRef.current);
}
};
}, []);
// Reset scroll when search results change (or QR row toggles)
useEffect(function () {
var initial = {
start: 0,
end: Math.min(INITIAL_VISIBLE_ROWS, totalRows)
};
setRenderRange(initial);
setViewportRange(initial);
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollTop = 0;
}
}, [totalRows]);
var debouncedSetSearch = useDebouncedCallback(function (value) {
return setSearchQuery(value);
}, 300);
// renderRange is in global row indices; subtract qrRowCount to slice walletEntries
var visibleItems = useMemo(function () {
var sliceStart = Math.max(0, renderRange.start - qrRowCount);
var sliceEnd = Math.max(0, renderRange.end - qrRowCount);
return walletEntries.slice(sliceStart, sliceEnd);
}, [walletEntries, renderRange, qrRowCount]);
var totalHeight = totalRows * ROW_STRIDE;
var offsetY = renderRange.start * ROW_STRIDE;
if (isLoadingApps) {
return jsx("div", {
className: "flex flex-col items-center py-5",
children: jsx(Spinner, {
strokeWidth: 2,
className: "w-12 h-12"
})
});
}
return jsxs("div", {
className: clsx("flex flex-col mt-10 gap-3", isMobile ? "w-full" : "w-80"),
children: [jsx(SearchInputBox, {
value: searchQuery,
onChange: debouncedSetSearch,
onClear: function onClear() {
return setSearchQuery("");
},
placeholder: "Search wallets..."
}), jsxs("div", {
ref: scrollContainerRef,
onScroll: onScroll,
className: clsx("min-h-42 overflow-y-auto tk-scrollbar p-0.5", isMobile ? "max-h-80" : "max-h-72"),
children: [jsxs("div", {
style: {
height: totalHeight,
position: "relative"
},
children: [jsx("div", {
style: {
transform: "translateY(".concat(viewportRange.start * ROW_STRIDE, "px)"),
position: "absolute",
width: "100%"
},
className: "flex flex-col gap-2",
"aria-hidden": "true",
children: Array.from({
length: Math.max(0, viewportRange.end - viewportRange.start)
}).map(function (_, i) {
return jsx(WalletButtonSkeleton, {}, "skeleton-".concat(viewportRange.start + i));
})
}), jsxs("div", {
style: {
transform: "translateY(".concat(offsetY, "px)"),
position: "absolute",
width: "100%"
},
className: "flex flex-col gap-2",
children: [qrRowCount > 0 && renderRange.start === 0 && jsx(WalletButton, {
icon: jsx("div", {
className: "flex items-center justify-center size-6 rounded-full text-icon-text-light dark:text-icon-text-dark bg-icon-background-light dark:bg-icon-background-dark",
children: jsx(FontAwesomeIcon, {
icon: faQrcode
})
}),
name: "Scan QR Code",
chains: [{
namespace: Chain.Ethereum,
isConnected: false
}, {
namespace: Chain.Solana,
isConnected: false
}],
onClick: onSelectQRCode,
isMobile: isMobile
}, "qr-code-walletconnect"), visibleItems.map(function (_ref4) {
var _first$icon, _first$name;
var _ref5 = _slicedToArray(_ref4, 2),
id = _ref5[0],
group = _ref5[1];
var first = group[0];
return jsx(WalletButton, {
icon: (_first$icon = first === null || first === void 0 ? void 0 : first.icon) !== null && _first$icon !== void 0 ? _first$icon : "",
name: (_first$name = first === null || first === void 0 ? void 0 : first.name) !== null && _first$name !== void 0 ? _first$name : "",
chains: group.map(function (app) {
return {
namespace: app.chain,
isConnected: false
};
}),
onClick: function onClick() {
return handleSelectGroup(group);
},
shouldShowDisconnect: false,
isMobile: isMobile
}, id);
})]
})]
}), searchQuery && walletEntries.length === 0 && jsxs("div", {
className: "flex items-center justify-center w-full h-40 text-center text-icon-text-light dark:text-icon-text-dark",
children: ["No wallets found matching \"", searchQuery, "\""]
})]
}), searchQuery && walletEntries.length > 0 && jsxs("div", {
className: "text-xs text-center text-icon-text-light dark:text-icon-text-dark",
children: ["Showing ", walletEntries.length, " wallet", walletEntries.length !== 1 ? "s" : ""]
})]
});
}
export { ShowAllWalletsScreen };
//# sourceMappingURL=ShowAllWalletsScreen.mjs.map