diff --git a/src/app/i18n/common/i18n.ts b/src/app/i18n/common/i18n.ts index 65e626347b..00bb99968b 100644 --- a/src/app/i18n/common/i18n.ts +++ b/src/app/i18n/common/i18n.ts @@ -194,6 +194,12 @@ export const translations: { [key: string]: any } = { DAILY: "Daily", }, + INPUT_PASSPHRASE: { + VALIDATION: { + ADDRESS_ALREADY_EXISTS: "Address {{address}} already exists", + }, + }, + INPUT_ADDRESS: { VALIDATION: { NOT_VALID: "The address is not valid", diff --git a/src/domains/wallet/e2e/import-wallet-action.e2e.ts b/src/domains/wallet/e2e/import-wallet-action.e2e.ts index 1f6deb3eb1..5a313a4c19 100755 --- a/src/domains/wallet/e2e/import-wallet-action.e2e.ts +++ b/src/domains/wallet/e2e/import-wallet-action.e2e.ts @@ -15,7 +15,7 @@ test("should import a wallet by mnemonic", async (t) => { .expect(Selector("div").withText(translations().WALLETS.PAGE_IMPORT_WALLET.NETWORK_STEP.SUBTITLE).exists) .ok(); - // Select a network and advance to step two + // Select a network and advance to second step await t.click(Selector("#ImportWallet__network-item-1")); await t .expect(Selector("button").withText(translations().COMMON.CONTINUE).hasAttribute("disabled")) @@ -25,11 +25,17 @@ test("should import a wallet by mnemonic", async (t) => { .expect(Selector("h1").withExactText(translations().WALLETS.PAGE_IMPORT_WALLET.METHOD_STEP.TITLE).exists) .ok(); - // Input passphrase + // Fill a passphrase and advance to third step const passphraseInput = Selector("input[name=passphrase]"); await t.typeText(passphraseInput, "this is a top secret passphrase oleg"); await t.click(Selector("button").withExactText(translations().COMMON.GO_TO_WALLET)); + + // Fill a wallet name + const walletNameInput = Selector("input[name=name]"); + + await t.typeText(walletNameInput, "Test"); + await t.click(Selector("button").withExactText(translations().COMMON.SAVE_FINISH)); }); test("should import a wallet by address", async (t) => { @@ -42,7 +48,7 @@ test("should import a wallet by address", async (t) => { .expect(Selector("div").withText(translations().WALLETS.PAGE_IMPORT_WALLET.NETWORK_STEP.SUBTITLE).exists) .ok(); - // Select a network and advance to step two + // Select a network and advance to the step two await t.click(Selector("#ImportWallet__network-item-1")); await t .expect(Selector("button").withText(translations().COMMON.CONTINUE).hasAttribute("disabled")) @@ -55,11 +61,17 @@ test("should import a wallet by address", async (t) => { // Use the address only await t.click(Selector("input[name=isAddressOnly]").parent()); - // Input address + // Fill an address and advance to the third step const addressInput = Selector("input[name=address]"); await t.typeText(addressInput, "D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib"); await t.click(Selector("button").withExactText(translations().COMMON.GO_TO_WALLET)); + + // Fill a wallet name + const walletNameInput = Selector("input[name=name]"); + + await t.typeText(walletNameInput, "Test"); + await t.click(Selector("button").withExactText(translations().COMMON.SAVE_FINISH)); }); test("should show an error message for invalid address", async (t) => { diff --git a/src/domains/wallet/i18n.ts b/src/domains/wallet/i18n.ts index 425c9252f7..001a201ae7 100644 --- a/src/domains/wallet/i18n.ts +++ b/src/domains/wallet/i18n.ts @@ -112,7 +112,7 @@ export const translations: { [key: string]: any } = { PROCESS_COMPLETED_STEP: { TITLE: "Completed", - SUBTITLE: "Wallet creation is complete. Now you can use it.", + SUBTITLE: "Wallet import is complete. Now you can use it.", }, WALLET_NAME: "Wallet name (optional)", diff --git a/src/domains/wallet/pages/ImportWallet/ImportWallet.test.tsx b/src/domains/wallet/pages/ImportWallet/ImportWallet.test.tsx index 92872449ba..06cfbfd1fe 100755 --- a/src/domains/wallet/pages/ImportWallet/ImportWallet.test.tsx +++ b/src/domains/wallet/pages/ImportWallet/ImportWallet.test.tsx @@ -18,7 +18,7 @@ import { waitFor, } from "testing-library"; -import { FirstStep, ImportWallet, SecondStep } from "./ImportWallet"; +import { FirstStep, ImportWallet, SecondStep, ThirdStep } from "./ImportWallet"; let profile: Profile; const fixtureProfileId = getDefaultProfileId(); @@ -119,6 +119,38 @@ describe("ImportWallet", () => { }); }); + it("should render 3st step", async () => { + const { result: form } = renderHook(() => + useForm({ + defaultValues: { + network: { + id: () => "devnet", + coin: () => "ARK", + }, + }, + }), + ); + const { getByTestId, asFragment } = render( + + + , + ); + + expect(getByTestId("ImportWallet__third-step")).toBeTruthy(); + expect(asFragment()).toMatchSnapshot(); + + expect(getByTestId("ImportWallet__network-name")).toHaveTextContent("Ark Devnet"); + expect(getByTestId("ImportWallet__wallet-address")).toHaveTextContent(identityAddress); + + const walletNameInput = getByTestId("ImportWallet__name-input"); + + act(() => { + fireEvent.change(walletNameInput, { target: { value: "Test" } }); + }); + + expect(form.current.getValues()).toEqual({ name: "Test" }); + }); + it("should go to previous step", async () => { const history = createMemoryHistory(); history.push(route); @@ -216,7 +248,24 @@ describe("ImportWallet", () => { await fireEvent.input(passphraseInput, { target: { value: mnemonic } }); - const submitButton = getByTestId("ImportWallet__submit-button"); + const goToWalletButton = getByTestId("ImportWallet__gotowallet-button"); + expect(goToWalletButton).toBeTruthy(); + await waitFor(() => { + expect(goToWalletButton).not.toHaveAttribute("disabled"); + }); + + await fireEvent.click(goToWalletButton); + + await waitFor(() => { + expect(getByTestId("ImportWallet__third-step")).toBeTruthy(); + }); + + const walletNameInput = getByTestId("ImportWallet__name-input"); + expect(walletNameInput).toBeTruthy(); + + await fireEvent.input(walletNameInput, { target: { value: "Test" } }); + + const submitButton = getByTestId("ImportWallet__save-button"); expect(submitButton).toBeTruthy(); await waitFor(() => { expect(submitButton).not.toHaveAttribute("disabled"); @@ -286,7 +335,19 @@ describe("ImportWallet", () => { await fireEvent.input(addressInput, { target: { value: randomAddress } }); - const submitButton = getByTestId("ImportWallet__submit-button"); + const goToWalletButton = getByTestId("ImportWallet__gotowallet-button"); + expect(goToWalletButton).toBeTruthy(); + await waitFor(() => { + expect(goToWalletButton).not.toHaveAttribute("disabled"); + }); + + await fireEvent.click(goToWalletButton); + + await waitFor(() => { + expect(getByTestId("ImportWallet__third-step")).toBeTruthy(); + }); + + const submitButton = getByTestId("ImportWallet__save-button"); expect(submitButton).toBeTruthy(); await waitFor(() => { expect(submitButton).not.toHaveAttribute("disabled"); @@ -358,10 +419,10 @@ describe("ImportWallet", () => { expect(getByText(commonTranslations.INPUT_ADDRESS.VALIDATION.NOT_VALID)).toBeVisible(); }); - const submitButton = getByTestId("ImportWallet__submit-button"); - expect(submitButton).toBeTruthy(); + const goToWalletButton = getByTestId("ImportWallet__gotowallet-button"); + expect(goToWalletButton).toBeTruthy(); await waitFor(() => { - expect(submitButton).toBeDisabled(); + expect(goToWalletButton).toBeDisabled(); }); }); }); @@ -410,6 +471,15 @@ describe("ImportWallet", () => { expect(getByTestId("ImportWallet__second-step")).toBeTruthy(); }); + const passphraseInput = getByTestId("ImportWallet__passphrase-input"); + expect(passphraseInput).toBeTruthy(); + + await fireEvent.input(passphraseInput, { target: { value: mnemonic } }); + + await waitFor(() => { + expect(getByText(`Address ${identityAddress} already exists`)).toBeVisible(); + }); + const addressToggle = getByTestId("ImportWallet__address-toggle"); expect(addressToggle).toBeTruthy(); @@ -424,10 +494,10 @@ describe("ImportWallet", () => { expect(getByText(`Address ${identityAddress} already exists`)).toBeVisible(); }); - const submitButton = getByTestId("ImportWallet__submit-button"); - expect(submitButton).toBeTruthy(); + const goToWalletButton = getByTestId("ImportWallet__gotowallet-button"); + expect(goToWalletButton).toBeTruthy(); await waitFor(() => { - expect(submitButton).toBeDisabled(); + expect(goToWalletButton).toBeDisabled(); }); }); }); diff --git a/src/domains/wallet/pages/ImportWallet/ImportWallet.tsx b/src/domains/wallet/pages/ImportWallet/ImportWallet.tsx index 19b64bcef9..ea68921c51 100755 --- a/src/domains/wallet/pages/ImportWallet/ImportWallet.tsx +++ b/src/domains/wallet/pages/ImportWallet/ImportWallet.tsx @@ -1,15 +1,20 @@ -import { NetworkData, Profile, Wallet } from "@arkecosystem/platform-sdk-profiles"; +import { Coins } from "@arkecosystem/platform-sdk"; +import { NetworkData, Profile, Wallet, WalletSetting } from "@arkecosystem/platform-sdk-profiles"; +import { Avatar } from "app/components/Avatar"; import { Button } from "app/components/Button"; +import { Divider } from "app/components/Divider"; import { Form, FormField, FormHelperText, FormLabel } from "app/components/Form"; import { Header } from "app/components/Header"; -import { InputAddress, InputPassword } from "app/components/Input"; +import { Input, InputAddress, InputPassword } from "app/components/Input"; import { Page, Section } from "app/components/Layout"; import { StepIndicator } from "app/components/StepIndicator"; import { TabPanel, Tabs } from "app/components/Tabs"; import { Toggle } from "app/components/Toggle"; import { useEnvironmentContext } from "app/contexts"; import { useActiveProfile } from "app/hooks/env"; +import { NetworkIcon } from "domains/network/components/NetworkIcon"; import { SelectNetwork } from "domains/network/components/SelectNetwork"; +import { getNetworkExtendedData } from "domains/network/helpers"; import React, { useEffect, useMemo, useState } from "react"; import { useForm, useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; @@ -51,6 +56,7 @@ export const FirstStep = () => { }; export const SecondStep = ({ profile }: { profile: Profile }) => { + const { env } = useEnvironmentContext(); const { getValues, register, unregister } = useFormContext(); const [isAddressOnly, setIsAddressOnly] = useState(false); @@ -68,6 +74,17 @@ export const SecondStep = ({ profile }: { profile: Profile }) => { required: t("COMMON.VALIDATION.FIELD_REQUIRED", { field: t("COMMON.YOUR_PASSPHRASE"), }).toString(), + validate: async (passphrase) => { + const instance: Coins.Coin = await env.coin(network.coin(), network.id()); + const address = await instance.identity().address().fromMnemonic(passphrase); + + return ( + !profile.wallets().findByAddress(address) || + t("COMMON.INPUT_PASSPHRASE.VALIDATION.ADDRESS_ALREADY_EXISTS", { + address, + }).toString() + ); + }, })} data-testid="ImportWallet__passphrase-input" /> @@ -88,7 +105,7 @@ export const SecondStep = ({ profile }: { profile: Profile }) => { field: t("COMMON.ADDRESS"), }).toString(), validate: { - duplicateAddress: (address: string) => + duplicateAddress: (address) => !profile.wallets().findByAddress(address) || t("COMMON.INPUT_ADDRESS.VALIDATION.ADDRESS_ALREADY_EXISTS", { address }).toString(), }, @@ -136,8 +153,58 @@ export const SecondStep = ({ profile }: { profile: Profile }) => { ); }; +export const ThirdStep = ({ address }: { address: string }) => { + const { getValues, register } = useFormContext(); + const network: NetworkData = getValues("network"); + const networkConfig = getNetworkExtendedData({ coin: network.coin(), network: network.id() }); + const { t } = useTranslation(); + + return ( +
+
+
+
+ + + + + + + + + +
+ ); +}; + export const ImportWallet = () => { const [activeTab, setActiveTab] = useState(1); + const [walletData, setWalletData] = useState(null); const history = useHistory(); const { persist } = useEnvironmentContext(); @@ -168,22 +235,33 @@ export const ImportWallet = () => { network, passphrase, address, + name, }: { network: NetworkData; passphrase: string; address: string; + name: string; }) => { let wallet: Wallet | undefined; - if (passphrase) { - wallet = await activeProfile.wallets().importByMnemonic(passphrase, network.coin(), network.id()); - } else { - wallet = await activeProfile.wallets().importByAddress(address, network.coin(), network.id()); - } + if (!walletData) { + if (passphrase) { + wallet = await activeProfile.wallets().importByMnemonic(passphrase, network.coin(), network.id()); + } else { + wallet = await activeProfile.wallets().importByAddress(address, network.coin(), network.id()); + } - await persist(); + setWalletData(wallet); + await persist(); + setActiveTab(activeTab + 1); + } else { + if (name) { + activeProfile.wallets().findById(walletData?.id()).settings().set(WalletSetting.Alias, name); + await persist(); + } - history.push(`/profiles/${activeProfile.id()}/wallets/${wallet?.id()}`); + history.push(`/profiles/${activeProfile.id()}/wallets/${walletData?.id()}`); + } }; return ( @@ -196,7 +274,7 @@ export const ImportWallet = () => { data-testid="ImportWallet__form" > - +
@@ -205,8 +283,22 @@ export const ImportWallet = () => { + + +
+ {activeTab < 3 && ( + + )} + {activeTab === 1 && ( - - + + )} + + {activeTab === 3 && ( + )}
diff --git a/src/domains/wallet/pages/ImportWallet/__snapshots__/ImportWallet.test.tsx.snap b/src/domains/wallet/pages/ImportWallet/__snapshots__/ImportWallet.test.tsx.snap index e018328bc5..0fdc628a44 100644 --- a/src/domains/wallet/pages/ImportWallet/__snapshots__/ImportWallet.test.tsx.snap +++ b/src/domains/wallet/pages/ImportWallet/__snapshots__/ImportWallet.test.tsx.snap @@ -87,7 +87,7 @@ exports[`ImportWallet should go to previous step 1`] = ` class="flex items-center h-full px-6 cursor-pointer text-theme-primary-300" >
@@ -108,7 +108,7 @@ exports[`ImportWallet should go to previous step 1`] = ` href="/profiles/b999d134-7a24-481e-a95d-bc47c543bfc9/transactions/transfer" >
@@ -131,7 +131,7 @@ exports[`ImportWallet should go to previous step 1`] = ` type="button" >
@@ -180,7 +180,7 @@ exports[`ImportWallet should go to previous step 1`] = ` class="text-theme-neutral-600" >
@@ -208,7 +208,7 @@ exports[`ImportWallet should go to previous step 1`] = ` class="sc-fzplWN dtBkxc flex border-2 rounded-full justify-center w-5 h-5 items-center align-middle bg-theme-background border-transparent transform bg-theme-primary-contrast border-theme-primary-contrast text-theme-primary-500" >
@@ -231,7 +231,7 @@ exports[`ImportWallet should go to previous step 1`] = ` data-testid="breadcrumbs__wrapper" >
@@ -275,6 +275,9 @@ exports[`ImportWallet should go to previous step 1`] = `
  • +
  • +