Skip to content

Commit

Permalink
Fix review
Browse files Browse the repository at this point in the history
  • Loading branch information
OKendigelyan committed Sep 5, 2024
1 parent 69ade38 commit 353e252
Show file tree
Hide file tree
Showing 13 changed files with 210 additions and 154 deletions.
43 changes: 43 additions & 0 deletions apps/web/src/components/MnemonicWord/MnemonicWord.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { GridItem, type GridItemProps, Text } from "@chakra-ui/react";
import { MnemonicAutocomplete } from "@umami/components";
import { type ComponentProps } from "react";

import { useColor } from "../../styles/useColor";

type MnemonicWordProps = {
index: number;
word?: string;
autocompleteProps?: ComponentProps<typeof MnemonicAutocomplete>;
} & GridItemProps;

export const MnemonicWord = ({ index, word, autocompleteProps, ...props }: MnemonicWordProps) => {
const color = useColor();

return (
<GridItem {...props}>
<Text
position="absolute"
zIndex={1}
marginTop={{ lg: "11px", base: "8px" }}
marginLeft={{ lg: "16px", base: "10px" }}
color={color("300")}
textAlign="right"
size={{ lg: "lg", base: "xs" }}
>
{String(index + 1).padStart(2, "0")}.
</Text>
{autocompleteProps && <MnemonicAutocomplete {...autocompleteProps} />}
{word && (
<Text
alignSelf="center"
paddingRight="10px"
paddingLeft={{ base: "30px", lg: "48px" }}
fontSize={{ base: "xs", lg: "lg" }}
fontWeight="medium"
>
{word}
</Text>
)}
</GridItem>
);
};
1 change: 1 addition & 0 deletions apps/web/src/components/MnemonicWord/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { MnemonicWord } from "./MnemonicWord";
32 changes: 11 additions & 21 deletions apps/web/src/components/Onboarding/ImportWallet/SeedPhraseTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,18 @@ import {
Button,
Flex,
Grid,
GridItem,
Heading,
Icon,
Text,
} from "@chakra-ui/react";
import { MnemonicAutocomplete, useDynamicModalContext, useMultiForm } from "@umami/components";
import { useDynamicModalContext, useMultiForm } from "@umami/components";
import { useAsyncActionHandler } from "@umami/state";
import { validateMnemonic } from "bip39";
import { range } from "lodash";
import { FormProvider, useFieldArray } from "react-hook-form";

import { CloseIcon } from "../../../assets/icons";
import { useColor } from "../../../styles/useColor";
import { MnemonicWord } from "../../MnemonicWord";
import { RadioButtons } from "../../RadioButtons";
import { SetupPassword } from "../SetupPassword";

Expand Down Expand Up @@ -114,21 +113,11 @@ export const SeedPhraseTab = () => {

<Grid gridRowGap="16px" gridColumnGap="12px" gridTemplateColumns="repeat(3, 1fr)">
{fields.map((field, index) => (
<GridItem key={field.id}>
<Text
position="absolute"
zIndex={1}
marginTop={{ lg: "10px", base: "8px" }}
marginLeft={{ lg: "16px", base: "10px" }}
color={color("black")}
textAlign="right"
size={{ lg: "lg", base: "xs" }}
>
{String(index + 1).padStart(2, "0")}.
</Text>
<MnemonicAutocomplete
inputName={`mnemonic.${index}.val`}
inputProps={{
<MnemonicWord
key={field.id}
autocompleteProps={{
inputName: `mnemonic.${index}.val`,
inputProps: {
...register(`mnemonic.${index}.val`, { required: true }),
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onPaste: async e => {
Expand All @@ -137,9 +126,10 @@ export const SeedPhraseTab = () => {
},
variant: "mnemonic",
placeholder: `word #${index + 1}`,
}}
/>
</GridItem>
},
}}
index={index}
/>
))}
</Grid>
<Button gap="4px" onClick={clearAll} variant="ghost">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,41 @@ import {
type UmamiStore,
generate24WordMnemonic,
makeStore,
useGetDecryptedMnemonic,
useIsPasswordSet,
useRestoreFromMnemonic,
useRestoreFromSecretKey,
} from "@umami/state";
import { mnemonic1 } from "@umami/test-utils";

import { SetupPassword } from "./SetupPassword";
import { act, fireEvent, renderInModal, screen, userEvent, waitFor } from "../../../testUtils";
import {
act,
dynamicModalContextMock,
fireEvent,
renderInModal,
screen,
userEvent,
waitFor,
} from "../../../testUtils";
import { CURVES } from "../AdvancedAccountSettings";
import { ImportantNoticeModal } from "../VerificationFlow/ImportantNoticeModal";

jest.mock("@umami/state", () => ({
...jest.requireActual("@umami/state"),
useRestoreFromMnemonic: jest.fn(),
useRestoreFromSecretKey: jest.fn(),
generate24WordMnemonic: jest.fn(),
useGetDecryptedMnemonic: jest.fn(),
useIsPasswordSet: jest.fn(),
}));

const password = "password";

beforeEach(() => {
jest.mocked(useGetDecryptedMnemonic).mockReturnValue(() => Promise.resolve(mnemonic1));
});

describe("<SetupPassword />", () => {
describe.each(["mnemonic", "secret_key"] as const)("%s mode", mode => {
describe("validations", () => {
Expand Down Expand Up @@ -250,4 +267,26 @@ describe("<SetupPassword />", () => {
});
});
});

describe("verification mode", () => {
it("renders the verification screen", async () => {
jest.mocked(useIsPasswordSet).mockReturnValue(true);
const { openWith } = dynamicModalContextMock;
const user = userEvent.setup();

await renderInModal(<SetupPassword mode="verification" />);

const passwordInput = screen.getByLabelText("Password");

await act(() => user.type(passwordInput, password));

const submitButton = screen.getByRole("button", { name: "Confirm" });

await act(() => user.click(submitButton));

expect(openWith).toHaveBeenCalledWith(<ImportantNoticeModal mnemonic={mnemonic1} />, {
size: "xl",
});
});
});
});
87 changes: 52 additions & 35 deletions apps/web/src/components/Onboarding/SetupPassword/SetupPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import {
} from "@chakra-ui/react";
import { type Curves } from "@taquito/signer";
import { useDynamicModalContext, useMultiForm } from "@umami/components";
import { DEFAULT_ACCOUNT_LABEL } from "@umami/core";
import { DEFAULT_ACCOUNT_LABEL, type MnemonicAccount } from "@umami/core";
import {
accountsActions,
generate24WordMnemonic,
useAppDispatch,
useAsyncActionHandler,
useCurrentAccount,
useGetDecryptedMnemonic,
useGetNextAvailableAccountLabels,
useIsPasswordSet,
useRestoreFromMnemonic,
Expand All @@ -33,6 +35,7 @@ import { ModalBackButton } from "../../BackButton";
import { ModalCloseButton } from "../../CloseButton";
import { PasswordInput } from "../../PasswordInput";
import { AdvancedAccountSettings } from "../AdvancedAccountSettings";
import { ImportantNoticeModal } from "../VerificationFlow/ImportantNoticeModal";

type FormFields = {
password: string;
Expand All @@ -41,26 +44,51 @@ type FormFields = {
curve: Exclude<Curves, "bip25519">;
};

type Mode = "mnemonic" | "secret_key" | "new_mnemonic";
type Mode = "mnemonic" | "secret_key" | "new_mnemonic" | "verification";

type SetupPasswordProps = {
mode?: Mode;
handleProceedToVerification?: (password: string) => void;
mode: Mode;
};

export const SetupPassword = ({
mode,
handleProceedToVerification: handleProceedToVerification,
}: SetupPasswordProps) => {
const getModeConfig = (mode: Mode) => {
switch (mode) {
case "verification":
return {
icon: LockIcon,
title: "Confirm password",
buttonLabel: "Confirm",
subtitle: "Confirm the password to secure your wallet and verification process.",
};
case "new_mnemonic":
return {
icon: UserIcon,
title: "Create Password",
buttonLabel: "Create Account",
subtitle: "Set a password to unlock your wallet. Make sure to store your password safely.",
};
case "mnemonic":
case "secret_key":
return {
icon: LockIcon,
title: "Almost there",
buttonLabel: "Import Wallet",
};
}
};

export const SetupPassword = ({ mode }: SetupPasswordProps) => {
const color = useColor();
const { handleAsyncAction, isLoading } = useAsyncActionHandler();
const { allFormValues, onClose } = useDynamicModalContext();
const dispatch = useAppDispatch();

const { allFormValues, onClose, openWith } = useDynamicModalContext();
const restoreFromMnemonic = useRestoreFromMnemonic();
const restoreFromSecretKey = useRestoreFromSecretKey();
const checkPassword = useValidateMasterPassword();
const getNextAvailableAccountLabels = useGetNextAvailableAccountLabels();
const isPasswordSet = useIsPasswordSet();
const dispatch = useAppDispatch();
const getDecryptedMnemonic = useGetDecryptedMnemonic();
const currentAccount = useCurrentAccount();

const form = useMultiForm<FormFields>({
mode: "onBlur",
Expand All @@ -77,18 +105,6 @@ export const SetupPassword = ({

const isNewMnemonic = mode === "new_mnemonic";

const onHandleSubmit = (formFields: FormFields) => {
if (handleProceedToVerification) {
return handleProceedToVerification(formFields.password);
}

if (isNewMnemonic) {
dispatch(accountsActions.setPassword(formFields.password));
}

return onSubmit(formFields);
};

const onSubmit = ({ password, curve, derivationPath }: FormFields) =>
handleAsyncAction(async () => {
const label = getNextAvailableAccountLabels(DEFAULT_ACCOUNT_LABEL)[0];
Expand Down Expand Up @@ -118,18 +134,24 @@ export const SetupPassword = ({
curve,
isVerified: !isNewMnemonic,
});

if (isNewMnemonic) {
dispatch(accountsActions.setPassword(password));
}

break;
}
default:
break;
case "verification": {
const mnemonic = await getDecryptedMnemonic(currentAccount as MnemonicAccount, password);

return openWith(<ImportantNoticeModal mnemonic={mnemonic} />, { size: "xl" });
}
}

return onClose();
});

const icon = isNewMnemonic ? UserIcon : LockIcon;
const title = mode ? (isNewMnemonic ? "Create Password" : "Almost there") : "Confirm password";
const buttonLabel = mode ? (isNewMnemonic ? "Create Account" : "Import Wallet") : "Confirm";
const { icon, title, buttonLabel, subtitle } = getModeConfig(mode);

return (
<ModalContent>
Expand All @@ -139,20 +161,15 @@ export const SetupPassword = ({
<Center flexDirection="column" gap="16px">
<Icon as={icon} width="24px" height="24px" color={color("blue")} />
<Heading size="xl">{title}</Heading>
{isNewMnemonic && (
<Text width="full" color={color("700")} fontWeight="400" textAlign="center" size="md">
Set a password to unlock your wallet. Make sure to store your password safely.
</Text>
)}
{!mode && (
{subtitle && (
<Text width="full" color={color("700")} fontWeight="400" textAlign="center" size="md">
Confirm the password to secure your wallet and verification process.
{subtitle}
</Text>
)}
</Center>
</ModalHeader>
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onHandleSubmit)}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<ModalBody>
<Flex flexDirection="column" gap="24px">
<PasswordInput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ const items = [
];

type ImportantNoticeModalProps = {
seedPhrase: string;
mnemonic: string;
};

export const ImportantNoticeModal = ({ seedPhrase }: ImportantNoticeModalProps) => {
export const ImportantNoticeModal = ({ mnemonic }: ImportantNoticeModalProps) => {
const color = useColor();
const { openWith } = useDynamicModalContext();

Expand Down Expand Up @@ -85,7 +85,7 @@ export const ImportantNoticeModal = ({ seedPhrase }: ImportantNoticeModalProps)
<ModalFooter>
<Button
width="full"
onClick={() => openWith(<RecordSeedphraseModal seedPhrase={seedPhrase} />)}
onClick={() => openWith(<RecordSeedphraseModal seedPhrase={mnemonic} />, { size: "xl" })}
variant="primary"
>
Next
Expand Down
Loading

1 comment on commit 353e252

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Title Lines Statements Branches Functions
apps/desktop Coverage: 84%
83.95% (1789/2131) 79.09% (840/1062) 78.57% (451/574)
apps/web Coverage: 84%
83.95% (1789/2131) 79.09% (840/1062) 78.57% (451/574)
packages/components Coverage: 96%
96.89% (125/129) 98.07% (51/52) 84.21% (32/38)
packages/core Coverage: 82%
83.05% (196/236) 73.55% (89/121) 82.14% (46/56)
packages/crypto Coverage: 100%
100% (28/28) 100% (3/3) 100% (5/5)
packages/data-polling Coverage: 98%
96.55% (140/145) 95.45% (21/22) 92.85% (39/42)
packages/multisig Coverage: 98%
98.4% (123/125) 89.47% (17/19) 100% (33/33)
packages/social-auth Coverage: 100%
100% (21/21) 100% (11/11) 100% (3/3)
packages/state Coverage: 84%
83.64% (772/923) 80.78% (164/203) 78.22% (291/372)
packages/tezos Coverage: 86%
85.57% (89/104) 89.47% (17/19) 82.75% (24/29)
packages/tzkt Coverage: 86%
84.05% (58/69) 81.25% (13/16) 76.92% (30/39)

Please sign in to comment.