UNPKG

@turnkey/react-wallet-kit

Version:

The easiest and most powerful way to integrate Turnkey's Embedded Wallets into your React applications.

276 lines (273 loc) 13.5 kB
'use client'; import { slicedToArray as _slicedToArray } from '../../_virtual/_rollupPluginBabelHelpers.mjs'; import { jsx, jsxs } from 'react/jsx-runtime'; import { useState, useRef, useEffect, Fragment } from 'react'; import { Transition, Portal, TransitionChild } from '@headlessui/react'; import { useModal } from './Hook.mjs'; import { IconButton } from '../../components/design/Buttons.mjs'; import { faArrowLeft, faClose } from '@fortawesome/free-solid-svg-icons'; import { TurnkeyLogo } from '../../components/design/Svg.mjs'; import { ClientState } from '../../types/base.mjs'; import { useTurnkey } from '../client/Hook.mjs'; import clsx from 'clsx'; import { DeveloperError } from '../../components/design/Failure.mjs'; function ModalRoot(props) { var _config$ui2, _config$ui$background, _config$ui3, _config$ui4; var config = props.config; // Note: This is the config passed into the TurnkeyProvider. If we ever need to get config from the dashboard as well, grab `config` from the useTurnkey hook instead. var _useModal = useModal(), modalStack = _useModal.modalStack, popPage = _useModal.popPage, closeModal = _useModal.closeModal, screenWidth = _useModal.screenWidth, isMobile = _useModal.isMobile; var _useTurnkey = useTurnkey(), clientState = _useTurnkey.clientState; var current = modalStack[modalStack.length - 1]; var hasBack = modalStack.length > 1 && !(current !== null && current !== void 0 && current.preventBack); var _useState = useState(0), _useState2 = _slicedToArray(_useState, 2), contentBlur = _useState2[0], setContentBlur = _useState2[1]; var innerPadding = 16; var containerRef = useRef(null); var _useState3 = useState(300), _useState4 = _slicedToArray(_useState3, 2), height = _useState4[0], setHeight = _useState4[1]; var _useState5 = useState(300), _useState6 = _slicedToArray(_useState5, 2), width = _useState6[0], setWidth = _useState6[1]; var _useState7 = useState(true), _useState8 = _slicedToArray(_useState7, 2), observeResize = _useState8[0], setObserveResize = _useState8[1]; useCssLoaded(modalStack, config); // triggers warning overlay if CSS missing var maxMobileScreenWidth = screenWidth * 0.9; // Only take up 90% of the screen width on mobile var getBorderRadius = function getBorderRadius() { var _config$ui$borderRadi, _config$ui; // Border radius can be passed in as either a number or a string, so we need to handle both cases var value = (_config$ui$borderRadi = config === null || config === void 0 ? void 0 : (_config$ui = config.ui) === null || _config$ui === void 0 ? void 0 : _config$ui.borderRadius) !== null && _config$ui$borderRadi !== void 0 ? _config$ui$borderRadi : 16; var stringBorderRadius = typeof value === "number" ? "".concat(value, "px") : value; // Remove bottom border radius on mobile to avoid rounded corners at the bottom of the screen return isMobile ? "".concat(stringBorderRadius, " ").concat(stringBorderRadius, " 0 0") : stringBorderRadius; }; useEffect(function () { // This useEffect sets up a ResizeObserver to monitor changes in the size of the modal content. // This only needs to run when the content is changing size without the page being swapped out var node = containerRef.current; if (!node || !observeResize) return; var cancelled = false; // To prevent setting state on unmounted component var observer = new ResizeObserver(function (_ref) { var _ref2 = _slicedToArray(_ref, 1), entry = _ref2[0]; if (!entry || cancelled) return; setContentBlur(10); var _entry$contentRect = entry.contentRect, newWidth = _entry$contentRect.width, newHeight = _entry$contentRect.height; setHeight(newHeight); setWidth(newWidth); setTimeout(function () { return !cancelled && setContentBlur(0); }, 100); }); observer.observe(node); return function () { cancelled = true; observer.disconnect(); }; }, [containerRef.current, observeResize]); useEffect(function () { var cancelled = false; // To prevent setting state on unmounted component var resize = function resize() { if (cancelled || !containerRef.current) return; setContentBlur(10); var rect = containerRef.current.getBoundingClientRect(); setHeight(rect.height); setWidth(rect.width); setTimeout(function () { return !cancelled && setContentBlur(0); }, 100); }; if (current) { setObserveResize(true); requestAnimationFrame(resize); var originalStyle = document.body.style.overflow; document.body.style.overflow = "hidden"; return function () { cancelled = true; document.body.style.overflow = originalStyle; }; } else { setObserveResize(false); setHeight(height / 1.3); setWidth(isMobile ? width : width / 1.3); return; } }, [current, isMobile]); return jsx(Transition, { appear: true, show: !!current, as: Fragment, children: jsx(Portal, { as: Fragment, children: jsxs("div", { className: "relative z-40", children: [jsx(TransitionChild, { as: Fragment, enter: "ease-out duration-200", enterFrom: "opacity-0", enterTo: "opacity-100", leave: "ease-in duration-150", leaveFrom: "opacity-100", leaveTo: "opacity-0", children: jsx("div", { className: "fixed inset-0 bg-black/40", style: { backdropFilter: "blur(".concat(typeof (config === null || config === void 0 ? void 0 : (_config$ui2 = config.ui) === null || _config$ui2 === void 0 ? void 0 : _config$ui2.backgroundBlur) === "number" ? "".concat(config.ui.backgroundBlur, "px") : (_config$ui$background = config === null || config === void 0 ? void 0 : (_config$ui3 = config.ui) === null || _config$ui3 === void 0 ? void 0 : _config$ui3.backgroundBlur) !== null && _config$ui$background !== void 0 ? _config$ui$background : "8px", ")") } }) }), jsx("div", { className: clsx("tk-modal fixed inset-0 flex justify-center transition-colors", { dark: config === null || config === void 0 ? void 0 : (_config$ui4 = config.ui) === null || _config$ui4 === void 0 ? void 0 : _config$ui4.darkMode }, { "items-end": isMobile }, { "items-center": !isMobile }), onMouseDown: function onMouseDown(e) { if (e.target === e.currentTarget) { e.stopPropagation(); closeModal(); } }, children: jsx(TransitionChild, { as: Fragment, enter: "ease-out duration-150", enterFrom: "opacity-0", enterTo: "opacity-100", leave: "ease-in duration-100", leaveFrom: "opacity-100", leaveTo: "opacity-0", children: jsxs("div", { onClick: function onClick(e) { return e.stopPropagation(); }, /* White / Black background container */ style: { height: height, width: isMobile ? maxMobileScreenWidth : width, padding: innerPadding, borderRadius: getBorderRadius() }, className: "bg-modal-background-light dark:bg-modal-background-dark text-modal-text-light dark:text-modal-text-dark flex justify-center shadow-xl transition-all overflow-x-clip", children: [jsxs("div", { className: "h-6.5 absolute z-30 flex items-center justify-between transition-all", style: { width: isMobile ? maxMobileScreenWidth : width }, children: [jsx("div", { className: "w-6.5 h-6.5", children: hasBack && jsx(IconButton, { className: "w-6.5 h-6.5", icon: faArrowLeft, onClick: popPage }) }), jsx("h2", { className: "text-base font-medium", children: (current === null || current === void 0 ? void 0 : current.showTitle) === undefined ? current === null || current === void 0 ? void 0 : current.key : current.showTitle ? current === null || current === void 0 ? void 0 : current.key : null }), jsx("div", { className: "w-6.5 h-6.5", children: jsx(IconButton, { className: "w-6.5 h-6.5", icon: faClose, onClick: closeModal }) })] }), jsx(TransitionChild, { as: Fragment, enter: "ease-in duration-200", enterFrom: "opacity-0", enterTo: "opacity-100", leave: "ease-in duration-100", leaveFrom: "opacity-100", leaveTo: "opacity-0", children: jsxs("div", { className: clsx("flex flex-col z-10 transition-all duration-50 self-start", // This allows the content to take the static `maxMobileScreenWidth` width on mobile isMobile && "w-full items-center"), style: { filter: "blur(".concat(contentBlur, "px)") }, ref: containerRef, children: [clientState === ClientState.Error ? jsx(DeveloperError, { developerTitle: "Turnkey SDK failed to initialize.", developerMessages: ["Check your config and ensure you're connected to the internet.", "Try attaching an onError callback to the TurnkeyProvider's callbacks to see more details.", "You can also inspect the network tab to see any failed network requests."], userMessages: ["An underlying error occurred. Try refreshing the page"] }) : current === null || current === void 0 ? void 0 : current.content, jsxs("a", { href: "https://www.turnkey.com/", target: "_blank", className: "flex flex-row items-center justify-center gap-0.5 mt-2 mb-1 text-icon-text-light/50 dark:text-icon-text-dark/50 text-xs no-underline", style: { opacity: current !== null && current !== void 0 && current.content ? 1 : 0 }, children: [jsx("span", { children: "Secured by" }), jsx(TurnkeyLogo, {})] })] }) })] }) }) })] }) }) }); } function useCssLoaded(modalStack, config) { useEffect(function () { var _config$ui5; if (process.env.NODE_ENV !== "development") return; // Only check in development mode var testElement = document.createElement("div"); testElement.className = "tk-modal"; // Use a class that should definitely be styled testElement.style.position = "absolute"; testElement.style.visibility = "hidden"; document.body.appendChild(testElement); // Check if the styles have been applied by inspecting a computed style var computedStyle = window.getComputedStyle(testElement); var hasStyles = computedStyle.lineHeight === "normal"; document.body.removeChild(testElement); if (!hasStyles && modalStack.length === 1 && !((_config$ui5 = config.ui) !== null && _config$ui5 !== void 0 && _config$ui5.supressMissingStylesError)) renderMissingStylesOverlay(); }, [modalStack]); } function renderMissingStylesOverlay() { if (typeof document === "undefined") return; if (document.getElementById("turnkey-missing-css")) return; var div = document.createElement("div"); div.id = "turnkey-missing-css"; Object.assign(div.style, { position: "fixed", inset: "0", backgroundColor: "rgba(255,255,255,0.96)", color: "#b91c1c", display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", fontFamily: "monospace", zIndex: "999999", padding: "2rem", textAlign: "center" }); div.innerHTML = "\n <div style=\"font-size: 1.5rem; font-weight: bold; margin-bottom: 1rem;\">\n \u26A0\uFE0F Turnkey styles are missing\n </div>\n <div style=\"font-size: 1rem; max-width: 600px;\">\n You must import the Turnkey React Wallet Kit styles in your app:\n <pre style=\"background-color: #f3f4f6; color: #111827; padding: 0.5rem 1rem; margin-top: 1rem; border-radius: 6px; font-size: 0.875rem;\">\n import \"@turnkey/react-wallet-kit/styles.css\";\n </pre>\n <p style=\"margin-top: 1rem; font-size: 0.875rem; color: #6b7280;\">\n This warning only shows in development mode.\n </p>\n <button id=\"turnkey-missing-css-close\" style=\"margin-top: 2rem; padding: 0.5rem 1.5rem; font-size: 1rem; background: #b91c1c; color: white; border: none; border-radius: 6px; cursor: pointer;\">\n Close\n </button>\n </div>\n "; document.body.appendChild(div); var closeBtn = div.querySelector("#turnkey-missing-css-close"); if (closeBtn) { closeBtn.addEventListener("click", function () { div.remove(); }); } } export { ModalRoot }; //# sourceMappingURL=Root.mjs.map