diff --git a/packages/beacon-ui/src/ui/alert/components/pairing-alert/index.tsx b/packages/beacon-ui/src/ui/alert/components/pairing-alert/index.tsx index 61674eae3..47aefd3e9 100644 --- a/packages/beacon-ui/src/ui/alert/components/pairing-alert/index.tsx +++ b/packages/beacon-ui/src/ui/alert/components/pairing-alert/index.tsx @@ -1,33 +1,427 @@ import Alert from 'src/components/alert' import useConnect from '../../hooks/useConnect' +import BugReportForm from 'src/components/bug-report-form' +import Info from 'src/components/info' +import PairOther from 'src/components/pair-other/pair-other' +import TopWallets from 'src/components/top-wallets' +import Wallets from 'src/components/wallets' +import { isIOS } from 'src/utils/platform' +import { StorageKey } from '@airgap/beacon-types' +import QR from 'src/components/qr' import useWallets from '../../hooks/useWallets' // todo remove any -const PairingAlert: React.FC = ({ wcPayload, p2pPayload, postPayload, onCloseHandler }) => { +const PairingAlert: React.FC = (props) => { + const { wcPayload, p2pPayload, postPayload, onCloseHandler } = props const wallets = useWallets() const [ - isLoading - // qrCode, - // state, - // displayQRExtra, - // showMoreContent, - // handleNewTab, - // handleDeepLinking, - // handleClickOther, - // handleClickConnectExtension, - // handleClickInstallExtension, - // handleClickOpenDesktopApp, - // handleClickDownloadDesktopApp - ] = useConnect(wcPayload, p2pPayload, postPayload, onCloseHandler) - - console.log('wallets', wallets) + wallet, + isMobile, + isLoading, + qrCode, + state, + displayQRExtra, + showMoreContent, + isWCWorking, + handleClickWallet, + handleNewTab, + handleDeepLinking, + handleClickOther, + handleClickConnectExtension, + handleClickInstallExtension, + handleClickOpenDesktopApp, + handleClickDownloadDesktopApp, + handleUpdateState, + handleUpdateQRCode + ] = useConnect(wcPayload, p2pPayload, postPayload, wallets, onCloseHandler) + const isOnline = navigator.onLine + const walletList = Array.from(wallets.values()) + const areMetricsEnabled = localStorage + ? localStorage.getItem(StorageKey.ENABLE_METRICS) === 'true' + : false + + const generateWCError = (title: string) => { + const errorMessage = localStorage ? localStorage.getItem(StorageKey.WC_INIT_ERROR) : undefined + const description: any = ( + <> +

A network error occurred.

+

+ This issue does not concern your wallet or dApp. If the problem persists, please report it + to the Beacon team{' '} + handleUpdateState('help')} + > + here + +

+ {errorMessage && {errorMessage}} + + ) + return + } + + const QRCode = ({ isMobile }: any) => { + localStorage.setItem( + StorageKey.LAST_SELECTED_WALLET, + JSON.stringify({ + key: wallet?.key, + name: wallet?.name, + type: 'mobile', + icon: wallet?.image + }) + ) + const isConnected = + !wallet?.supportedInteractionStandards?.includes('wallet_connect') || isWCWorking + return ( + <> + {isConnected ? ( + {}} + onClickQrCode={() => {}} + /> + ) : ( + generateWCError(`Connect with ${wallet?.name} Mobile`) + )} + + ) + } return ( Hello World} + showMore={showMoreContent} + content={ +
+ {state === 'install' && ( +
+ {isOnline && wallet?.types.includes('web') && ( + handleNewTab(props, wallet) + } + ]} + /> + )} + {!isMobile && wallet?.types.includes('extension') && ( + handleClickConnectExtension() + } + ] + : [ + { + label: 'Install extension', + type: 'primary', + onClick: () => handleClickInstallExtension() + } + ] + } + /> + )} + {!isMobile && wallet?.types.includes('desktop') && ( + handleClickOpenDesktopApp() + }, + { + label: 'Download desktop app', + type: 'secondary', + onClick: () => handleClickDownloadDesktopApp() + } + ]} + /> + )} + {!isMobile && + (qrCode?.length ?? 0) && + wallet?.types.includes('ios') && + (wallet?.types.length as number) > 1 && } + {!isMobile && + (qrCode?.length ?? 0) && + wallet?.types.includes('ios') && + (wallet?.types.length as number) <= 1 && } + {isMobile && + wallet?.types.includes('ios') && + (!wallet?.supportedInteractionStandards?.includes('wallet_connect') || + isWCWorking ? ( + { + if (!wallet) { + return + } + + handleDeepLinking( + wallet.supportedInteractionStandards?.includes('wallet_connect') + ? wcPayload + : p2pPayload + ) + } + } + ]} + downloadLink={ + wallet?.name.toLowerCase().includes('kukai') && isIOS(window) + ? { + label: 'Get Kukai Mobile >', + url: 'https://ios.kukai.app' + } + : undefined + } + onShowQRCodeClick={async () => { + const syncCode = wallet?.supportedInteractionStandards?.includes( + 'wallet_connect' + ) + ? wcPayload + : p2pPayload + + if (!syncCode.length || !wallet) { + onCloseHandler() + return + } + + if (isMobile && wallet.types.includes('ios') && wallet.types.length === 1) { + handleDeepLinking(syncCode) + } else { + handleUpdateQRCode(syncCode) + } + + handleUpdateState('qr') + // todo setDisplayQrExtra(true) + }} + /> + ) : ( + generateWCError(`Connect with ${wallet?.name} Mobile`) + ))} +
+ )} + {state === 'qr' && ( +
+ {!displayQRExtra ? ( + {}} + p2pPayload={p2pPayload} + wcPayload={wcPayload} + > + ) : ( + + )} +
+ )} +
+ handleClickWallet(id, props)} + onClickOther={handleClickOther} + /> +
+
+ {areMetricsEnabled && } + {!areMetricsEnabled && ( + <> + + + + + } + title="What is a wallet?" + description="Wallets let you send, receive, store and interact with digital assets. Your wallet can be used as an easy way to login, instead of having to remember a password." + /> + + + + + + } + title="Not sure where to start?" + description="If you are new to the Web3, we recommend that you start by creating a Kukai wallet. Kukai is a fast way of creating your first wallet using your preferred social account." + /> + + )} +
+
+ handleClickWallet(id, props)} + onClickLearnMore={() => {}} + otherWallets={ + isMobile + ? { + images: [walletList[3].image, walletList[4].image, walletList[5].image], + onClick: () => handleUpdateState('wallets') + } + : undefined + } + /> +
+
+ } /> ) } diff --git a/packages/beacon-ui/src/ui/alert/hooks/useConnect.tsx b/packages/beacon-ui/src/ui/alert/hooks/useConnect.tsx index e1f212baf..dffeb13bd 100644 --- a/packages/beacon-ui/src/ui/alert/hooks/useConnect.tsx +++ b/packages/beacon-ui/src/ui/alert/hooks/useConnect.tsx @@ -2,12 +2,13 @@ import { Logger, windowRef } from '@airgap/beacon-core' import { StorageKey, ExtensionMessage, ExtensionMessageTarget } from '@airgap/beacon-types' import { useCallback, useState } from 'react' import { getTzip10Link } from 'src/utils/get-tzip10-link' -import { isTwBrowser, isAndroid } from 'src/utils/platform' +import { isTwBrowser, isAndroid, isMobileOS, isIOS } from 'src/utils/platform' import { MergedWallet, OSLink } from 'src/utils/wallets' import { Serializer } from '@airgap/beacon-core' import getDefaultLogo from '../getDefautlLogo' import { parseUri } from '@walletconnect/utils' import { AlertConfig } from '../common' +import useIsMobile from './useIsMobile' const logger = new Logger('useConnect') @@ -15,9 +16,10 @@ const useConnect = ( wcPayload: string, p2pPayload: string, postPayload: string, + wallets: Map, onCloseHandler: Function ) => { - // const [wallet, setWallet] = useState() + const [wallet, setWallet] = useState() const [isLoading, setIsLoading] = useState(false) const [qrCode, setQRCode] = useState() const [state, setState] = useState<'top-wallets' | 'wallets' | 'install' | 'help' | 'qr'>( @@ -25,14 +27,88 @@ const useConnect = ( ) const [displayQRExtra, setDisplayQRExtra] = useState(false) const [showMoreContent, setShowMoreContent] = useState(false) + const [isWCWorking, setIsWCWorking] = useState(true) + const isMobile = isMobileOS(window) || useIsMobile() - const handleCloseAlert = useCallback(() => { - onCloseHandler() - }, []) + const handleClickWallet = async (id: string, config: AlertConfig) => { + setIsLoading(true) + setShowMoreContent(false) + const wallet = wallets.get(id) + setWallet(wallet) - const generateUri = useCallback((uri: string) => { - return !!parseUri(uri).symKey - }, []) + localStorage.setItem( + StorageKey.LAST_SELECTED_WALLET, + JSON.stringify({ + key: wallet?.key, + type: 'mobile', + icon: wallet?.image + }) + ) + + if ( + (wallet?.types.includes('web') && wallet?.types.length === 1) || + (isAndroid(window) && wallet?.name.toLowerCase().includes('kukai')) + ) { + handleNewTab(config, wallet) + return + } + + if (wallet && wallet.supportedInteractionStandards?.includes('wallet_connect')) { + const isValid = !!parseUri(wcPayload).symKey + setIsWCWorking(isValid) + + if (!isValid && wallet?.name.toLowerCase().includes('kukai')) { + setQRCode('error') + // setInstallState(wallet) + setIsLoading(false) + return + } + + if (isValid) { + if (isMobile && wallet.types.includes('ios') && wallet.types.length === 1) { + handleDeepLinking(wcPayload) + } else { + setQRCode(wcPayload) + // setInstallState(wallet) + } + } + setIsLoading(false) + } else if (wallet?.types.includes('ios') && isMobile) { + setQRCode('') + + if (config.pairingPayload) { + const serializer = new Serializer() + const code = await serializer.serialize(await p2pPayload) + + const link = getTzip10Link( + isIOS(window) && wallet.deepLink + ? wallet.deepLink + : isAndroid(window) + ? wallet.links[OSLink.IOS] + : 'tezos://', + code + ) + + updateSelectedWalletWithURL(link) + + if (isAndroid(window)) window.open(link, '_blank', 'noopener') + else if (isIOS(window)) { + const a = document.createElement('a') + a.setAttribute('href', link) + a.setAttribute('rel', 'noopener') + a.dispatchEvent( + new MouseEvent('click', { view: window, bubbles: true, cancelable: true }) + ) + } + } + + setIsLoading(false) + } else { + setIsLoading(false) + // setInstallState(wallet) + // await setDefaultPayload() + } + } const updateSelectedWalletWithURL = useCallback((url: string) => { let wallet = JSON.parse(localStorage.getItem(StorageKey.LAST_SELECTED_WALLET) ?? '{}') @@ -73,13 +149,14 @@ const useConnect = ( wallet.supportedInteractionStandards?.includes('wallet_connect') && !wallet.name.toLowerCase().includes('kukai') ) { - const uri = generateUri(wcPayload) + const isValid = !!parseUri(wcPayload).symKey + setIsWCWorking(isValid) - if (!uri) { + if (!isValid) { return } - link = `${wallet.links[OSLink.WEB]}/wc?uri=${encodeURIComponent(uri)}` + link = `${wallet.links[OSLink.WEB]}/wc?uri=${encodeURIComponent(wcPayload)}` } else { const serializer = new Serializer() const code = await serializer.serialize(p2pPayload) @@ -103,23 +180,23 @@ const useConnect = ( ) }, []) - const handleDeepLinking = useCallback(async (wallet: MergedWallet, uri: string) => { + const handleDeepLinking = useCallback(async (uri: string) => { localStorage.setItem( StorageKey.LAST_SELECTED_WALLET, JSON.stringify({ - key: wallet.key, + key: wallet?.key, type: 'mobile', - icon: wallet.image + icon: wallet?.image }) ) - if (!wallet.links[OSLink.IOS].length) { + if (!wallet?.links[OSLink.IOS].length) { const syncCode = wallet?.supportedInteractionStandards?.includes('wallet_connect') ? wcPayload ?? '' : await new Serializer().serialize(p2pPayload) if (!syncCode.length) { - handleCloseAlert() + onCloseHandler() return } @@ -160,7 +237,7 @@ const useConnect = ( setState('qr') }, []) - const handleClickConnectExtension = useCallback(async (wallet: MergedWallet) => { + const handleClickConnectExtension = useCallback(async () => { setShowMoreContent(false) const serializer = new Serializer() const postmessageCode = await serializer.serialize(postPayload) @@ -194,12 +271,12 @@ const useConnect = ( ) }, []) - const handleClickInstallExtension = useCallback(async (wallet: MergedWallet) => { + const handleClickInstallExtension = useCallback(async () => { setShowMoreContent(false) window.open(wallet?.links[OSLink.EXTENSION] || '', '_blank', 'noopener') }, []) - const handleClickOpenDesktopApp = useCallback(async (wallet: MergedWallet) => { + const handleClickOpenDesktopApp = useCallback(async () => { setShowMoreContent(false) if (p2pPayload) { @@ -220,24 +297,37 @@ const useConnect = ( ) }, []) - const handleClickDownloadDesktopApp = useCallback(async (wallet: MergedWallet) => { + const handleClickDownloadDesktopApp = useCallback(async () => { setShowMoreContent(false) window.open(wallet?.links[OSLink.DESKTOP] || '', '_blank', 'noopener') }, []) + const handleUpdateState = useCallback( + (newState: 'top-wallets' | 'wallets' | 'install' | 'help' | 'qr') => setState(newState), + [] + ) + + const handleUpdateQRCode = useCallback((uri: string) => setQRCode(uri), []) + return [ + wallet, + isMobile, isLoading, qrCode, state, displayQRExtra, showMoreContent, + isWCWorking, + handleClickWallet, handleNewTab, handleDeepLinking, handleClickOther, handleClickConnectExtension, handleClickInstallExtension, handleClickOpenDesktopApp, - handleClickDownloadDesktopApp + handleClickDownloadDesktopApp, + handleUpdateState, + handleUpdateQRCode ] as const } diff --git a/packages/beacon-ui/src/ui/alert/hooks/useIsMobile.tsx b/packages/beacon-ui/src/ui/alert/hooks/useIsMobile.tsx new file mode 100644 index 000000000..e4d8e82ae --- /dev/null +++ b/packages/beacon-ui/src/ui/alert/hooks/useIsMobile.tsx @@ -0,0 +1,22 @@ +import { useState, useEffect } from 'react' + +const useIsMobile = (breakpoint = 768) => { + const [isMobile, setIsMobile] = useState(window.innerWidth <= breakpoint) + + useEffect(() => { + const handleResize = () => { + setIsMobile(window.innerWidth <= breakpoint) + } + + window.addEventListener('resize', handleResize) + + // Cleanup the event listener when the component unmounts + return () => { + window.removeEventListener('resize', handleResize) + } + }, [breakpoint]) + + return isMobile +} + +export default useIsMobile diff --git a/packages/beacon-ui/src/ui/alert/hooks/useWallets.tsx b/packages/beacon-ui/src/ui/alert/hooks/useWallets.tsx index 24ef27bc9..e9bb4804f 100644 --- a/packages/beacon-ui/src/ui/alert/hooks/useWallets.tsx +++ b/packages/beacon-ui/src/ui/alert/hooks/useWallets.tsx @@ -120,6 +120,6 @@ const useWallets = (networkType?: NetworkType, featuredWallets?: string[]) => { return arrangedWallets }, []) - return wallets + return new Map(wallets.map((wallet) => [wallet.id, wallet])) } export default useWallets