Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add additional account import feature #1814

Merged
merged 1 commit into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading