Skip to content

Commit

Permalink
Merge pull request #9175 from carrillo-erik/refactor/M3-6063
Browse files Browse the repository at this point in the history
refactor: [M3-6063] - Refactor components to use TypeToConfirmDialog component
  • Loading branch information
carrillo-erik authored Jul 5, 2023
2 parents 8755aad + c2ce6a6 commit 94f8fa1
Show file tree
Hide file tree
Showing 15 changed files with 242 additions and 334 deletions.
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-9175-changed-1685739474444.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Changed
---

Refactor components to use TypeToConfirmDialog ([#9175](https://github.com/linode/manager/pull/9175))
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const useStyles = makeStyles()((theme: Theme) => ({
marginBottom: 0,
},
},
dialogContent: {
display: 'flex',
flexDirection: 'column',
},
}));

export interface ConfirmationDialogProps extends DialogProps {
Expand Down Expand Up @@ -53,7 +57,7 @@ export const ConfirmationDialog = (props: ConfirmationDialogProps) => {
data-testid="drawer"
>
<DialogTitle title={title} onClose={onClose} />
<DialogContent data-qa-dialog-content className="dialog-content">
<DialogContent data-qa-dialog-content className={classes.dialogContent}>
{children}
{error && (
<DialogContentText className={`${classes.error} error-for-scroll`}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { TypeToConfirmDialog } from './TypeToConfirmDialog';

const meta: Meta<typeof TypeToConfirmDialog> = {
title: 'Components/TypeToConfirmDialog',
component: TypeToConfirmDialog,
argTypes: {
children: {
description: 'The items of the Dialog, passed-in as sub-components.',
},
error: { description: 'Error that will be shown in the dialog.' },
onClose: {
action: 'onClose',
description: 'Callback fired when the component requests to be closed.',
},
onClick: {
description: 'Callback fired when the action is confirmed.',
},
open: { description: 'Is the modal open?' },
title: { description: 'Title that appears in the heading of the dialog.' },
},
args: {
open: true,
title: 'Delete Linode?',
onClose: action('onClose'),
onClick: action('onDelete'),
loading: false,
label: 'Linode Label',
children: '',
error: undefined,
entity: {
type: 'Linode',
action: 'deletion',
name: 'test linode',
primaryBtnText: 'Delete',
},
},
};

export default meta;

type Story = StoryObj<typeof TypeToConfirmDialog>;

export const Default: Story = {
render: (args) => (
<TypeToConfirmDialog {...args}>{args.children}</TypeToConfirmDialog>
),
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@ describe('TypeToConfirmDialog Component', () => {
const { getByText } = renderWithTheme(
<TypeToConfirmDialog
title="Delete Linode test?"
label={'Linode Label'}
open={true}
entity={{ type: 'Linode', label: 'test' }}
entity={{
type: 'Linode',
name: 'test',
action: 'deletion',
primaryBtnText: 'Delete',
}}
loading={false}
{...props}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,26 @@ import {
import { usePreferences } from 'src/queries/preferences';

interface EntityInfo {
type: 'Linode' | 'Volume' | 'NodeBalancer' | 'Bucket';
label: string | undefined;
type:
| 'Linode'
| 'Volume'
| 'NodeBalancer'
| 'Bucket'
| 'Database'
| 'Kubernetes'
| 'AccountSetting';
subType?: 'Cluster' | 'ObjectStorage' | 'CloseAccount';
action?: 'deletion' | 'detachment' | 'restoration' | 'cancellation';
name?: string | undefined;
primaryBtnText: string;
}

interface TypeToConfirmDialogProps {
entity: EntityInfo;
children: React.ReactNode;
loading: boolean;
confirmationText?: string | JSX.Element;
errors?: APIError[] | undefined | null;
label: string;
onClick: () => void;
}

Expand All @@ -38,27 +48,38 @@ export const TypeToConfirmDialog = (props: CombinedProps) => {
onClick,
loading,
entity,
label,
children,
confirmationText,
errors,
typographyStyle,
textFieldStyle,
} = props;

const [confirmText, setConfirmText] = React.useState('');

const { data: preferences } = usePreferences();
const disabled =
preferences?.type_to_confirm !== false && confirmText !== entity.label;
preferences?.type_to_confirm !== false && confirmText !== entity.name;

React.useEffect(() => {
if (open) {
setConfirmText('');
}
}, [open]);

const typeInstructions =
entity.action === 'cancellation'
? `type your Username `
: `type the name of the ${entity.type} ${entity.subType || ''} `;

const actions = (
<ActionsPanel style={{ padding: 0 }}>
<Button buttonType="secondary" onClick={onClose} data-qa-cancel>
<Button
buttonType="secondary"
onClick={onClose}
data-qa-cancel
data-testid={'dialog-cancel'}
>
Cancel
</Button>
<Button
Expand All @@ -67,10 +88,9 @@ export const TypeToConfirmDialog = (props: CombinedProps) => {
loading={loading}
disabled={disabled}
data-qa-confirm
data-testid={'dialog-confirm'}
>
{entity.type === 'Volume' && title.startsWith('Detach')
? 'Detach'
: 'Delete'}
{entity.primaryBtnText}
</Button>
</ActionsPanel>
);
Expand All @@ -85,25 +105,28 @@ export const TypeToConfirmDialog = (props: CombinedProps) => {
>
{children}
<TypeToConfirm
label={`${entity.type} ${entity.type !== 'Bucket' ? 'Label' : 'Name'}`}
hideInstructions={entity.subType === 'CloseAccount'}
label={label}
confirmationText={
confirmationText ? (
confirmationText
entity.subType === 'CloseAccount' ? (
''
) : (
<span>
To confirm deletion, type the name of the {entity.type} (
<b>{entity.label}</b>) in the field below:
To confirm {entity.action}, {typeInstructions}(
<b>{entity.name}</b>) in the field below:
</span>
)
}
value={confirmText}
typographyStyle={typographyStyle}
textFieldStyle={textFieldStyle}
data-testid={'dialog-confirm-text-input'}
expand
onChange={(input) => {
setConfirmText(input);
}}
visible={preferences?.type_to_confirm}
placeholder={entity.subType === 'CloseAccount' ? 'Username' : ''}
/>
</ConfirmationDialog>
);
Expand Down
123 changes: 49 additions & 74 deletions packages/manager/src/features/Account/CloseAccountDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@ import { cancelAccount } from '@linode/api-v4/lib/account';
import { APIError } from '@linode/api-v4/lib/types';
import * as React from 'react';
import { useHistory } from 'react-router-dom';
import ActionsPanel from 'src/components/ActionsPanel';
import { Button } from 'src/components/Button/Button';
import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog';
import { TypeToConfirmDialog } from 'src/components/TypeToConfirmDialog/TypeToConfirmDialog';
import { makeStyles } from 'tss-react/mui';
import { Theme } from '@mui/material/styles';
import { Theme, styled } from '@mui/material/styles';
import { Notice } from 'src/components/Notice/Notice';
import { Typography } from 'src/components/Typography';
import { TypeToConfirm } from 'src/components/TypeToConfirm/TypeToConfirm';
import { TextField } from 'src/components/TextField';
import { useProfile } from 'src/queries/profile';

Expand All @@ -21,6 +18,7 @@ interface Props {
const useStyles = makeStyles()((theme: Theme) => ({
dontgo: {
marginTop: theme.spacing(2),
order: 1,
},
}));

Expand All @@ -32,7 +30,6 @@ const CloseAccountDialog = ({ closeDialog, open }: Props) => {
const [comments, setComments] = React.useState<string>('');
const [inputtedUsername, setUsername] = React.useState<string>('');
const [canSubmit, setCanSubmit] = React.useState<boolean>(false);

const { classes } = useStyles();
const history = useHistory();
const { data: profile } = useProfile();
Expand Down Expand Up @@ -98,86 +95,64 @@ const CloseAccountDialog = ({ closeDialog, open }: Props) => {
}

return (
<ConfirmationDialog
open={open}
<TypeToConfirmDialog
title="Are you sure you want to close your Linode account?"
label={`Please enter your Username (${profile.username}) to confirm.`}
entity={{
type: 'AccountSetting',
subType: 'CloseAccount',
primaryBtnText: 'Close Account',
name: profile.username,
}}
open={open}
onClose={closeDialog}
error={errors ? errors[0].reason : ''}
actions={
<Actions
onClose={closeDialog}
isCanceling={isClosingAccount}
onSubmit={handleCancelAccount}
disabled={!canSubmit}
/>
}
onClick={handleCancelAccount}
loading={isClosingAccount}
inputRef={inputRef}
disabled={!canSubmit}
textFieldStyle={{ maxWidth: '415px' }}
>
<Notice warning>
<Typography style={{ fontSize: '0.875rem' }}>
<strong>Warning:</strong> Please note this is an extremely destructive
action. Closing your account means that all services including
Linodes, Volumes, DNS Records, etc will be lost and may not be able to
be restored.
</Typography>
</Notice>
<TypeToConfirm
label={`Please enter your username (${profile.username}) to confirm.`}
onChange={(input) => setUsername(input)}
inputRef={inputRef}
aria-label="username field"
value={inputtedUsername}
visible
hideInstructions
placeholder="Username"
/>
{errors ? <Notice error text={errors ? errors[0].reason : ''} /> : null}
<StyledNoticeWrapper>
<Notice warning spacingBottom={12}>
<Typography sx={{ fontSize: '0.875rem' }}>
<strong>Warning:</strong> 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.
</Typography>
</Notice>
</StyledNoticeWrapper>
<Typography className={classes.dontgo}>
We&rsquo;d hate to see you go. Please let us know what we could be doing
better in the comments section below. After your account is closed,
you&rsquo;ll be directed to a quick survey so we can better gauge your
feedback.
</Typography>
<TextField
label="Comments"
multiline
onChange={(e) => setComments(e.target.value)}
optional
placeholder="Provide Feedback"
rows={1}
value={comments}
aria-label="Optional comments field"
/>
</ConfirmationDialog>
<StyledCommentSectionWrapper>
<TextField
label="Comments"
multiline
onChange={(e) => setComments(e.target.value)}
optional
placeholder="Provide Feedback"
rows={1}
value={comments}
aria-label="Optional comments field"
/>
</StyledCommentSectionWrapper>
</TypeToConfirmDialog>
);
};

interface ActionsProps {
onClose: () => void;
onSubmit: () => void;
isCanceling: boolean;
disabled: boolean;
}
// The order property helps inject the TypeToConfirm input field in the TypeToConfirmDialog when the components
// below are passed in as the children prop.
const StyledNoticeWrapper = styled('div')(() => ({
order: 0,
}));

const Actions = ({
disabled,
isCanceling,
onClose,
onSubmit,
}: ActionsProps) => {
return (
<ActionsPanel>
<Button buttonType="secondary" onClick={onClose}>
Cancel
</Button>
<Button
buttonType="primary"
onClick={onSubmit}
disabled={disabled}
loading={isCanceling}
>
Close Account
</Button>
</ActionsPanel>
);
};
const StyledCommentSectionWrapper = styled('div')(() => ({
order: 2,
}));

export default React.memo(CloseAccountDialog);
Loading

0 comments on commit 94f8fa1

Please sign in to comment.