Skip to content

Commit

Permalink
Add additional account import feature
Browse files Browse the repository at this point in the history
  • Loading branch information
serjonya-trili committed Aug 30, 2024
1 parent d1a4a0a commit 48bef7d
Show file tree
Hide file tree
Showing 24 changed files with 290 additions and 179 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useToast } from "@chakra-ui/react";
import {
useAsyncActionHandler,
useIsPasswordSet,
useRestoreFromMnemonic,
useRestoreFromSecretKey,
useValidateMasterPassword,
Expand All @@ -20,15 +21,14 @@ export const MasterPassword = ({
const restoreFromMnemonic = useRestoreFromMnemonic();
const restoreFromSecretKey = useRestoreFromSecretKey();
const checkPassword = useValidateMasterPassword();
const passwordHasBeenSet = checkPassword !== null;
const passwordHasBeenSet = useIsPasswordSet();

const { isLoading, handleAsyncAction } = useAsyncActionHandler();
const toast = useToast();
const handleSubmit = (password: string) =>
handleAsyncAction(async () => {
if (passwordHasBeenSet) {
await checkPassword(password);
}
await checkPassword?.(password);

switch (account.type) {
case "secret_key":
await restoreFromSecretKey(account.secretKey, password, account.label);
Expand Down
116 changes: 56 additions & 60 deletions apps/web/src/components/AccountSelectorModal/AccountSelectorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@ import { ThreeDotsIcon } from "../../assets/icons";
import { useColor } from "../../styles/useColor";
import { AccountTile } from "../AccountTile";
import { ModalCloseButton } from "../CloseButton";
import { OnboardOptionsModal } from "../Onboarding/OnboardOptions";
import { useCheckVerified } from "../Onboarding/useCheckUnverified";

export const AccountSelectorModal = () => {
const accounts = useImplicitAccounts();
const color = useColor();
const getBalance = useGetAccountBalance();
// TODO: add ConnectOptions onboarding modal
const { onOpen: openOnboardingModal, modalElement } = { modalElement: null, onOpen: () => {} };
const { onClose } = useDynamicModalContext();
const isVerified = useCheckVerified();
const { openWith, onClose } = useDynamicModalContext();

const dispatch = useDispatch();

Expand All @@ -41,64 +40,61 @@ export const AccountSelectorModal = () => {
.value();

return (
<>
{modalElement}
<ModalContent>
<ModalHeader>
<ModalCloseButton />
</ModalHeader>
<ModalBody flexDirection="column" gap="18px">
<VStack overflowY="auto" width="100%" maxHeight="400px" divider={<Divider />}>
{Object.entries(groupedAccounts).map(([type, accounts]) => (
<Flex key={type} flexDirection="column" width="100%">
<Center
justifyContent="space-between"
marginBottom="18px"
paddingRight="20px"
paddingLeft="12px"
>
<Heading color={color("900")} size="sm">
{type.split("_").map(capitalize).join(" ")}
</Heading>
<IconButton
color={color("500")}
background="transparent"
aria-label="actions"
icon={<ThreeDotsIcon />}
size="xs"
variant="ghost"
/>
</Center>
{accounts.map(account => {
const address = account.address.pkh;
const balance = getBalance(address);
const onClick = () => {
dispatch(accountsActions.setCurrent(address));
onClose();
};
<ModalContent>
<ModalHeader>
<ModalCloseButton />
</ModalHeader>
<ModalBody flexDirection="column" gap="18px">
<VStack overflowY="auto" width="100%" maxHeight="400px" divider={<Divider />}>
{Object.entries(groupedAccounts).map(([type, accounts]) => (
<Flex key={type} flexDirection="column" width="100%">
<Center
justifyContent="space-between"
marginBottom="18px"
paddingRight="20px"
paddingLeft="12px"
>
<Heading color={color("900")} size="sm">
{type.split("_").map(capitalize).join(" ")}
</Heading>
<IconButton
color={color("500")}
background="transparent"
aria-label="actions"
icon={<ThreeDotsIcon />}
size="xs"
variant="ghost"
/>
</Center>
{accounts.map(account => {
const address = account.address.pkh;
const balance = getBalance(address);
const onClick = () => {
dispatch(accountsActions.setCurrent(address));
onClose();
};

return (
<AccountTile key={address} account={account} onClick={onClick}>
<Flex justifyContent="center" flexDirection="column" gap="2px">
{isVerified && <AccountSelectorPopover account={account} />}
<Text color={color("700")} size="sm">
{balance ? prettyTezAmount(balance) : "\u00A0"}
</Text>
</Flex>
</AccountTile>
);
})}
</Flex>
))}
</VStack>
return (
<AccountTile key={address} account={account} onClick={onClick}>
<Flex justifyContent="center" flexDirection="column" gap="2px">
{isVerified && <AccountSelectorPopover account={account} />}
<Text color={color("700")} size="sm">
{balance ? prettyTezAmount(balance) : "\u00A0"}
</Text>
</Flex>
</AccountTile>
);
})}
</Flex>
))}
</VStack>

{isVerified && (
<Button width="full" onClick={openOnboardingModal} variant="primary">
Add Account
</Button>
)}
</ModalBody>
</ModalContent>
</>
{isVerified && (
<Button width="full" onClick={() => openWith(<OnboardOptionsModal />)} variant="primary">
Add Account
</Button>
)}
</ModalBody>
</ModalContent>
);
};
1 change: 1 addition & 0 deletions apps/web/src/components/AccountTile/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./AccountTile";
export * from "./AccountTileWrapper";
2 changes: 1 addition & 1 deletion apps/web/src/components/AddressTile/AddressTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { type Address, prettyTezAmount } from "@umami/tezos";

import { AddressTileIcon } from "./AddressTileIcon";
import { useColor } from "../../styles/useColor";
import { AccountTileWrapper } from "../AccountTile/AccountTileWrapper";
import { AccountTileWrapper } from "../AccountTile";
import { CopyAddressButton } from "../CopyAddressButton";

/**
Expand Down
17 changes: 3 additions & 14 deletions apps/web/src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,13 @@ import {
SettingsIcon,
UserPlusIcon,
} from "../../assets/icons";
import { OnboardOptionsModal } from "../Onboarding/OnboardOptions";
import { useCheckVerified } from "../Onboarding/useCheckUnverified";

// TODO: Make this work
export const useOnboardingModal = () => ({
onOpen: () => {},
modalElement: <></>,
});

export const Menu = () => {
const { openWith: openModal } = useDynamicModalContext();
const { openWith: openDrawer } = useDynamicDrawerContext();
const { colorMode, toggleColorMode } = useColorMode();
const { onOpen: openOnboardingModal, modalElement } = useOnboardingModal();
const isVerified = useCheckVerified();

const colorModeSwitchLabel = colorMode === "light" ? "Light mode" : "Dark mode";
Expand All @@ -44,7 +38,7 @@ export const Menu = () => {
{
label: "Add Account",
icon: <UserPlusIcon />,
onClick: openOnboardingModal,
onClick: () => openModal(<OnboardOptionsModal />),
},
{
label: "Save Backup",
Expand Down Expand Up @@ -86,10 +80,5 @@ export const Menu = () => {
],
];

return (
<>
<GenericMenu menuItems={menuItems} />
{modalElement}
</>
);
return <GenericMenu menuItems={menuItems} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { mockImplicitAccount } from "@umami/core";
import { addTestAccount, makeStore } from "@umami/state";

import { ImportWallet } from "./ImportWallet";
import { renderInModal, screen, waitFor } from "../../../testUtils";

jest.mock("@chakra-ui/react", () => ({
...jest.requireActual("@chakra-ui/react"),
useBreakpointValue: jest.fn(map => map["lg"]),
}));

describe("<ImportWallet />", () => {
it.each(["Seed Phrase", "Secret Key", "Ledger"])("renders %s tab", async tabName => {
await renderInModal(<ImportWallet />);

await waitFor(() => expect(screen.getByText(tabName)).toBeVisible());
});

describe("when the user has not onboarded", () => {
it("renders the Backup tab", async () => {
await renderInModal(<ImportWallet />);

await waitFor(() => expect(screen.getByText("Backup")).toBeVisible());
});
});

describe("when the user has onboarded", () => {
it("does not render the Backup tab", async () => {
const store = makeStore();
addTestAccount(store, mockImplicitAccount(0));

await renderInModal(<ImportWallet />, store);

expect(screen.queryByText("Backup")).not.toBeInTheDocument();
});
});
});
21 changes: 17 additions & 4 deletions apps/web/src/components/Onboarding/ImportWallet/ImportWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
TabPanels,
Tabs,
} from "@chakra-ui/react";
import { useImplicitAccounts } from "@umami/state";

import { ImportBackupTab } from "./ImportBackupTab";
import { LedgerTab } from "./LedgerTab";
Expand All @@ -19,8 +20,12 @@ import { useColor } from "../../../styles/useColor";
import { ModalCloseButton } from "../../CloseButton";
import { TabSwitch } from "../../TabSwitch/TabSwitch";

const BEFORE_ONBOARDING_OPTIONS = ["Seed Phrase", "Secret Key", "Backup", "Ledger"];
const AFTER_ONBOARDING_OPTIONS = ["Seed Phrase", "Secret Key", "Ledger"];

export const ImportWallet = () => {
const color = useColor();
const hasOnboarded = useImplicitAccounts().length > 0;

return (
<ModalContent>
Expand All @@ -31,20 +36,28 @@ export const ImportWallet = () => {
<Heading size="xl">Import Wallet</Heading>
</Center>
</ModalHeader>

<ModalBody>
<Tabs isLazy variant="onboarding">
<TabSwitch options={["Seed Phrase", "Secret Key", "Backup", "Ledger"]} />
<TabSwitch
options={hasOnboarded ? AFTER_ONBOARDING_OPTIONS : BEFORE_ONBOARDING_OPTIONS}
/>

<TabPanels padding="30px 0 0 0">
<TabPanel>
<SeedPhraseTab />
</TabPanel>

<TabPanel>
<SecretKeyTab />
</TabPanel>
<TabPanel>
<ImportBackupTab />
</TabPanel>

{!hasOnboarded && (
<TabPanel>
<ImportBackupTab />
</TabPanel>
)}

<TabPanel>
<LedgerTab />
</TabPanel>
Expand Down
48 changes: 27 additions & 21 deletions apps/web/src/components/Onboarding/ImportWallet/LedgerTab.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, Flex, Heading, Link, ListItem, OrderedList } from "@chakra-ui/react";
import { Button, Flex, Heading, Link, ListItem, OrderedList, Text } from "@chakra-ui/react";
import { type Curves } from "@taquito/signer";
import { useDynamicModalContext, useMultiForm } from "@umami/components";
import {
Expand Down Expand Up @@ -76,32 +76,38 @@ export const LedgerTab = () => {
<Heading color={color("black")} size="lg">
3
</Heading>
Ensure your ledger has the{" "}
<Link
textDecoration="underline"
_hover={{ color: color("600") }}
href="https://support.ledger.com/article/360002731113-zd"
isExternal
>
latest firmware
</Link>{" "}
version

<Text>
Ensure your ledger has the{" "}
<Link
textDecoration="underline"
_hover={{ color: color("600") }}
href="https://support.ledger.com/article/360002731113-zd"
isExternal
>
latest firmware
</Link>{" "}
version
</Text>
</ListItem>

<ListItem>
<Heading color={color("black")} size="lg">
4
</Heading>
Install & open the{" "}
<Link
textDecoration="underline"
_hover={{ color: color("600") }}
href="https://support.ledger.com/article/360016057774-zd"
isExternal
>
Tezos Wallet
</Link>{" "}
app on your ledger

<Text>
Install & open the{" "}
<Link
textDecoration="underline"
_hover={{ color: color("600") }}
href="https://support.ledger.com/article/360016057774-zd"
isExternal
>
Tezos Wallet
</Link>{" "}
app on your ledger
</Text>
</ListItem>

<ListItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const SecretKeyTab = () => {
<FormLabel>Secret Key</FormLabel>
<Textarea
minHeight="120px"
fontSize="18px"
{...register("secretKey", {
required: "Secret Key is required",
onChange: event => setIsEncrypted(isEncryptedSecretKeyPrefix(event.target.value)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export const SeedPhraseTab = () => {
zIndex={1}
marginTop={{ lg: "10px", base: "8px" }}
marginLeft={{ lg: "16px", base: "10px" }}
color={color("white", "black")}
color={color("black")}
textAlign="right"
size={{ lg: "lg", base: "xs" }}
>
Expand Down
Loading

1 comment on commit 48bef7d

@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%
84.19% (767/911) 80.4% (160/199) 79.12% (288/364)
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.