diff --git a/apps/browser-extension-wallet/src/hooks/useWalletManager.ts b/apps/browser-extension-wallet/src/hooks/useWalletManager.ts index fda5f5640..cd1bc0234 100644 --- a/apps/browser-extension-wallet/src/hooks/useWalletManager.ts +++ b/apps/browser-extension-wallet/src/hooks/useWalletManager.ts @@ -208,6 +208,10 @@ export interface UseWalletManager { reloadWallet: () => Promise; addAccount: (props: WalletManagerAddAccountProps) => Promise; getMnemonic: (passphrase: Uint8Array) => Promise; + getMnemonicForWallet: ( + wallet: AnyWallet, + passphrase: Uint8Array + ) => Promise; getSharedWalletExtendedPublicKey: (passphrase: Uint8Array) => Promise; enableCustomNode: (network: EnvironmentTypes, value: string) => Promise; generateSharedWalletKey: GenerateSharedWalletKeyFn; @@ -1237,6 +1241,28 @@ export const useWalletManager = (): UseWalletManager => { } }; + const getMnemonicForWallet = useCallback( + async (wallet: AnyWallet, passphrase: Uint8Array) => { + switch (wallet.type) { + case WalletType.InMemory: { + const keyMaterialBytes = await Wallet.KeyManagement.emip3decrypt( + Buffer.from(wallet.encryptedSecrets.keyMaterial, 'hex'), + passphrase + ); + const keyMaterialBuffer = Buffer.from(keyMaterialBytes); + const mnemonic = keyMaterialBuffer.toString('utf8').split(' '); + clearBytes(passphrase); + clearBytes(keyMaterialBytes); + return mnemonic; + } + case WalletType.Ledger: + case WalletType.Trezor: + throw new Error('Mnemonic is not available for hardware wallets'); + } + }, + [] + ); + const getMnemonic = useCallback( async (passphrase: Uint8Array) => { const { activeBlockchain } = await backgroundService.getBackgroundStorage(); @@ -1256,27 +1282,9 @@ export const useWalletManager = (): UseWalletManager => { throw new Error('Wallet not found'); } - switch (wallet.type) { - case WalletType.InMemory: { - const keyMaterialBytes = await Wallet.KeyManagement.emip3decrypt( - Buffer.from(wallet.encryptedSecrets.keyMaterial, 'hex'), - passphrase - ); - - const keyMaterialBuffer = Buffer.from(keyMaterialBytes); - - const mnemonic = keyMaterialBuffer.toString('utf8').split(' '); - clearBytes(passphrase); - clearBytes(keyMaterialBytes); - clearBytes(keyMaterialBuffer); - return mnemonic; - } - case WalletType.Ledger: - case WalletType.Trezor: - throw new Error('Mnemonic is not available for hardware wallets'); - } + return getMnemonicForWallet(wallet, passphrase); }, - [backgroundService] + [backgroundService, getMnemonicForWallet] ); const getSharedWalletExtendedPublicKey = useCallback( @@ -1471,6 +1479,7 @@ export const useWalletManager = (): UseWalletManager => { bitcoinWalletManager, bitcoinWallet, getMnemonic, + getMnemonicForWallet, enableCustomNode, generateSharedWalletKey, createMultiSigAccount, diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/CreateWallet.test.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/CreateWallet.test.tsx index 8baa2a2a3..aa38cca6c 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/CreateWallet.test.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/CreateWallet.test.tsx @@ -126,15 +126,25 @@ describe('Multi Wallet Setup/Create Wallet', () => { ); + // Initial state - on SelectBlockchain step await screen.findByTestId('wallet-setup-step-btn-next'); expect(formDirty).toBe(false); + // Navigate to RecoveryPhraseWriteDown step const nextButton = getNextButton(); fireEvent.click(nextButton); + await screen.findByTestId('wallet-setup-step-btn-next'); + expect(formDirty).toBe(false); + + // Navigate to RecoveryPhraseInput step - this marks form as dirty + const nextButton2 = getNextButton(); + fireEvent.click(nextButton2); await waitFor(() => expect(formDirty).toBe(true)); + // Go back to RecoveryPhraseWriteDown - form should still be dirty const backButton = getBackButton(); fireEvent.click(backButton); - await waitFor(() => expect(formDirty).toBe(false)); + await screen.findByTestId('wallet-setup-step-btn-next'); + expect(formDirty).toBe(true); }); }); diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/CreateWallet.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/CreateWallet.tsx index 14f2807ee..99d23b6ab 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/CreateWallet.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/CreateWallet.tsx @@ -7,6 +7,9 @@ import { ChooseRecoveryMethod } from './steps/ChooseRecoveryMethod'; import { SecurePaperWallet } from './steps/SecurePaperWallet'; import { SavePaperWallet } from './steps/SavePaperWallet'; import { SelectBlockchain } from './steps/SelectBlockchain'; +import { ReuseRecoveryPhrase } from './steps/ReuseRecoveryPhrase'; +import { EnterWalletPassword } from './steps/EnterWalletPassword'; +import { RecoveryPhraseError } from './steps/RecoveryPhraseError'; export const CreateWallet = (): JSX.Element => ( @@ -23,6 +26,12 @@ export const CreateWallet = (): JSX.Element => ( case WalletCreateStep.RecoveryPhraseWriteDown: case WalletCreateStep.RecoveryPhraseInput: return ; + case WalletCreateStep.ReuseRecoveryPhrase: + return ; + case WalletCreateStep.EnterWalletPassword: + return ; + case WalletCreateStep.RecoveryPhraseError: + return ; // Common steps case WalletCreateStep.Setup: return ; diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/context.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/context.tsx index dff97be81..ac49e8320 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/context.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/context.tsx @@ -1,5 +1,5 @@ /* eslint-disable unicorn/no-null, complexity */ -import { CreateWalletParams } from '@hooks'; +import { CreateWalletParams, useLocalStorage } from '@hooks'; import { Wallet } from '@lace/cardano'; import { walletRoutePaths } from '@routes'; import React, { createContext, useCallback, useContext, useMemo, useState } from 'react'; @@ -10,7 +10,10 @@ import { WalletCreateStep } from './types'; import { RecoveryMethod } from '../types'; import { usePostHogClientContext } from '@providers/PostHogClientProvider'; import { PublicPgpKeyData } from '@src/types'; -import { Blockchain } from '@cardano-sdk/web-extension'; +import { Blockchain, AnyWallet, WalletConflictError, WalletType } from '@cardano-sdk/web-extension'; +import { useObservable } from '@lace/common'; +import { walletRepository } from '@lib/wallet-api-ui'; +import { getWalletBlockchain } from './get-wallet-blockchain'; type OnNameChange = (state: { name: string }) => void; interface PgpValidation { @@ -32,6 +35,13 @@ interface State { setPgpValidation: React.Dispatch>; selectedBlockchain: Blockchain; setSelectedBlockchain: React.Dispatch>; + walletToReuse: AnyWallet | null; + setWalletToReuse: React.Dispatch< + React.SetStateAction | null> + >; + showRecoveryPhraseError: () => void; + setMnemonic: (mnemonic: string[]) => void; + nonSelectedBlockchainWallets: AnyWallet[] | undefined; } interface Props { @@ -62,11 +72,17 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => } = useHotWalletCreation({ initialMnemonic: Wallet.KeyManagement.util.generateMnemonicWords() }); + const [, { updateLocalStorage: setShowWalletConflictError }] = useLocalStorage('showWalletConflictError', false); const [selectedBlockchain, setSelectedBlockchain] = useState('Cardano'); const [step, setStep] = useState(WalletCreateStep.SelectBlockchain); + const [walletToReuse, setWalletToReuse] = useState | null>( + null + ); const [recoveryMethod, setRecoveryMethod] = useState('mnemonic'); const [pgpInfo, setPgpInfo] = useState(INITIAL_PGP_STATE); const [pgpValidation, setPgpValidation] = useState({ error: null, success: null }); + const wallets = useObservable(walletRepository.wallets$); + const generateMnemonic = useCallback(() => { setCreateWalletData((prevState) => ({ ...prevState, mnemonic: Wallet.KeyManagement.util.generateMnemonicWords() })); }, [setCreateWalletData]); @@ -82,6 +98,26 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => console.error('finalizeBitcoinWalletCreation'); }, []); + const nonSelectedBlockchainWallets = useMemo( + () => + wallets?.filter( + (wallet) => + getWalletBlockchain(wallet).toLowerCase() !== selectedBlockchain.toLowerCase() && + wallet.type === WalletType.InMemory + ), + [selectedBlockchain, wallets] + ); + + const showRecoveryPhraseError = useCallback(() => setStep(WalletCreateStep.RecoveryPhraseError), [setStep]); + const setMnemonic = useCallback( + (mnemonic: string[]) => + setCreateWalletData((prevState) => ({ + ...prevState, + mnemonic + })), + [setCreateWalletData] + ); + const finalizeWalletCreation = useCallback( async (params: Partial) => { const wallet = await createHotWallet({ ...params, blockchain: selectedBlockchain }); @@ -96,15 +132,37 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => [createHotWallet, selectedBlockchain, sendPostWalletAddAnalytics, postHogActions.create.WALLET_ADDED, pgpInfo] ); + const handleSetupStep = useCallback( + // eslint-disable-next-line consistent-return + async (state: Partial) => { + if (recoveryMethod !== 'mnemonic' && recoveryMethod !== 'mnemonic-bitcoin') { + return setStep(WalletCreateStep.SavePaperWallet); + } + + try { + const finalizationFn = recoveryMethod === 'mnemonic' ? finalizeWalletCreation : finalizeBitcoinWalletCreation; + await finalizationFn(state); + } catch (error) { + if (error instanceof WalletConflictError) { + setShowWalletConflictError(true); + } else { + throw error; + } + } finally { + history.push(walletRoutePaths.assets); + window.location.reload(); + } + }, + [recoveryMethod, finalizeWalletCreation, finalizeBitcoinWalletCreation, history, setShowWalletConflictError] + ); + const next: State['next'] = useCallback( - // eslint-disable-next-line max-statements async (state) => { if (state) { setCreateWalletData((prevState) => ({ ...prevState, ...state })); } switch (step) { case WalletCreateStep.SelectBlockchain: { - setFormDirty(true); setStep( paperWalletEnabled ? WalletCreateStep.ChooseRecoveryMethod : WalletCreateStep.RecoveryPhraseWriteDown ); @@ -112,12 +170,27 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => } case WalletCreateStep.ChooseRecoveryMethod: { if (recoveryMethod === 'mnemonic' || recoveryMethod === 'mnemonic-bitcoin') { - setStep(WalletCreateStep.RecoveryPhraseWriteDown); + const nextStep = + nonSelectedBlockchainWallets.length > 0 + ? WalletCreateStep.ReuseRecoveryPhrase + : WalletCreateStep.RecoveryPhraseWriteDown; + setStep(nextStep); break; } setStep(WalletCreateStep.SecurePaperWallet); break; } + case WalletCreateStep.ReuseRecoveryPhrase: { + setStep(WalletCreateStep.EnterWalletPassword); + break; + } + case WalletCreateStep.EnterWalletPassword: + setStep(WalletCreateStep.Setup); + break; + case WalletCreateStep.RecoveryPhraseError: { + setStep(WalletCreateStep.RecoveryPhraseWriteDown); + break; + } case WalletCreateStep.RecoveryPhraseWriteDown: { setFormDirty(true); setStep(WalletCreateStep.RecoveryPhraseInput); @@ -129,21 +202,7 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => break; } case WalletCreateStep.Setup: { - if (recoveryMethod === 'mnemonic') { - await finalizeWalletCreation(state); - history.push(walletRoutePaths.assets); - window.location.reload(); - break; - } - - if (recoveryMethod === 'mnemonic-bitcoin') { - await finalizeBitcoinWalletCreation(); - history.push(walletRoutePaths.assets); - window.location.reload(); - break; - } - - setStep(WalletCreateStep.SavePaperWallet); + await handleSetupStep(state); break; } case WalletCreateStep.SavePaperWallet: { @@ -158,12 +217,13 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => [ step, setCreateWalletData, + setFormDirty, paperWalletEnabled, recoveryMethod, - setFormDirty, + nonSelectedBlockchainWallets?.length, + handleSetupStep, finalizeWalletCreation, - history, - finalizeBitcoinWalletCreation + history ] ); @@ -186,10 +246,22 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => : history.push(walletRoutePaths.newWallet.root); break; } + case WalletCreateStep.ReuseRecoveryPhrase: { + setStep(WalletCreateStep.RecoveryPhraseWriteDown); + break; + } case WalletCreateStep.SecurePaperWallet: { setStep(WalletCreateStep.ChooseRecoveryMethod); break; } + case WalletCreateStep.EnterWalletPassword: { + setStep(WalletCreateStep.ReuseRecoveryPhrase); + break; + } + case WalletCreateStep.RecoveryPhraseError: { + setStep(WalletCreateStep.ReuseRecoveryPhrase); + break; + } case WalletCreateStep.RecoveryPhraseInput: { setFormDirty(false); generateMnemonic(); @@ -225,7 +297,12 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => pgpValidation, setPgpValidation, selectedBlockchain, - setSelectedBlockchain + setSelectedBlockchain, + setWalletToReuse, + walletToReuse, + showRecoveryPhraseError, + setMnemonic, + nonSelectedBlockchainWallets }), [ back, @@ -234,13 +311,13 @@ export const CreateWalletProvider = ({ children }: Props): React.ReactElement => onNameChange, step, recoveryMethod, - setRecoveryMethod, pgpInfo, - setPgpInfo, pgpValidation, - setPgpValidation, selectedBlockchain, - setSelectedBlockchain + walletToReuse, + showRecoveryPhraseError, + setMnemonic, + nonSelectedBlockchainWallets ] ); diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/get-wallet-blockchain.ts b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/get-wallet-blockchain.ts new file mode 100644 index 000000000..88299b87c --- /dev/null +++ b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/get-wallet-blockchain.ts @@ -0,0 +1,13 @@ +import { AnyWallet, Blockchain } from '@cardano-sdk/web-extension'; +import { Wallet } from '@lace/cardano'; + +type AnyWalletWithBlockchainName = AnyWallet & { + blockchainName: string; +}; + +const hasBlockchainName = ( + wallet: AnyWallet +): wallet is AnyWalletWithBlockchainName => typeof (wallet as Record)?.blockchainName === 'string'; + +export const getWalletBlockchain = (wallet: AnyWallet): Blockchain => + hasBlockchainName(wallet) ? (wallet.blockchainName as Blockchain) : 'Cardano'; diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/EnterWalletPassword.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/EnterWalletPassword.tsx new file mode 100644 index 000000000..e4d4344a2 --- /dev/null +++ b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/EnterWalletPassword.tsx @@ -0,0 +1,48 @@ +import React, { ReactElement } from 'react'; +import { WalletSetupEnterPasswordStep } from '@lace/core'; +import { useCreateWallet } from '../context'; +import { useWalletManager } from '@hooks'; +import { WalletConflictError } from '@cardano-sdk/web-extension'; +import { useTranslation } from 'react-i18next'; + +const SUPPORTED_PASSPHRASE_LENGTH = 24; + +export const EnterWalletPassword = (): ReactElement => { + const { back, walletToReuse, setMnemonic, next, showRecoveryPhraseError } = useCreateWallet(); + const { getMnemonicForWallet } = useWalletManager(); + const [errorMessage, setErrorMessage] = React.useState(); + const { t } = useTranslation(); + + const handleSubmit = async (password: string) => { + try { + const mnemonic = await getMnemonicForWallet(walletToReuse, Buffer.from(password)); + if (mnemonic.length < SUPPORTED_PASSPHRASE_LENGTH) { + showRecoveryPhraseError(); + return; + } + + setMnemonic(mnemonic); + await next(); + } catch (error) { + if (error instanceof Error && error.message.includes('Unsupported state or unable to authenticate data')) { + setErrorMessage(t('walletSetup.reuseRecoveryPhrase.invalidPassword')); + return; + } + + if (error instanceof WalletConflictError) { + setErrorMessage(t('walletSetup.reuseRecoveryPhrase.walletAlreadyExists')); + return; + } + setErrorMessage(error.message); + } + }; + + return ( + + ); +}; diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/RecoveryPhraseError.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/RecoveryPhraseError.tsx new file mode 100644 index 000000000..d6e81d495 --- /dev/null +++ b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/RecoveryPhraseError.tsx @@ -0,0 +1,9 @@ +import React, { ReactElement } from 'react'; +import { WalletSetupMnemonicErrorStep } from '@lace/core'; +import { useCreateWallet } from '../context'; + +export const RecoveryPhraseError = (): ReactElement => { + const { next, back } = useCreateWallet(); + + return ; +}; diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/ReuseRecoveryPhrase.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/ReuseRecoveryPhrase.tsx new file mode 100644 index 000000000..060704268 --- /dev/null +++ b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/steps/ReuseRecoveryPhrase.tsx @@ -0,0 +1,16 @@ +import React, { ReactElement } from 'react'; +import { WalletSetupReuseMnemonicStep } from '@lace/core'; +import { useCreateWallet } from '../context'; + +export const ReuseRecoveryPhrase = (): ReactElement => { + const { back, next, setWalletToReuse, nonSelectedBlockchainWallets } = useCreateWallet(); + + return ( + + ); +}; diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/types.ts b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/types.ts index 1dd970d18..f1320d109 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/types.ts +++ b/apps/browser-extension-wallet/src/views/browser-view/features/multi-wallet/create-wallet/types.ts @@ -2,6 +2,9 @@ export enum WalletCreateStep { // Legacy RecoveryPhraseInput = 'RecoveryPhraseInput', RecoveryPhraseWriteDown = 'RecoveryPhraseWriteDown', + ReuseRecoveryPhrase = 'ReuseRecoveryPhrase', + EnterWalletPassword = 'EnterWalletPassword', + RecoveryPhraseError = 'RecoveryPhraseError', // Paper wallet ChooseRecoveryMethod = 'ChooseRecoveryMethod', SecurePaperWallet = 'SecurePaperWallet', diff --git a/package.json b/package.json index 243a1e818..039a869e5 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "ws@^8.8.0": "^8.17.1" }, "dependencies": { - "@input-output-hk/lace-ui-toolkit": "3.5.0", + "@input-output-hk/lace-ui-toolkit": "3.9.2", "axios": "^1.9.0", "normalize.css": "^8.0.1", "openpgp": "^6.1.1", diff --git a/packages/core/package.json b/packages/core/package.json index 9397034b2..5af7ee508 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -45,7 +45,7 @@ "@cardano-sdk/crypto": "0.4.4", "@cardano-sdk/wallet": "0.53.22", "@cardano-sdk/web-extension": "0.39.33", - "@input-output-hk/lace-ui-toolkit": "3.8.0", + "@input-output-hk/lace-ui-toolkit": "3.9.2", "@lace/cardano": "0.1.0", "@lace/common": "0.1.0", "@lace/translation": "0.1.0", diff --git a/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupEnterPasswordStep.tsx b/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupEnterPasswordStep.tsx new file mode 100644 index 000000000..5e32a58d4 --- /dev/null +++ b/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupEnterPasswordStep.tsx @@ -0,0 +1,63 @@ +import React, { ReactElement } from 'react'; +import { WalletSetupStepLayoutRevamp } from './WalletSetupStepLayoutRevamp'; +import { WalletTimelineSteps } from '@ui/components/WalletSetup'; +import { Box, PasswordBox, Text } from '@input-output-hk/lace-ui-toolkit'; +import { useSecrets } from '@ui/hooks'; +import { Trans, useTranslation } from 'react-i18next'; + +type WalletSetupConfirmPasswordStepProps = { + walletName: string; + errorMessage?: string; + onBack: () => void; + onNext: (password: string) => void; +}; + +export const WalletSetupEnterPasswordStep = ({ + walletName, + errorMessage, + onNext, + onBack +}: WalletSetupConfirmPasswordStepProps): ReactElement => { + const { password, setPassword, clearSecrets } = useSecrets(); + const { t } = useTranslation(); + + const renderDescription = () => ( + + }} + /> + + ); + + const handleOnNext = () => { + if (!password.value) return; + onNext(password.value); + clearSecrets(); + }; + + return ( + + + {t('core.walletSetupReuseRecoveryPhrase.password')} + + handleOnNext()} + onChange={(target) => setPassword(target)} + data-testid="wallet-setup-enter-password-input" + errorMessage={errorMessage} + autoFocus + /> + + ); +}; diff --git a/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupMnemonicErrorStep.tsx b/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupMnemonicErrorStep.tsx new file mode 100644 index 000000000..11cffbe98 --- /dev/null +++ b/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupMnemonicErrorStep.tsx @@ -0,0 +1,36 @@ +import React, { ReactElement } from 'react'; +import { WalletTimelineSteps } from '@ui/components/WalletSetup'; +import { WalletSetupStepLayoutRevamp } from './WalletSetupStepLayoutRevamp'; +import { ListEmptyState } from '@ui/components/ListEmptyState'; +import { Flex, Text } from '@input-output-hk/lace-ui-toolkit'; +import { useTranslation } from 'react-i18next'; + +interface WalletSetupMnemonicErrorStepProps { + onBack: () => void; + onNext: () => void; +} + +export const WalletSetupMnemonicErrorStep = ({ onBack, onNext }: WalletSetupMnemonicErrorStepProps): ReactElement => { + const { t } = useTranslation(); + + const renderDescription = () => ( + + {t('core.walletSetupReuseRecoveryPhrase.error')} + {t('core.walletSetupReuseRecoveryPhrase.supportedRecoveryPhrase')} + {t('core.walletSetupReuseRecoveryPhrase.createNewRecoveryPhrase')} + + ); + + return ( + + + + ); +}; diff --git a/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupReuseMnemonicStep.tsx b/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupReuseMnemonicStep.tsx new file mode 100644 index 000000000..ff8771d6c --- /dev/null +++ b/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupReuseMnemonicStep.tsx @@ -0,0 +1,70 @@ +import React, { ReactElement, useEffect, useState } from 'react'; +import { WalletSetupStepLayoutRevamp } from './WalletSetupStepLayoutRevamp'; +import { WalletTimelineSteps } from '@ui/components/WalletSetup'; +import { Box, Flex, Select, Text } from '@input-output-hk/lace-ui-toolkit'; +import { AnyWallet } from '@cardano-sdk/web-extension'; +import { Wallet } from '@lace/cardano'; +import { useTranslation } from 'react-i18next'; + +type WalletSetupReuseMnemonicStepProps = { + wallets: AnyWallet[]; + setWalletToReuse: React.Dispatch< + React.SetStateAction | undefined> + >; + onBack: () => void; + onNext: () => void; +}; + +export const WalletSetupReuseMnemonicStep = ({ + wallets = [], + setWalletToReuse, + onNext, + onBack +}: WalletSetupReuseMnemonicStepProps): ReactElement => { + const [selectedWallet, setSelectedWallet] = useState(wallets[0]?.walletId); + const { t } = useTranslation(); + + useEffect(() => { + setSelectedWallet(wallets[0]?.walletId); + setWalletToReuse(wallets[0]); + }, [setWalletToReuse, wallets]); + + const handleOnChange = (value: string) => { + setSelectedWallet(value); + const wallet = wallets.find((w) => w.walletId === value); + setWalletToReuse(wallet); + }; + + return ( + + + {t('core.walletSetupReuseRecoveryPhrase.selectWallet')} + + + {wallets.map(({ walletId, metadata }) => ( + + ))} + + + + + ); +}; diff --git a/packages/core/src/ui/components/WalletSetupRevamp/index.ts b/packages/core/src/ui/components/WalletSetupRevamp/index.ts index e08a5716f..c7bde9b1e 100644 --- a/packages/core/src/ui/components/WalletSetupRevamp/index.ts +++ b/packages/core/src/ui/components/WalletSetupRevamp/index.ts @@ -11,3 +11,6 @@ export { WalletSetupNamePasswordStepRevamp } from './WalletSetupNamePasswordStep export { WalletSetupConnectHardwareWalletStepRevamp } from './WalletSetupConnectHardwareWalletStepRevamp'; export { WalletSetupHWCreationStep } from './WalletSetupHWCreationStep'; export { WalletSetupSelectBlockchain } from './WalletSetupSelectBlockchain'; +export { WalletSetupReuseMnemonicStep } from './WalletSetupReuseMnemonicStep'; +export { WalletSetupEnterPasswordStep } from './WalletSetupEnterPasswordStep'; +export { WalletSetupMnemonicErrorStep } from './WalletSetupMnemonicErrorStep'; diff --git a/packages/translation/src/lib/translations/browser-extension-wallet/en.json b/packages/translation/src/lib/translations/browser-extension-wallet/en.json index b00c1ec83..b955a82d9 100644 --- a/packages/translation/src/lib/translations/browser-extension-wallet/en.json +++ b/packages/translation/src/lib/translations/browser-extension-wallet/en.json @@ -907,6 +907,8 @@ "walletSetup.setupOptions.restoreWallet": "Restore wallet", "walletSetup.walletName.pleaseNameYourWallet": "Please name your wallet", "walletSetup.walletName.walletNameDescription": "Lorem ipsum dolor sit amet", + "walletSetup.reuseRecoveryPhrase.walletAlreadyExists": "This wallet already exists", + "walletSetup.reuseRecoveryPhrase.invalidPassword": "Invalid password", "midnightEventBanner.title": "Discover the Midnight Token Distribution", "midnightEventBanner.description": "The free, multi-phase distribution of NIGHT tokens aimed at empowering a broad, diverse community to build the future of the Midnight network", "midnightEventBanner.learnMore": "Learn more", diff --git a/packages/translation/src/lib/translations/browser-extension-wallet/es.json b/packages/translation/src/lib/translations/browser-extension-wallet/es.json index cbc05c59d..fd2fd2c05 100644 --- a/packages/translation/src/lib/translations/browser-extension-wallet/es.json +++ b/packages/translation/src/lib/translations/browser-extension-wallet/es.json @@ -889,6 +889,8 @@ "walletSetup.setupOptions.restoreWallet": "Recuperar la billetera", "walletSetup.walletName.pleaseNameYourWallet": "Dé nombre a su billetera, por favor", "walletSetup.walletName.walletNameDescription": "Lorem ipsum dolor sit amet", + "walletSetup.reuseRecoveryPhrase.walletAlreadyExists": "Esta billetera ya existe", + "walletSetup.reuseRecoveryPhrase.invalidPassword": "Contraseña inválida", "midnightEventBanner.title": "Descubra la distribución de tokens de Midnight", "midnightEventBanner.description": "La distribución gratuita en múltiples fases de tokens NIGHT, con objetivo de empoderar a una comunidad amplia y diversa a construir el futuro de la red Midnight", "midnightEventBanner.learnMore": "Aprender más", diff --git a/packages/translation/src/lib/translations/core/en.json b/packages/translation/src/lib/translations/core/en.json index 52691ea9a..726792ae3 100644 --- a/packages/translation/src/lib/translations/core/en.json +++ b/packages/translation/src/lib/translations/core/en.json @@ -684,6 +684,20 @@ "core.WalletSetupSelectBlockchain.bitcoin.description": "Allows you to manage your Bitcoin (BTC) and other assets on the Bitcoin blockchain", "core.WalletSetupSelectBlockchain.defaultBadge": "Default", "core.WalletSetupSelectBlockchain.newBadge": "New", + "core.walletSetupReuseRecoveryPhrase.title": "Reuse your Recovery Phrase?", + "core.walletSetupReuseRecoveryPhrase.description": "Do you wish to use the same passphrase as the wallet below, or wish to create a new one?", + "core.walletSetupReuseRecoveryPhrase.useSameRecoveryPhrase": "Use same recovery phrase", + "core.walletSetupReuseRecoveryPhrase.createNewOne": "Create a new one", + "core.walletSetupReuseRecoveryPhrase.selectWallet": "Select a wallet", + "core.walletSetupReuseRecoveryPhrase.confirmPassword": "Confirm your password", + "core.walletSetupReuseRecoveryPhrase.insertPassword": "Insert your password", + "core.walletSetupReuseRecoveryPhrase.confirmPasswordDescription": "Confirm typing your password below for \"{{walletName}}\"", + "core.walletSetupReuseRecoveryPhrase.password": "Password", + "core.walletSetupReuseRecoveryPhrase.confirm": "Confirm", + "core.walletSetupReuseRecoveryPhrase.error": "An error occurred", + "core.walletSetupReuseRecoveryPhrase.supportedRecoveryPhrase": "Only 24-word recovery phrases are supported.", + "core.walletSetupReuseRecoveryPhrase.createNewRecoveryPhrase": "Please create a new recovery phrase or select a different wallet", + "core.walletSetupReuseRecoveryPhrase.selectAnotherWallet": "Select another wallet", "core.nftDetail.directory": "Folder", "core.walletSetup.selectBlockchain": "Select a blockchain", "core.walletSetup.recoveryMethod": "Recovery method", diff --git a/packages/translation/src/lib/translations/core/es.json b/packages/translation/src/lib/translations/core/es.json index 7a0a66af3..bdd77ef62 100644 --- a/packages/translation/src/lib/translations/core/es.json +++ b/packages/translation/src/lib/translations/core/es.json @@ -684,6 +684,20 @@ "core.WalletSetupSelectBlockchain.bitcoin.description": "Te permite gestionar Bitcoin (BTC) y otros activos en la cadena de bloques de Bitcoin.", "core.WalletSetupSelectBlockchain.defaultBadge": "Predeterminado", "core.WalletSetupSelectBlockchain.newBadge": "Nuevo", + "core.walletSetupReuseRecoveryPhrase.title": "¿Reutilizar su frase de recuperación?", + "core.walletSetupReuseRecoveryPhrase.description": "¿Desea usar la misma frase de recuperación que la billetera a continuación, o desea crear una nueva?", + "core.walletSetupReuseRecoveryPhrase.useSameRecoveryPhrase": "Usar la misma frase de recuperación", + "core.walletSetupReuseRecoveryPhrase.createNewOne": "Crear una nueva", + "core.walletSetupReuseRecoveryPhrase.selectWallet": "Seleccionar una billetera", + "core.walletSetupReuseRecoveryPhrase.confirmPassword": "Confirme su contraseña", + "core.walletSetupReuseRecoveryPhrase.insertPassword": "Introduzca su contraseña", + "core.walletSetupReuseRecoveryPhrase.confirmPasswordDescription": "Confirma escribiendo tu contraseña a continuación para \"{{walletName}}\"", + "core.walletSetupReuseRecoveryPhrase.password": "Contraseña", + "core.walletSetupReuseRecoveryPhrase.confirm": "Confirmar", + "core.walletSetupReuseRecoveryPhrase.error": "Ha ocurrido un error", + "core.walletSetupReuseRecoveryPhrase.supportedRecoveryPhrase": "Solo se admiten frases de recuperación de 24 palabras.", + "core.walletSetupReuseRecoveryPhrase.createNewRecoveryPhrase": "Por favor, cree una nueva frase de recuperación o seleccione una billetera diferente", + "core.walletSetupReuseRecoveryPhrase.selectAnotherWallet": "Seleccionar otra billetera", "core.nftDetail.directory": "Carpeta", "core.walletSetup.selectBlockchain": "Selecciona una cadena de bloques", "core.walletSetup.recoveryMethod": "Método de recuperación", diff --git a/yarn.lock b/yarn.lock index 55ccacf94..ce6265d08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11500,9 +11500,9 @@ __metadata: languageName: node linkType: hard -"@input-output-hk/lace-ui-toolkit@npm:3.8.0": - version: 3.8.0 - resolution: "@input-output-hk/lace-ui-toolkit@npm:3.8.0::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40input-output-hk%2Flace-ui-toolkit%2F3.8.0%2F3cfebb953d437ed4b48da5dcce2f7e3f28495a24" +"@input-output-hk/lace-ui-toolkit@npm:3.9.2": + version: 3.9.2 + resolution: "@input-output-hk/lace-ui-toolkit@npm:3.9.2::__archiveUrl=https%3A%2F%2Fnpm.pkg.github.com%2Fdownload%2F%40input-output-hk%2Flace-ui-toolkit%2F3.9.2%2Fb1d85b371740660b8144db829222cd162065e8b7" dependencies: "@radix-ui/react-alert-dialog": ^1.0.4 "@radix-ui/react-avatar": ^1.0.2 @@ -11534,7 +11534,7 @@ __metadata: peerDependencies: react: ">=17.0.2" react-dom: ">=17.0.2" - checksum: 5c2c7696b218845a1ffeda051ea9fdd5da5de0736da628071d81e1e5394f236707f8d495b12992fcfc76d9e3da7e17df217c2fac048a0faaccc5c599ffd6f5aa + checksum: 97923d08d9b2ccd9023c5af1364c2907c1b8961c4afbaa16675c6d32a29260335d25bd2e5b6174b776c3ad461af1636cc9f6b3439c1bbf6adf19538b8c8a8f51 languageName: node linkType: hard @@ -12602,7 +12602,7 @@ __metadata: "@cardano-sdk/crypto": 0.4.4 "@cardano-sdk/wallet": 0.53.22 "@cardano-sdk/web-extension": 0.39.33 - "@input-output-hk/lace-ui-toolkit": 3.8.0 + "@input-output-hk/lace-ui-toolkit": 3.9.2 "@lace/cardano": 0.1.0 "@lace/common": 0.1.0 "@lace/translation": 0.1.0 @@ -42444,7 +42444,7 @@ __metadata: "@babel/runtime": ^7.26.10 "@commitlint/cli": ^17.0.3 "@commitlint/config-conventional": ^17.0.3 - "@input-output-hk/lace-ui-toolkit": 3.5.0 + "@input-output-hk/lace-ui-toolkit": 3.9.2 "@rollup/plugin-commonjs": 20.0.0 "@rollup/plugin-image": 2.1.1 "@rollup/plugin-json": ^6.0.0