All files / app/pages/onboarding/04-restore-wallet restore-wallet.tsx

0% Statements 0/52
0% Branches 0/15
0% Functions 0/4
0% Lines 0/49

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139                                                                                                                                                                                                                                                                                     
import { ErrorLabel } from '@components/error-label';
import { ErrorText } from '@components/error-text';
import { ExternalLink } from '@components/external-link';
import { Hr } from '@components/hr';
import {
  Onboarding,
  OnboardingTitle,
  OnboardingButton,
  OnboardingText,
} from '@components/onboarding';
import routes from '@constants/routes.json';
import { useAnalytics } from '@hooks/use-analytics';
import { useBackButton } from '@hooks/use-back-url';
import { Text, Input, Flex, Box, color } from '@stacks/ui';
import { persistMnemonic } from '@store/keys/keys.actions';
import { parseSeedPhraseInput } from '@utils/parse-seed-phrase';
import { OnboardingSelector } from 'app/tests/features/onboarding.selectors';
import { validateMnemonic } from 'bip39';
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router';
 
export const RestoreWallet: React.FC = () => {
  useBackButton(routes.WELCOME);
 
  const [mnemonic, setMnemonic] = useState('');
  const [hasSubmitted, setHasSubmitted] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const history = useHistory();
  const dispatch = useDispatch();
  const analytics = useAnalytics();
 
  const handleMnemonicInput = (e: React.FormEvent<HTMLInputElement>) => {
    Iif (hasSubmitted) setHasSubmitted(false);
    Iif (error) setError(null);
    setMnemonic(e.currentTarget.value.trim());
  };
 
  const handleSecretKeyRestore = (e: React.FormEvent) => {
    e.preventDefault();
    setHasSubmitted(true);
 
    const parsedMnemonic = parseSeedPhraseInput(mnemonic);
 
    Iif (parsedMnemonic === null) {
      setError('Unable to parse Secret Key input');
      void analytics.track('submit_invalid_secret_key');
      return;
    }
 
    const mnemonicLength = parsedMnemonic.split(' ').length;
 
    Iif (mnemonicLength !== 12 && mnemonicLength !== 24) {
      setError('Leather can only be used with 12 and 24-word Secret Keys');
      void analytics.track('submit_invalid_secret_key');
      return;
    }
 
    Iif (!validateMnemonic(parsedMnemonic)) {
      void analytics.track('submit_invalid_secret_key');
      setError('bip39error');
      return;
    }
 
    dispatch(persistMnemonic(parsedMnemonic));
    history.push(routes.SET_PASSWORD);
  };
 
  return (
    <Onboarding as="form" onSubmit={handleSecretKeyRestore}>
      <OnboardingTitle>Sign in to your wallet</OnboardingTitle>
 
      <OnboardingText>Connect your Ledger hardware wallet or enter your Secret Key</OnboardingText>
      <OnboardingButton mt="extra-loose" onClick={() => history.push(routes.CONNECT_LEDGER)}>
        Continue with Ledger
      </OnboardingButton>
      <Hr mt="extra-loose" />
 
      <Box>
        <Flex flexDirection="row" alignItems="baseline">
          <Text textStyle="body.small.medium" mt="extra-loose" display="block">
            Secret Key
          </Text>
          <ExternalLink
            color={color('brand')}
            href="https://www.hiro.so/questions/what-are-secret-keys-and-how-do-they-work"
            fontSize="12px"
            ml="tight"
          >
            Learn more
          </ExternalLink>
        </Flex>
 
        <Text textStyle="caption" color={color('text-caption')}>
          Your Secret Key is a series of random words also known as a seed phrase.
        </Text>
      </Box>
      <Input
        onChange={handleMnemonicInput}
        as="textarea"
        mt="base-tight"
        minHeight="110px"
        placeholder="apple bounce ladder..."
        style={{
          resize: 'none',
          border: error ? `2px solid ${color('feedback-error')}` : '',
        }}
        data-test={OnboardingSelector.InputSecretKey}
      />
      {error && error !== 'bip39error' && (
        <ErrorLabel>
          <ErrorText>{error}</ErrorText>
        </ErrorLabel>
      )}
      {error && error === 'bip39error' && (
        <ErrorLabel>
          <ErrorText>
            Your Secret Key is not BIP-39 compliant.{' '}
            <ExternalLink
              display="inline-block"
              href="https://www.hiro.so/questions/what-are-secret-keys-and-how-do-they-work#Secret%20Keys%20must%20be%20BIP39%20compatible"
            >
              Learn more
            </ExternalLink>
          </ErrorText>
        </ErrorLabel>
      )}
      <OnboardingButton
        mt="loose"
        type="submit"
        mode="secondary"
        data-test={OnboardingSelector.BtnContinueWithKey}
      >
        Continue with Secret Key
      </OnboardingButton>
    </Onboarding>
  );
};