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 remove account group #1871

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
Expand Up @@ -4,7 +4,13 @@ import {
mockMnemonicAccount,
mockSocialAccount,
} from "@umami/core";
import { type UmamiStore, accountsActions, addTestAccounts, makeStore } from "@umami/state";
import {
type UmamiStore,
accountsActions,
addTestAccount,
addTestAccounts,
makeStore,
} from "@umami/state";

import { AccountSelectorModal } from "./AccountSelectorModal";
import { DeriveMnemonicAccountModal } from "./DeriveMnemonicAccountModal";
Expand Down Expand Up @@ -58,6 +64,87 @@ describe("<AccountSelectorModal />", () => {
}
);

describe("when clicking 'Remove account group' button", () => {
it.each([
["mnemonic", mockMnemonicAccount(0)],
["ledger", mockLedgerAccount(1)],
["social", mockSocialAccount(2)],
])(
"opens confirmation modal when clicking remove button for %s accounts",
async (_, account) => {
const user = userEvent.setup();
await renderInModal(<AccountSelectorModal />, store);
const accountLabel = getAccountGroupLabel(account);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: accountLabel -> groupLabel maybe?
Same for other tests here


const removeButton = screen.getByLabelText(`Remove ${accountLabel} accounts`);
await act(() => user.click(removeButton));

expect(screen.getByText("Remove All Accounts")).toBeInTheDocument();

await waitFor(() =>
expect(
screen.getByText(`Are you sure you want to remove all of your ${accountLabel}?`)
).toBeVisible()
);
}
);

it("removes mnemonic accounts when confirmed", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this test is not grouped with ledger & social?

const user = userEvent.setup();
await renderInModal(<AccountSelectorModal />, store);
const account = mockMnemonicAccount(0);
const accountLabel = getAccountGroupLabel(account);
const removeButton = screen.getByLabelText(`Remove ${accountLabel} accounts`);
await act(() => user.click(removeButton));

const confirmButton = screen.getByText("Remove");
await act(() => user.click(confirmButton));

expect(store.getState().accounts.seedPhrases[account.seedFingerPrint]).toBe(undefined);
expect(store.getState().accounts.items.length).toBe(2);
});

it.each([
["ledger", mockLedgerAccount(1)],
["social", mockSocialAccount(2)],
])("removes %s accounts when confirmed", async (_, account) => {
const user = userEvent.setup();
await renderInModal(<AccountSelectorModal />, store);
const accountLabel = getAccountGroupLabel(account);
const accounts = store.getState().accounts.items;

const removeButton = screen.getByLabelText(`Remove ${accountLabel} accounts`);
await act(() => user.click(removeButton));

const confirmButton = screen.getByText("Remove");
await act(() => user.click(confirmButton));

expect(store.getState().accounts.items.length).toBe(accounts.length - 1);
});

it('shows "Remove & Off-board" message when removing last group of accounts', async () => {
store.dispatch(accountsActions.reset());
addTestAccount(store, mockSocialAccount(0));

const user = userEvent.setup();
const accountLabel = getAccountGroupLabel(mockSocialAccount(0));
await renderInModal(<AccountSelectorModal />, store);

const removeButton = screen.getByLabelText(`Remove ${accountLabel} accounts`);
await act(() => user.click(removeButton));

expect(screen.getByText("Remove & Off-board")).toBeInTheDocument();

await waitFor(() =>
expect(
screen.getByText(
"Removing all your accounts will off-board you from Umami. This will remove or reset all customized settings to their defaults. Personal data (including saved contacts, password and accounts) won't be affected."
)
).toBeVisible()
);
});
});

it("opens appropriate modal when clicking 'Add Account' button", async () => {
const user = userEvent.setup();
const { openWith } = dynamicModalContextMock;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,19 @@ import {
VStack,
} from "@chakra-ui/react";
import { useDynamicModalContext } from "@umami/components";
import { type ImplicitAccount, type MnemonicAccount, getAccountGroupLabel } from "@umami/core";
import { accountsActions, useGetAccountBalance, useImplicitAccounts } from "@umami/state";
import {
type Account,
type ImplicitAccount,
type MnemonicAccount,
getAccountGroupLabel,
} from "@umami/core";
import {
accountsActions,
useGetAccountBalance,
useImplicitAccounts,
useRemoveMnemonic,
useRemoveNonMnemonic,
} from "@umami/state";
import { prettyTezAmount } from "@umami/tezos";
import { groupBy } from "lodash";
import { useDispatch } from "react-redux";
Expand All @@ -25,19 +36,22 @@ import { useColor } from "../../styles/useColor";
import { AccountTile } from "../AccountTile";
import { ModalCloseButton } from "../CloseButton";
import { DeriveMnemonicAccountModal } from "./DeriveMnemonicAccountModal";
import { ConfirmationModal } from "../ConfirmationModal";
import { OnboardOptionsModal } from "../Onboarding/OnboardOptions";
import { useIsAccountVerified } from "../Onboarding/VerificationFlow";

export const AccountSelectorModal = () => {
const accounts = useImplicitAccounts();
const implicitAccounts = useImplicitAccounts();
const color = useColor();
const getBalance = useGetAccountBalance();
const isVerified = useIsAccountVerified();
const { openWith, onClose } = useDynamicModalContext();
const removeMnemonic = useRemoveMnemonic();
const removeNonMnemonic = useRemoveNonMnemonic();
const { openWith, goBack, onClose } = useDynamicModalContext();

const dispatch = useDispatch();

const groupedAccounts = groupBy(accounts, getAccountGroupLabel);
const groupedAccounts = groupBy(implicitAccounts, getAccountGroupLabel);

const handleDeriveAccount = (account?: ImplicitAccount) => {
if (!account) {
Expand All @@ -52,6 +66,33 @@ export const AccountSelectorModal = () => {
}
};

const buttonLabel = (isLast: boolean) => (isLast ? "Remove & Off-board" : "Remove");
const description = (isLast: boolean, type: string) =>
isLast
? "Removing all your accounts will off-board you from Umami. This will remove or reset all customized settings to their defaults. Personal data (including saved contacts, password and accounts) won't be affected."
: `Are you sure you want to remove all of your ${type}?`;

const onRemove = (type: string, accounts: Account[]) => {
const account = accounts[0];
const isLast = accounts.length === implicitAccounts.length;

return openWith(
<ConfirmationModal
buttonLabel={buttonLabel(isLast)}
description={description(isLast, type)}
onSubmit={() => {
if (account.type === "mnemonic") {
removeMnemonic(account.seedFingerPrint);
} else if (account.type !== "multisig") {
removeNonMnemonic(account.type);
}
goBack();
}}
title="Remove All Accounts"
/>
);
};

return (
<ModalContent>
<ModalHeader>
Expand Down Expand Up @@ -81,6 +122,7 @@ export const AccountSelectorModal = () => {
color={color("500")}
aria-label={`Remove ${type} accounts`}
icon={<TrashIcon />}
onClick={() => onRemove(type, accounts)}
size="sm"
variant="ghost"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
import {
Button,
Flex,
Heading,
ModalContent,
ModalFooter,
ModalHeader,
Text,
} from "@chakra-ui/react";
import { useDynamicModalContext } from "@umami/components";
import { type LedgerAccount, type SecretKeyAccount, type SocialAccount } from "@umami/core";
import { useImplicitAccounts, useRemoveAccount } from "@umami/state";

import { AlertIcon } from "../../assets/icons";
import { useColor } from "../../styles/useColor";
import { ModalCloseButton } from "../CloseButton";
import { ConfirmationModal } from "../ConfirmationModal";

type RemoveAccountModalProps = {
account: SocialAccount | LedgerAccount | SecretKeyAccount;
Expand All @@ -22,7 +11,6 @@ type RemoveAccountModalProps = {
export const RemoveAccountModal = ({ account }: RemoveAccountModalProps) => {
const { goBack, onClose } = useDynamicModalContext();
const removeAccount = useRemoveAccount();
const color = useColor();

const isLastImplicitAccount = useImplicitAccounts().length === 1;

Expand Down Expand Up @@ -50,24 +38,11 @@ export const RemoveAccountModal = ({ account }: RemoveAccountModalProps) => {
}

return (
<ModalContent>
<ModalHeader>
<Flex alignItems="center" justifyContent="center" flexDirection="column">
<AlertIcon width="24px" color="red" />
<Heading marginTop="18px" marginBottom="12px" size="xl">
Remove Account
</Heading>
<Text maxWidth="340px" color={color("700")} fontWeight="400" size="md">
{description}
</Text>
</Flex>
<ModalCloseButton />
</ModalHeader>
<ModalFooter>
<Button width="full" onClick={handleRemoveAccount} variant="alert">
{buttonLabel}
</Button>
</ModalFooter>
</ModalContent>
<ConfirmationModal
buttonLabel={buttonLabel}
description={description}
onSubmit={handleRemoveAccount}
title="Remove Account"
/>
);
};
64 changes: 64 additions & 0 deletions apps/web/src/components/ConfirmationModal/ConfirmationModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
Button,
Center,
Heading,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Text,
} from "@chakra-ui/react";
import { useDynamicModalContext } from "@umami/components";

import { useColor } from "../../styles/useColor";
import { ModalBackButton } from "../BackButton";
import { ModalCloseButton } from "../CloseButton";

export const ConfirmationModal = ({
title,
description,
buttonLabel,
onSubmit,
}: {
title: string;
buttonLabel: string;
description?: string;
onSubmit: () => void;
}) => {
const { onClose } = useDynamicModalContext();
const onClick = () => {
onSubmit();
onClose();
};

const color = useColor();

return (
<ModalContent>
<ModalHeader>
<Heading size="xl">{title}</Heading>
<ModalBackButton />
<ModalCloseButton />
</ModalHeader>
<ModalBody>
<Center>
<Text
width="full"
maxWidth="340px"
color={color("700")}
fontWeight="400"
textAlign="center"
size="md"
>
{description}
</Text>
</Center>
</ModalBody>
<ModalFooter>
<Button width="full" onClick={onClick} size="lg" variant="alert">
{buttonLabel}
</Button>
</ModalFooter>
</ModalContent>
);
};
1 change: 1 addition & 0 deletions apps/web/src/components/ConfirmationModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ConfirmationModal } from "./ConfirmationModal";
53 changes: 9 additions & 44 deletions apps/web/src/components/Menu/LogoutModal.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,13 @@
import {
Button,
Center,
Heading,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Text,
} from "@chakra-ui/react";
import { logout } from "@umami/state";

import { useColor } from "../../styles/useColor";
import { persistor } from "../../utils/persistor";
import { ModalCloseButton } from "../CloseButton";
import { ConfirmationModal } from "../ConfirmationModal";

export const LogoutModal = () => {
const color = useColor();

return (
<ModalContent>
<ModalHeader>
<Heading size="xl">Logout</Heading>
<ModalCloseButton />
</ModalHeader>
<ModalBody>
<Center>
<Text
width="full"
maxWidth="340px"
color={color("700")}
fontWeight="400"
textAlign="center"
size="md"
>
Before logging out, make sure your mnemonic phrase is securely saved. Losing this phrase
could result in permanent loss of access to your data.
</Text>
</Center>
</ModalBody>
<ModalFooter>
<Button width="full" onClick={() => logout(persistor)} size="lg" variant="alert">
Logout
</Button>
</ModalFooter>
</ModalContent>
);
};
export const LogoutModal = () => (
<ConfirmationModal
buttonLabel="Logout"
description="Before logging out, make sure your mnemonic phrase is securely saved. Losing this phrase could result in permanent loss of access to your data."
onSubmit={() => logout(persistor)}
title="Logout"
/>
);
Loading