-
Notifications
You must be signed in to change notification settings - Fork 191
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fc7d0b4
commit 35cafb1
Showing
12 changed files
with
435 additions
and
137 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
216 changes: 216 additions & 0 deletions
216
src/context/WalletProvider/NativeWallet/components/NativeImportKeystore.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
import { | ||
Box, | ||
Button, | ||
FormControl, | ||
FormErrorMessage, | ||
Icon, | ||
Input, | ||
ModalBody, | ||
ModalHeader, | ||
Text as CText, | ||
useColorModeValue, | ||
VStack, | ||
} from '@chakra-ui/react' | ||
import { useCallback, useState } from 'react' | ||
import type { FieldValues } from 'react-hook-form' | ||
import { useForm } from 'react-hook-form' | ||
import { FaFile } from 'react-icons/fa' | ||
import { useTranslate } from 'react-polyglot' | ||
import type { RouteComponentProps } from 'react-router-dom' | ||
import { Text } from 'components/Text' | ||
import { NativeWalletRoutes } from 'context/WalletProvider/types' | ||
import { getMixPanel } from 'lib/mixpanel/mixPanelSingleton' | ||
import { MixPanelEvent } from 'lib/mixpanel/types' | ||
|
||
import type { NativeWalletValues } from '../types' | ||
|
||
const hoverSx = { borderColor: 'blue.500' } | ||
|
||
// TODO(gomes): use https://www.chakra-ui.com/docs/components/file-upload if/when we migrate to chakra@3 | ||
const FileUpload = ({ onFileSelect }: { onFileSelect: (file: File) => void }) => { | ||
const bgColor = useColorModeValue('gray.50', 'gray.800') | ||
const borderColor = useColorModeValue('gray.200', 'gray.600') | ||
const [isDragging, setIsDragging] = useState(false) | ||
const [filename, setFilename] = useState<string | null>(null) | ||
|
||
const handleDragEnter = useCallback((e: React.DragEvent) => { | ||
e.preventDefault() | ||
e.stopPropagation() | ||
setIsDragging(true) | ||
}, []) | ||
|
||
const handleDragLeave = useCallback((e: React.DragEvent) => { | ||
e.preventDefault() | ||
e.stopPropagation() | ||
setIsDragging(false) | ||
}, []) | ||
|
||
const handleDragOver = useCallback((e: React.DragEvent) => { | ||
e.preventDefault() | ||
e.stopPropagation() | ||
}, []) | ||
|
||
const processFile = useCallback( | ||
(file: File) => { | ||
setFilename(file.name) | ||
onFileSelect(file) | ||
}, | ||
[onFileSelect], | ||
) | ||
|
||
const handleDrop = useCallback( | ||
(e: React.DragEvent) => { | ||
e.preventDefault() | ||
e.stopPropagation() | ||
setIsDragging(false) | ||
|
||
const files = e.dataTransfer.files | ||
if (files?.[0]) { | ||
processFile(files[0]) | ||
} | ||
}, | ||
[processFile], | ||
) | ||
|
||
const handleFileInput = useCallback( | ||
(e: React.ChangeEvent<HTMLInputElement>) => { | ||
const files = e.target.files | ||
if (files?.[0]) { | ||
processFile(files[0]) | ||
} | ||
}, | ||
[processFile], | ||
) | ||
|
||
return ( | ||
<FormControl> | ||
<Input type='file' accept='.txt' onChange={handleFileInput} id='file-upload' display='none' /> | ||
<Box | ||
as='label' | ||
htmlFor='file-upload' | ||
w='full' | ||
h='32' | ||
border='2px' | ||
borderStyle='dashed' | ||
borderColor={isDragging ? 'blue.500' : borderColor} | ||
borderRadius='xl' | ||
display='flex' | ||
flexDirection='column' | ||
alignItems='center' | ||
justifyContent='center' | ||
bg={bgColor} | ||
cursor='pointer' | ||
transition='all 0.2s' | ||
_hover={hoverSx} | ||
onDragEnter={handleDragEnter} | ||
onDragOver={handleDragOver} | ||
onDragLeave={handleDragLeave} | ||
onDrop={handleDrop} | ||
> | ||
<Icon as={FaFile} boxSize={6} color='gray.500' mb={2} /> | ||
{filename ? ( | ||
<CText color='gray.500'>{filename}</CText> | ||
) : ( | ||
<Text color='gray.500' translation='walletProvider.shapeShift.import.dragAndDrop' /> | ||
)} | ||
</Box> | ||
</FormControl> | ||
) | ||
} | ||
|
||
export const NativeImportKeystore = ({ history }: RouteComponentProps) => { | ||
const [keystoreFile, setKeystoreFile] = useState<string | null>(null) | ||
const mixpanel = getMixPanel() | ||
|
||
const translate = useTranslate() | ||
|
||
const { | ||
setError, | ||
handleSubmit, | ||
formState: { errors, isSubmitting }, | ||
register, | ||
} = useForm<NativeWalletValues>({ shouldUnregister: true }) | ||
|
||
const onSubmit = useCallback( | ||
async (values: FieldValues) => { | ||
const { Vault } = await import('@shapeshiftoss/hdwallet-native-vault') | ||
const vault = await Vault.create() | ||
vault.meta.set('createdAt', Date.now()) | ||
|
||
if (!keystoreFile) { | ||
throw new Error('No keystore uploaded') | ||
} | ||
|
||
try { | ||
await vault.loadFromKeystore(keystoreFile, values.keystorePassword) | ||
} catch (e) { | ||
setError('keystorePassword', { | ||
type: 'manual', | ||
message: translate('walletProvider.shapeShift.import.invalidKeystorePassword'), | ||
}) | ||
return | ||
} | ||
|
||
history.push(NativeWalletRoutes.Password, { vault }) | ||
mixpanel?.track(MixPanelEvent.NativeImportKeystore) | ||
}, | ||
[history, keystoreFile, mixpanel, setError, translate], | ||
) | ||
|
||
const handleFileSelect = useCallback((file: File) => { | ||
const reader = new FileReader() | ||
reader.onload = e => { | ||
if (!e?.target) return | ||
if (typeof e.target.result !== 'string') return | ||
setKeystoreFile(e.target.result) | ||
} | ||
reader.readAsText(file) | ||
}, []) | ||
|
||
return ( | ||
<> | ||
<ModalHeader> | ||
<Text translation={'walletProvider.shapeShift.import.keystoreHeader'} /> | ||
</ModalHeader> | ||
<ModalBody> | ||
<Text | ||
color='text.subtle' | ||
mb={4} | ||
translation='walletProvider.shapeShift.import.keystoreImportBody' | ||
/> | ||
|
||
<form onSubmit={handleSubmit(onSubmit)}> | ||
<VStack spacing={6}> | ||
<FileUpload onFileSelect={handleFileSelect} /> | ||
|
||
{keystoreFile && ( | ||
<> | ||
<FormControl isInvalid={Boolean(errors.keystorePassword)}> | ||
<Input | ||
type='password' | ||
placeholder='Keystore Password' | ||
size='lg' | ||
data-test='wallet-native-keystore-password' | ||
{...register('keystorePassword')} | ||
/> | ||
<FormErrorMessage>{errors.keystorePassword?.message}</FormErrorMessage> | ||
</FormControl> | ||
|
||
<Button | ||
colorScheme='blue' | ||
width='full' | ||
size='lg' | ||
type='submit' | ||
isLoading={isSubmitting} | ||
data-test='wallet-native-keystore-submit' | ||
> | ||
<Text translation='walletProvider.shapeShift.import.importKeystore' /> | ||
</Button> | ||
</> | ||
)} | ||
</VStack> | ||
</form> | ||
</ModalBody> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
src/context/WalletProvider/NativeWallet/components/NativeImportSelect.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { ArrowForwardIcon } from '@chakra-ui/icons' | ||
import { Button, HStack, ModalBody, ModalHeader, Stack, VStack } from '@chakra-ui/react' | ||
import { useCallback } from 'react' | ||
import { FaFile, FaKey } from 'react-icons/fa' | ||
import type { RouteComponentProps } from 'react-router' | ||
import { Text } from 'components/Text' | ||
import { NativeWalletRoutes } from 'context/WalletProvider/types' | ||
|
||
const arrowForwardIcon = <ArrowForwardIcon /> | ||
|
||
export const NativeImportSelect = ({ history }: RouteComponentProps) => { | ||
const handleImportKeystoreClick = useCallback( | ||
() => history.push(NativeWalletRoutes.ImportKeystore), | ||
[history], | ||
) | ||
const handleImportSeedClick = useCallback( | ||
() => history.push(NativeWalletRoutes.ImportSeed), | ||
[history], | ||
) | ||
|
||
return ( | ||
<> | ||
<ModalHeader> | ||
<Text translation={'walletProvider.shapeShift.start.selectHeader'} /> | ||
</ModalHeader> | ||
<ModalBody> | ||
<Text | ||
mb={4} | ||
color='text.subtle' | ||
translation={'walletProvider.shapeShift.start.selectBody'} | ||
/> | ||
<Stack mt={6} spacing={4}> | ||
<Button | ||
w='full' | ||
h='auto' | ||
px={6} | ||
py={4} | ||
justifyContent='space-between' | ||
rightIcon={arrowForwardIcon} | ||
onClick={handleImportSeedClick} | ||
data-test='wallet-native-import-button' | ||
> | ||
<HStack spacing={4}> | ||
<FaKey size={20} /> | ||
<VStack spacing={0} align='flex-start'> | ||
<Text translation={'walletProvider.shapeShift.start.secretRecoveryPhrase'} /> | ||
<Text | ||
fontSize='sm' | ||
color='gray.500' | ||
translation='walletProvider.shapeShift.start.twelveWordSeedPhrase' | ||
/> | ||
</VStack> | ||
</HStack> | ||
</Button> | ||
<Button | ||
w='full' | ||
h='auto' | ||
px={6} | ||
py={4} | ||
justifyContent='space-between' | ||
rightIcon={arrowForwardIcon} | ||
onClick={handleImportKeystoreClick} | ||
data-test='wallet-native-create-button' | ||
> | ||
<HStack spacing={4}> | ||
<FaFile size={20} /> | ||
<VStack spacing={0} align='flex-start'> | ||
<Text translation={'walletProvider.shapeShift.start.keystore'} /> | ||
<Text | ||
fontSize='sm' | ||
color='gray.500' | ||
translation='walletProvider.shapeShift.start.uploadKeystore' | ||
/> | ||
</VStack> | ||
</HStack> | ||
</Button> | ||
</Stack> | ||
</ModalBody> | ||
</> | ||
) | ||
} |
Oops, something went wrong.