Merge pull request 'Place account deletion in its own danger row' (#82) from adilallo/fix/CR-207-delete-account-danger into main

Reviewed-on: #82
This commit was merged in pull request #82.
This commit is contained in:
an.di committed 2026-09-28 21:18:05 +00:00
commit 2653ae4602
3 files changed
+166 -20

No files matched your search

@@ -6,6 +6,7 @@ import Rule from "../../../components/cards/Rule";
import TextInput from "../../../components/controls/TextInput"; import TextInput from "../../../components/controls/TextInput";
import List from "../../../components/layout/List"; import List from "../../../components/layout/List";
import type { ListItem, ListSize } from "../../../components/layout/List"; import type { ListItem, ListSize } from "../../../components/layout/List";
import ListEntry from "../../../components/layout/ListEntry";
import Icon from "../../../components/asset/icon"; import Icon from "../../../components/asset/icon";
import Dialog from "../../../components/modals/Dialog"; import Dialog from "../../../components/modals/Dialog";
import Alert from "../../../components/modals/Alert"; import Alert from "../../../components/modals/Alert";
@@ -237,17 +238,8 @@ export function ProfilePageView({
leadingIcon: "mail", leadingIcon: "mail",
showDescription: false, showDescription: false,
}, },
{
id: "delete-account",
title: t("deleteAccount"),
description: "",
onClick: onOpenDeleteAccount,
leadingIcon: "warning",
variant: "danger",
showDescription: false,
},
]; ];
}, [t, onSignOut, onOpenDeleteAccount, onOpenEmailChange, onStartNewCustomRule]); }, [t, onSignOut, onOpenEmailChange, onStartNewCustomRule]);
const ruleCardShellClass = const ruleCardShellClass =
"w-full !max-w-full cursor-default !gap-3 !rounded-[12px] shadow-[0_0_48px_rgba(0,0,0,0.1)] lg:!rounded-[24px] lg:shadow-[0_0_24px_rgba(0,0,0,0.1)]"; "w-full !max-w-full cursor-default !gap-3 !rounded-[12px] shadow-[0_0_48px_rgba(0,0,0,0.1)] lg:!rounded-[24px] lg:shadow-[0_0_24px_rgba(0,0,0,0.1)]";
@@ -406,6 +398,22 @@ export function ProfilePageView({
leadingIcon="edit" leadingIcon="edit"
/> />
</nav> </nav>
<section
aria-label={t("deleteAccount")}
className="w-full overflow-hidden rounded-[12px] border border-[var(--color-border-default-negative-primary)] bg-[var(--color-surface-default-negative-secondary)]"
>
<ListEntry
title={t("deleteAccount")}
description=""
showDescription={false}
onClick={onOpenDeleteAccount}
size={profileListSize}
leadingIcon="edit"
variant="danger"
topDivider={false}
bottomDivider={false}
/>
</section>
</section> </section>
</div> </div>
</div> </div>
@@ -502,7 +510,7 @@ export function ProfilePageView({
<Button <Button
type="button" type="button"
size="medium" size="medium"
buttonType="filled" buttonType="danger"
palette="default" palette="default"
onClick={onConfirmDeleteAccount} onClick={onConfirmDeleteAccount}
disabled={accountDeleteBusy} disabled={accountDeleteBusy}
@@ -51,15 +51,8 @@ const ListEntryRow = memo(function ListEntryRow({
? `${layout.title} !text-[var(--color-content-default-negative-primary)]` ? `${layout.title} !text-[var(--color-content-default-negative-primary)]`
: layout.title; : layout.title;
const leadingToneClass = const leadingToneClass = "text-[var(--color-content-default-primary)]";
variant === "danger" const chevronToneClass = "text-[var(--color-content-default-primary)]";
? "text-[var(--color-content-default-negative-primary)]"
: "text-[var(--color-content-default-primary)]";
const chevronToneClass =
variant === "danger"
? "text-[var(--color-content-default-negative-primary)]"
: "text-[var(--color-content-default-primary)]";
const leadingSlot = ( const leadingSlot = (
<div className={`${leadingBoxClass} ${leadingToneClass}`}> <div className={`${leadingBoxClass} ${leadingToneClass}`}>
+145
View File
@@ -0,0 +1,145 @@
import { afterEach, describe, expect, test, vi } from "vitest";
import userEvent from "@testing-library/user-event";
import { within } from "@testing-library/react";
import {
cleanup,
renderWithProviders as render,
screen,
} from "../utils/test-utils";
import {
ProfilePageView,
type ProfilePageViewProps,
} from "../../app/(app)/profile/_components/ProfilePage.view";
import messages from "../../messages/en/index";
const copy = messages.pages.profile;
afterEach(() => {
cleanup();
});
function viewProps(
overrides: Partial<ProfilePageViewProps> = {},
): ProfilePageViewProps {
return {
userEmail: "dani@example.com",
ruleCardSize: "M",
profileLgUp: true,
profileListSize: "l",
rules: [],
rulesError: false,
draft: null,
showDraftCard: false,
ruleDeleteOpen: false,
ruleDeleteBusy: false,
draftDeleteOpen: false,
draftDeleteBusy: false,
accountDeleteOpen: false,
accountDeleteBusy: false,
actionError: null,
profileSuccessMessage: null,
emailChangeOpen: false,
emailChangeValue: "",
onEmailChangeValueChange: vi.fn(),
emailChangeBusy: false,
emailChangeRequestSent: false,
emailChangeModalError: null,
onDismissEmailChangeModalError: vi.fn(),
onOpenEmailChange: vi.fn(),
onCloseEmailChange: vi.fn(),
onSubmitEmailChange: vi.fn(),
onSignOut: vi.fn(),
onDeleteRule: vi.fn(),
onCloseDeleteRule: vi.fn(),
onConfirmDeleteRule: vi.fn(),
onDuplicateRule: vi.fn(),
onContinueDraft: vi.fn(),
onDeleteDraft: vi.fn(),
onCloseDeleteDraft: vi.fn(),
onConfirmDeleteDraft: vi.fn(),
onOpenDeleteAccount: vi.fn(),
onCloseDeleteAccount: vi.fn(),
onConfirmDeleteAccount: vi.fn(),
onDismissProfileSuccess: vi.fn(),
onDismissActionError: vi.fn(),
onDismissRulesError: vi.fn(),
onStartNewCustomRule: vi.fn(),
...overrides,
};
}
describe("ProfilePageView", () => {
test("puts delete account in its own danger area", () => {
render(<ProfilePageView {...viewProps()} />);
const options = screen.getByRole("navigation", {
name: copy.yourOptionsHeading,
});
expect(
within(options).getByRole("button", { name: copy.optionCreateCustom }),
).toBeInTheDocument();
expect(
within(options).queryByRole("button", { name: copy.deleteAccount }),
).not.toBeInTheDocument();
expect(screen.queryByText(copy.deleteAccountIntro)).not.toBeInTheDocument();
const danger = screen.getByRole("region", { name: copy.deleteAccount });
expect(danger.className).toContain(
"border-[var(--color-border-default-negative-primary)]",
);
const deleteAccount = within(danger).getByRole("button", {
name: copy.deleteAccount,
});
const deleteTitle = within(deleteAccount).getByText(copy.deleteAccount);
expect(deleteTitle.className).toContain(
"!text-[var(--color-content-default-negative-primary)]",
);
expect(deleteAccount.firstElementChild?.className).not.toContain(
"negative-primary",
);
expect(deleteAccount.lastElementChild?.className).not.toContain(
"negative-primary",
);
});
test("asks for confirmation before deleting the account", async () => {
const user = userEvent.setup();
const onOpenDeleteAccount = vi.fn();
const onConfirmDeleteAccount = vi.fn();
const { rerender } = render(
<ProfilePageView {...viewProps({ onOpenDeleteAccount })} />,
);
await user.click(
screen.getByRole("button", { name: copy.deleteAccount }),
);
expect(onOpenDeleteAccount).toHaveBeenCalledTimes(1);
rerender(
<ProfilePageView
{...viewProps({
onOpenDeleteAccount,
onConfirmDeleteAccount,
accountDeleteOpen: true,
})}
/>,
);
const dialog = screen.getByRole("dialog", {
name: copy.deleteAccountModalTitle,
});
expect(dialog).toHaveTextContent(copy.deleteAccountModalBody);
const confirm = within(dialog).getByRole("button", {
name: copy.deleteAccountConfirm,
});
expect(confirm.className).toContain(
"border-[var(--color-border-default-negative-primary)]",
);
expect(confirm.className).not.toContain(
"bg-[var(--color-surface-inverse-primary)]",
);
await user.click(confirm);
expect(onConfirmDeleteAccount).toHaveBeenCalledTimes(1);
});
});