Skip to content
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Button, Icon, Text, VStack } from '@chakra-ui/react'
import { useQueryClient } from '@tanstack/react-query'
import { useCallback } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { IoIosCheckmarkCircle } from 'react-icons/io'
import { useTranslate } from 'react-polyglot'
import type { Location } from 'react-router-dom'
Expand Down Expand Up @@ -33,82 +33,111 @@ export const CreateSuccess = ({ onClose }: CreateSuccessProps) => {
const queryClient = useQueryClient()
const { dispatch, getAdapter } = useWallet()
const localWallet = useLocalWallet()
const saveAttemptedRef = useRef(false)
const connectionAttemptedRef = useRef(false)
const savedWalletRef = useRef<Awaited<ReturnType<typeof addWallet>> | null>(null)
const [isConnecting, setIsConnecting] = useState(false)

const saveWallet = useCallback(async () => {
if (saveAttemptedRef.current) return
saveAttemptedRef.current = true

const saveAndSelectWallet = useCallback(async () => {
if (location.state?.vault?.label && location.state?.vault?.mnemonic) {
const wallet = await addWallet({
label: location.state.vault.label,
mnemonic: location.state.vault.mnemonic,
})
try {
const wallet = await addWallet({
label: location.state.vault.label,
mnemonic: location.state.vault.mnemonic,
})

if (!wallet) {
return
if (wallet) {
savedWalletRef.current = wallet
await queryClient.invalidateQueries({ queryKey: ['listWallets'] })
}
} catch (e) {
console.error('Failed to save wallet:', e)
saveAttemptedRef.current = false
}
}
}, [location.state?.vault, queryClient])

const handleWalletConnection = useCallback(async () => {
if (connectionAttemptedRef.current) return
connectionAttemptedRef.current = true

const wallet = savedWalletRef.current
if (!wallet || !location.state?.vault?.mnemonic) {
connectionAttemptedRef.current = false
return
}

try {
const adapter = await getAdapter(KeyManager.Mobile)
const deviceId = wallet.id
if (adapter && deviceId) {
const { name, icon } = MobileConfig
try {
const walletInstance = await adapter.pairDevice(deviceId)
await walletInstance?.loadDevice({ mnemonic: location.state?.vault?.mnemonic ?? '' })

if (!(await walletInstance?.isInitialized())) {
await walletInstance?.initialize()
}
dispatch({
type: WalletActions.SET_WALLET,
payload: {
wallet: walletInstance,
name,
icon,
deviceId,
meta: { label: location.state?.vault?.label },
connectedType: KeyManager.Mobile,
},
})
dispatch({ type: WalletActions.SET_IS_CONNECTED, payload: true })
dispatch({
type: WalletActions.SET_CONNECTOR_TYPE,
payload: { modalType: KeyManager.Mobile, isMipdProvider: false },
})

localWallet.setLocalWallet({ type: KeyManager.Mobile, deviceId })
localWallet.setLocalNativeWalletName(location.state?.vault?.label ?? 'label')
} catch (e) {
console.log(e)
const walletInstance = await adapter.pairDevice(deviceId)
await walletInstance?.loadDevice({ mnemonic: location.state?.vault?.mnemonic ?? '' })

if (!(await walletInstance?.isInitialized())) {
await walletInstance?.initialize()
}
dispatch({
type: WalletActions.SET_WALLET,
payload: {
wallet: walletInstance,
name,
icon,
deviceId,
meta: { label: location.state?.vault?.label },
connectedType: KeyManager.Mobile,
},
})
dispatch({ type: WalletActions.SET_IS_CONNECTED, payload: true })
dispatch({
type: WalletActions.SET_CONNECTOR_TYPE,
payload: { modalType: KeyManager.Mobile, isMipdProvider: false },
})

localWallet.setLocalWallet({ type: KeyManager.Mobile, deviceId })
localWallet.setLocalNativeWalletName(location.state?.vault?.label ?? 'label')
appDispatch(setWelcomeModal({ show: true }))
}
await queryClient.invalidateQueries({ queryKey: ['listWallets'] })
wallet.revoke()
appDispatch(setWelcomeModal({ show: true }))
savedWalletRef.current = null
} catch (e) {
console.error('Failed to connect wallet:', e)
connectionAttemptedRef.current = false
}
}, [location.state?.vault, dispatch, getAdapter, localWallet, appDispatch, setWelcomeModal])

useEffect(() => {
saveWallet()
}, [saveWallet])

useEffect(() => {
return () => {
if (savedWalletRef.current) {
savedWalletRef.current.revoke()
savedWalletRef.current = null
}
}
}, [])

const handleViewWallet = useCallback(async () => {
setIsConnecting(true)
try {
await handleWalletConnection()
onClose()
} finally {
setIsConnecting(false)
}
}, [
location.state?.vault,
dispatch,
getAdapter,
localWallet,
queryClient,
appDispatch,
setWelcomeModal,
])

const handleViewWallet = useCallback(() => {
saveAndSelectWallet()
onClose()
}, [onClose, saveAndSelectWallet])

const handleClose = useCallback(() => {
saveAndSelectWallet()
onClose()
appDispatch(setWelcomeModal({ show: true }))
}, [onClose, appDispatch, setWelcomeModal, saveAndSelectWallet])
}, [onClose, handleWalletConnection])

return (
<SlideTransition>
<DialogHeader>
<DialogHeaderRight>
<DialogCloseButton onClick={handleClose} />
<DialogCloseButton onClick={onClose} />
</DialogHeaderRight>
</DialogHeader>
<DialogBody>
Expand All @@ -126,7 +155,14 @@ export const CreateSuccess = ({ onClose }: CreateSuccessProps) => {
</VStack>
</DialogBody>
<DialogFooter>
<Button colorScheme='blue' size='lg' width='full' onClick={handleViewWallet}>
<Button
colorScheme='blue'
size='lg'
width='full'
onClick={handleViewWallet}
isLoading={isConnecting}
loadingText={translate('walletProvider.manualBackup.success.viewWallet')}
>
{translate('walletProvider.manualBackup.success.viewWallet')}
</Button>
</DialogFooter>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Button, Icon, VStack } from '@chakra-ui/react'
import type { NativeHDWallet } from '@shapeshiftoss/hdwallet-native'
import { useCallback } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import { useCallback, useEffect, useRef, useState } from 'react'
import { IoIosCheckmarkCircle } from 'react-icons/io'
import { useTranslate } from 'react-polyglot'
import type { Location } from 'react-router-dom'
Expand Down Expand Up @@ -32,12 +33,35 @@ export const ImportSuccess = ({ onClose }: ImportSuccessProps) => {
const { getAdapter, dispatch } = useWallet()
const localWallet = useLocalWallet()
const translate = useTranslate()
const queryClient = useQueryClient()
const connectionAttemptedRef = useRef(false)
const refreshAttemptedRef = useRef(false)
const [isConnecting, setIsConnecting] = useState(false)

const refreshWalletList = useCallback(async () => {
if (refreshAttemptedRef.current) return
refreshAttemptedRef.current = true
try {
await queryClient.invalidateQueries({ queryKey: ['listWallets'] })
} catch (e) {
console.error('Failed to refresh wallet list:', e)
refreshAttemptedRef.current = false
}
}, [queryClient])

useEffect(() => {
refreshWalletList()
}, [refreshWalletList])

const handleWalletConnection = useCallback(async () => {
if (connectionAttemptedRef.current) return
if (!location.state?.vault) return
const adapter = await getAdapter(KeyManager.Mobile)
if (!adapter) throw new Error('Native adapter not found')
connectionAttemptedRef.current = true

try {
const adapter = await getAdapter(KeyManager.Mobile)
if (!adapter) throw new Error('Native adapter not found')

const deviceId = location.state.vault.id ?? ''
const wallet = (await adapter.pairDevice(deviceId)) as NativeHDWallet
const mnemonic = location.state.vault.mnemonic
Expand Down Expand Up @@ -67,18 +91,29 @@ export const ImportSuccess = ({ onClose }: ImportSuccessProps) => {
type: WalletActions.SET_CONNECTOR_TYPE,
payload: { modalType: KeyManager.Mobile, isMipdProvider: false },
})
appDispatch(setWelcomeModal({ show: true }))
}
} catch (e) {
console.log(e)
console.error('Failed to connect wallet:', e)
connectionAttemptedRef.current = false
}
}, [getAdapter, location.state?.vault, dispatch, localWallet, appDispatch, setWelcomeModal])

const handleViewWallet = useCallback(async () => {
setIsConnecting(true)
try {
await handleWalletConnection()
onClose()
setTimeout(() => location.state?.vault?.revoke(), 500)
} finally {
setIsConnecting(false)
}
}, [getAdapter, location.state?.vault, dispatch, localWallet])
}, [onClose, handleWalletConnection, location.state?.vault])

const handleClose = useCallback(() => {
handleWalletConnection()
onClose()
appDispatch(setWelcomeModal({ show: true }))
setTimeout(() => location.state?.vault?.revoke(), 500)
}, [onClose, appDispatch, setWelcomeModal, handleWalletConnection, location.state?.vault])
}, [onClose, location.state?.vault])

return (
<SlideTransition>
Expand All @@ -105,7 +140,14 @@ export const ImportSuccess = ({ onClose }: ImportSuccessProps) => {
</VStack>
</DialogBody>
<DialogFooter>
<Button colorScheme='blue' size='lg' width='full' onClick={handleClose}>
<Button
colorScheme='blue'
size='lg'
width='full'
onClick={handleViewWallet}
isLoading={isConnecting}
loadingText={translate('walletProvider.manualBackup.success.viewWallet')}
>
{translate('walletProvider.manualBackup.success.viewWallet')}
</Button>
</DialogFooter>
Expand Down
56 changes: 29 additions & 27 deletions src/pages/ConnectWallet/MobileConnect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
Stack,
} from '@chakra-ui/react'
import { keyframes } from '@emotion/react'
import { useQuery as useReactQuery } from '@tanstack/react-query'
import { AnimatePresence, motion } from 'framer-motion'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslate } from 'react-polyglot'
Expand All @@ -33,7 +34,6 @@ import { SlideTransitionY } from '@/components/SlideTransitionY'
import { RawText, Text } from '@/components/Text'
import { WalletActions } from '@/context/WalletProvider/actions'
import { listWallets } from '@/context/WalletProvider/MobileWallet/mobileMessageHandlers'
import type { RevocableWallet } from '@/context/WalletProvider/MobileWallet/RevocableWallet'
import { useModal } from '@/hooks/useModal/useModal'
import { useQuery } from '@/hooks/useQuery/useQuery'
import { useWallet } from '@/hooks/useWallet/useWallet'
Expand Down Expand Up @@ -67,17 +67,40 @@ const BodyText: React.FC<TextProps> = props => (
export const MobileConnect = () => {
const { dispatch, state } = useWallet()
const translate = useTranslate()
const [wallets, setWallets] = useState<RevocableWallet[]>([])
const [error, setError] = useState<string | null>(null)
const [hideWallets, setHideWallets] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const scaleFadeAnimation = `${scaleFade} 0.6s cubic-bezier(0.76, 0, 0.24, 1)`
const hasWallet = Boolean(state.walletInfo?.deviceId)
const navigate = useNavigate()

const mobileWalletDialog = useModal('mobileWalletDialog')
const [isWaitingForRedirection, setIsWaitingForRedirection] = useState<boolean>(false)

const {
isLoading,
data: wallets = [],
error: queryError,
} = useReactQuery({
queryKey: ['listWallets'],
staleTime: 0,
gcTime: 0,
retry: false,
refetchOnMount: true,
queryFn: listWallets,
})

const error = useMemo(() => {
if (queryError) {
if (queryError instanceof Error) {
console.error('Failed to fetch wallets:', queryError.message)
}
return 'walletProvider.shapeShift.load.error.fetchingWallets'
}
if (!wallets.length && !isLoading) {
return 'walletProvider.shapeShift.load.error.noWallet'
}
return null
}, [queryError, wallets.length, isLoading])

const handleOpenCreateWallet = useCallback(() => {
mobileWalletDialog.open({ defaultRoute: MobileWalletDialogRoutes.Create })
}, [mobileWalletDialog])
Expand Down Expand Up @@ -122,34 +145,13 @@ export const MobileConnect = () => {
}
}, [navigate, hasWallet, query, state, dispatch, setIsWaitingForRedirection])

useEffect(() => {
if (!wallets.length) {
setIsLoading(true) // Set loading state to true when fetching wallets
;(async () => {
try {
const vaults = await listWallets()
if (!vaults.length) {
setError('walletProvider.shapeShift.load.error.noWallet')
} else {
setWallets(vaults)
}
} catch (e) {
console.log(e)
setError('An error occurred while fetching wallets.')
} finally {
setIsLoading(false) // Set loading state to false when fetching is done
}
})()
}
}, [wallets])

const handleToggleWallets = useCallback(() => {
setHideWallets(!hideWallets) // allow users with saved wallets to toggle between saved and create/import
setHideWallets(!hideWallets)
}, [hideWallets])

useEffect(() => {
if (!wallets.length && !isLoading) {
setHideWallets(true) // If they have no wallets, show the default create or import
setHideWallets(true)
}
}, [isLoading, wallets.length])

Expand Down