Skip to content

Commit

Permalink
upcoming: [M3-9008] Improve Close Account UI (linode#11469)
Browse files Browse the repository at this point in the history
* upcoming: [M3-9008] Improve Close Account UI

* Add changesets

* Update e2e test to cover parent/child

* More cleanup

* Change warning to error and update variants/colors

* Enforce type-to-confirm for close account

* Small revert

* Revision

* Text updates to title, warning, comment blurb

* Expand comments section full width - allow this prop to be set

* Fix unit tests due to usePreference updates - remove loading variant

* Update packages/manager/cypress/e2e/core/account/account-cancellation.spec.ts

Co-authored-by: Connie Liu <[email protected]>

* Update packages/manager/cypress/e2e/core/account/account-cancellation.spec.ts

Co-authored-by: Connie Liu <[email protected]>

* Update packages/manager/cypress/e2e/core/account/account-cancellation.spec.ts

Co-authored-by: Connie Liu <[email protected]>

* Update state variable name @coliu-akamai

* Allow expand to be passed for deletionDialogs

* Expand all delete type-to-confirms @coliu-akamai

* more deletion dialogs

* Few more dialogs

---------

Co-authored-by: Jaalah Ramos <[email protected]>
Co-authored-by: Connie Liu <[email protected]>
  • Loading branch information
3 people authored and dmcintyr-akamai committed Jan 9, 2025
1 parent 795264e commit 26c5cf4
Show file tree
Hide file tree
Showing 35 changed files with 574 additions and 164 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Improve Close Account Dialog UI ([#11469](https://github.com/linode/manager/pull/11469))
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import {
cancellationDataLossWarning,
cancellationPaymentErrorMessage,
cancellationDialogTitle,
} from 'support/constants/account';
import {
CHILD_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT,
Expand Down Expand Up @@ -39,7 +40,7 @@ describe('Account cancellation', () => {
it('users can cancel account', () => {
const mockAccount = accountFactory.build();
const mockProfile = profileFactory.build({
username: 'mock-user',
email: 'mock-user@linode.com',
restricted: false,
});
const mockCancellationResponse: CancelAccount = {
Expand Down Expand Up @@ -74,9 +75,7 @@ describe('Account cancellation', () => {
});

ui.dialog
.findByTitle(
'Are you sure you want to close your cloud computing services account?'
)
.findByTitle(cancellationDialogTitle)
.should('be.visible')
.within(() => {
cy.findByText(cancellationDataLossWarning, { exact: false }).should(
Expand All @@ -89,14 +88,30 @@ describe('Account cancellation', () => {
.should('be.visible')
.should('be.disabled');

// Enter username, confirm that submit button becomes enabled, and click
// Verify checkboxes are present with correct labels
cy.get('[data-qa-checkbox="deleteAccountServices"]')
.should('be.visible')
.should('not.be.checked');

cy.get('[data-qa-checkbox="deleteAccountUsers"]')
.should('be.visible')
.should('not.be.checked');

// Check both boxes but verify submit remains disabled without email
cy.get('[data-qa-checkbox="deleteAccountServices"]').click();
cy.get('[data-qa-checkbox="deleteAccountUsers"]').click();

ui.button
.findByTitle('Close Account')
.should('be.visible')
.should('be.disabled');

// Enter email, confirm that submit button becomes enabled, and click
// the submit button.
cy.findByLabelText(
`Please enter your Username (${mockProfile.username}) to confirm.`
)
cy.findByLabelText(`Enter your email address (${mockProfile.email})`)
.should('be.visible')
.should('be.enabled')
.type(mockProfile.username);
.type(mockProfile.email);

ui.button
.findByTitle('Close Account')
Expand Down Expand Up @@ -151,7 +166,7 @@ describe('Account cancellation', () => {
it('restricted users cannot cancel account', () => {
const mockAccount = accountFactory.build();
const mockProfile = profileFactory.build({
username: 'mock-restricted-user',
email: 'mock-user@linode.com',
restricted: true,
});

Expand All @@ -176,17 +191,22 @@ describe('Account cancellation', () => {

// Fill out cancellation dialog and attempt submission.
ui.dialog
.findByTitle(
'Are you sure you want to close your cloud computing services account?'
)
.findByTitle(cancellationDialogTitle)
.should('be.visible')
.within(() => {
cy.findByLabelText(
`Please enter your Username (${mockProfile.username}) to confirm.`
)
// Check both boxes but verify submit remains disabled without email
cy.get('[data-qa-checkbox="deleteAccountServices"]').click();
cy.get('[data-qa-checkbox="deleteAccountUsers"]').click();

ui.button
.findByTitle('Close Account')
.should('be.visible')
.should('be.disabled');

cy.findByLabelText(`Enter your email address (${mockProfile.email})`)
.should('be.visible')
.should('be.enabled')
.type(mockProfile.username);
.type(mockProfile.email);

ui.button
.findByTitle('Close Account')
Expand All @@ -208,7 +228,7 @@ describe('Parent/Child account cancellation', () => {
it('disables the "Close Account" button for a child user', () => {
const mockAccount = accountFactory.build({});
const mockProfile = profileFactory.build({
username: 'mock-child-user',
email: 'mock-user@linode.com',
restricted: false,
user_type: 'child',
});
Expand Down Expand Up @@ -242,7 +262,7 @@ describe('Parent/Child account cancellation', () => {
it('disables "Close Account" button for proxy users', () => {
const mockAccount = accountFactory.build();
const mockProfile = profileFactory.build({
username: 'proxy-user',
email: 'mock-user@linode.com',
restricted: false,
user_type: 'proxy',
});
Expand Down Expand Up @@ -276,7 +296,7 @@ describe('Parent/Child account cancellation', () => {
it('disables "Close Account" button for parent users', () => {
const mockAccount = accountFactory.build();
const mockProfile = profileFactory.build({
username: 'parent-user',
email: 'mock-user@linode.com',
restricted: false,
user_type: 'parent',
});
Expand Down Expand Up @@ -310,7 +330,7 @@ describe('Parent/Child account cancellation', () => {
it('allows a default account with no active child accounts to close the account', () => {
const mockAccount = accountFactory.build();
const mockProfile = profileFactory.build({
username: 'default-user',
email: 'mock-user@linode.com',
restricted: false,
user_type: 'default',
});
Expand Down Expand Up @@ -346,9 +366,7 @@ describe('Parent/Child account cancellation', () => {
});

ui.dialog
.findByTitle(
'Are you sure you want to close your cloud computing services account?'
)
.findByTitle(cancellationDialogTitle)
.should('be.visible')
.within(() => {
cy.findByText(cancellationDataLossWarning, { exact: false }).should(
Expand All @@ -361,14 +379,21 @@ describe('Parent/Child account cancellation', () => {
.should('be.visible')
.should('be.disabled');

// Enter username, confirm that submit button becomes enabled, and click
// Check both boxes but verify submit remains disabled without email
cy.get('[data-qa-checkbox="deleteAccountServices"]').click();
cy.get('[data-qa-checkbox="deleteAccountUsers"]').click();

ui.button
.findByTitle('Close Account')
.should('be.visible')
.should('be.disabled');

// Enter email, confirm that submit button becomes enabled, and click
// the submit button.
cy.findByLabelText(
`Please enter your Username (${mockProfile.username}) to confirm.`
)
cy.findByLabelText(`Enter your email address (${mockProfile.email})`)
.should('be.visible')
.should('be.enabled')
.type(mockProfile.username);
.type(mockProfile.email);

ui.button
.findByTitle('Close Account')
Expand Down
12 changes: 9 additions & 3 deletions packages/manager/cypress/support/constants/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
* Data loss warning which is displayed in the account cancellation dialog.
*/
export const cancellationDataLossWarning =
'Please note this is an extremely destructive action. Closing your account \
means that all services Linodes, Volumes, DNS Records, etc will be lost and \
may not be able be restored.';
'This is an extremely destructive action. All services, Linodes, volumes, \
DNS records, and user accounts will be permanently lost.';

/**
* Title text displayed in the account cancellation confirmation dialog.
*/
export const cancellationDialogTitle =
'Are you sure you want to close your Akamai cloud \
computing services account?';

/**
* Error message that appears when a payment failure occurs upon cancellation attempt.
Expand Down
25 changes: 15 additions & 10 deletions packages/manager/src/components/ActionsPanel/ActionsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Box, Button } from '@linode/ui';
import { Box, Button, omittedProps } from '@linode/ui';
import { styled } from '@mui/material/styles';
import * as React from 'react';
import { useStyles } from 'tss-react/mui';

import type { BoxProps, ButtonProps } from '@linode/ui';

interface ActionButtonsProps extends ButtonProps {
export interface ActionButtonsProps extends ButtonProps {
'data-node-idx'?: number;
'data-qa-form-data-loading'?: boolean;
'data-testid'?: string;
Expand All @@ -17,6 +17,10 @@ export interface ActionPanelProps extends BoxProps {
* primary type actionable button custom aria descripton.
*/
primaryButtonProps?: ActionButtonsProps;
/**
* Determines the position of the primary button within the actions panel.
*/
reversePrimaryButtonPosition?: boolean;
/**
* secondary type actionable button custom aria descripton.
*/
Expand All @@ -31,6 +35,7 @@ export const ActionsPanel = (props: ActionPanelProps) => {
const {
className,
primaryButtonProps,
reversePrimaryButtonPosition = false,
secondaryButtonProps,
...rest
} = props;
Expand All @@ -44,6 +49,7 @@ export const ActionsPanel = (props: ActionPanelProps) => {
<StyledBox
className={cx(className, 'actionPanel')}
data-qa-buttons
reversePrimaryButtonPosition={reversePrimaryButtonPosition}
{...rest}
>
{secondaryButtonProps ? (
Expand All @@ -69,14 +75,13 @@ export const ActionsPanel = (props: ActionPanelProps) => {
);
};

const StyledBox = styled(Box)(({ theme: { spacing } }) => ({
'& > :first-of-type': {
marginLeft: 0,
marginRight: spacing(),
},
'& > :only-child': {
marginRight: 0,
},
const StyledBox = styled(Box, {
label: 'StyledActionsPanel',
shouldForwardProp: omittedProps(['reversePrimaryButtonPosition']),
})<ActionPanelProps>(({ theme: { spacing }, ...props }) => ({
display: 'flex',
flexDirection: props.reversePrimaryButtonPosition ? 'row-reverse' : 'row',
gap: spacing(),
justifyContent: 'flex-end',
marginTop: spacing(1),
paddingBottom: spacing(1),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export const DeletionDialog = React.memo((props: DeletionDialogProps) => {
onChange={(input) => {
setConfirmationText(input);
}}
expand
label={`${capitalize(entity)} Name:`}
placeholder={label}
value={confirmationText}
Expand Down
49 changes: 47 additions & 2 deletions packages/manager/src/components/TypeToConfirm/TypeToConfirm.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
import { TextField, Typography } from '@linode/ui';
import { Checkbox, FormControlLabel, TextField, Typography } from '@linode/ui';
import * as React from 'react';

import { FormGroup } from 'src/components/FormGroup';
import { Link } from 'src/components/Link';

import type { TextFieldProps } from '@linode/ui';
import type { Theme } from '@mui/material';
import type { SxProps } from '@mui/material';

export interface TypeToConfirmProps extends Omit<TextFieldProps, 'onChange'> {
confirmationText?: JSX.Element | string;
expand?: boolean;
handleDeleteAccountServices?: (
e: React.ChangeEvent<HTMLInputElement>
) => void;
hideInstructions?: boolean;
isCloseAccount?: boolean;
onChange: (value: string) => void;
textFieldStyle?: React.CSSProperties;
title?: string;
typographyStyle?: React.CSSProperties;
typographyStyleSx?: SxProps<Theme>;
visible?: boolean | undefined;
}

export const TypeToConfirm = (props: TypeToConfirmProps) => {
const {
confirmationText,
expand,
handleDeleteAccountServices,
hideInstructions,
isCloseAccount,
onChange,
textFieldStyle,
title,
typographyStyle,
typographyStyleSx,
visible,
...rest
} = props;
Expand All @@ -41,11 +54,43 @@ export const TypeToConfirm = (props: TypeToConfirmProps) => {
{showTypeToConfirmInput ? (
<>
<Typography variant="h2">{title}</Typography>
<Typography style={typographyStyle}>{confirmationText}</Typography>
<Typography style={typographyStyle} sx={typographyStyleSx}>
{confirmationText}
</Typography>
{isCloseAccount && (
<FormGroup
sx={(theme) => ({
marginTop: theme.tokens.spacing[20],
paddingLeft: theme.tokens.spacing[10],
})}
>
<FormControlLabel
control={
<Checkbox
name="services"
onChange={handleDeleteAccountServices}
/>
}
data-qa-checkbox="deleteAccountServices"
label="Delete all account services and entities (Linodes, volumes, DNS records, etc.)"
/>
<FormControlLabel
control={
<Checkbox
name="users"
onChange={handleDeleteAccountServices}
/>
}
data-qa-checkbox="deleteAccountUsers"
label="Delete all user accounts, including your own."
/>
</FormGroup>
)}
<TextField
onChange={(e) => onChange(e.target.value)}
style={textFieldStyle}
{...rest}
expand={expand}
/>
</>
) : null}
Expand Down
Loading

0 comments on commit 26c5cf4

Please sign in to comment.