diff --git a/packages/manager/.changeset/pr-9175-changed-1685739474444.md b/packages/manager/.changeset/pr-9175-changed-1685739474444.md
new file mode 100644
index 00000000000..f49d6d361b9
--- /dev/null
+++ b/packages/manager/.changeset/pr-9175-changed-1685739474444.md
@@ -0,0 +1,5 @@
+---
+"@linode/manager": Changed
+---
+
+Refactor components to use TypeToConfirmDialog ([#9175](https://github.com/linode/manager/pull/9175))
diff --git a/packages/manager/src/components/ConfirmationDialog/ConfirmationDialog.tsx b/packages/manager/src/components/ConfirmationDialog/ConfirmationDialog.tsx
index 880d95a525f..2aaea03adce 100644
--- a/packages/manager/src/components/ConfirmationDialog/ConfirmationDialog.tsx
+++ b/packages/manager/src/components/ConfirmationDialog/ConfirmationDialog.tsx
@@ -23,6 +23,10 @@ const useStyles = makeStyles()((theme: Theme) => ({
marginBottom: 0,
},
},
+ dialogContent: {
+ display: 'flex',
+ flexDirection: 'column',
+ },
}));
export interface ConfirmationDialogProps extends DialogProps {
@@ -53,7 +57,7 @@ export const ConfirmationDialog = (props: ConfirmationDialogProps) => {
data-testid="drawer"
>
-
+
{children}
{error && (
diff --git a/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.stories.tsx b/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.stories.tsx
new file mode 100644
index 00000000000..f8764da9ba9
--- /dev/null
+++ b/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.stories.tsx
@@ -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 = {
+ 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;
+
+export const Default: Story = {
+ render: (args) => (
+ {args.children}
+ ),
+};
diff --git a/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.test.tsx b/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.test.tsx
index 22586a7f23e..c221d57f379 100644
--- a/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.test.tsx
+++ b/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.test.tsx
@@ -14,8 +14,14 @@ describe('TypeToConfirmDialog Component', () => {
const { getByText } = renderWithTheme(
diff --git a/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.tsx b/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.tsx
index 06a1f3225c2..a62397ace9b 100644
--- a/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.tsx
+++ b/packages/manager/src/components/TypeToConfirmDialog/TypeToConfirmDialog.tsx
@@ -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;
}
@@ -38,17 +48,18 @@ 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) {
@@ -56,9 +67,19 @@ export const TypeToConfirmDialog = (props: CombinedProps) => {
}
}, [open]);
+ const typeInstructions =
+ entity.action === 'cancellation'
+ ? `type your Username `
+ : `type the name of the ${entity.type} ${entity.subType || ''} `;
+
const actions = (
-
);
@@ -85,25 +105,28 @@ export const TypeToConfirmDialog = (props: CombinedProps) => {
>
{children}
- To confirm deletion, type the name of the {entity.type} (
- {entity.label}) in the field below:
+ To confirm {entity.action}, {typeInstructions}(
+ {entity.name}) in the field below:
)
}
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' : ''}
/>
);
diff --git a/packages/manager/src/features/Account/CloseAccountDialog.tsx b/packages/manager/src/features/Account/CloseAccountDialog.tsx
index a88a0210ba4..04aaa6a24fe 100644
--- a/packages/manager/src/features/Account/CloseAccountDialog.tsx
+++ b/packages/manager/src/features/Account/CloseAccountDialog.tsx
@@ -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';
@@ -21,6 +18,7 @@ interface Props {
const useStyles = makeStyles()((theme: Theme) => ({
dontgo: {
marginTop: theme.spacing(2),
+ order: 1,
},
}));
@@ -32,7 +30,6 @@ const CloseAccountDialog = ({ closeDialog, open }: Props) => {
const [comments, setComments] = React.useState('');
const [inputtedUsername, setUsername] = React.useState('');
const [canSubmit, setCanSubmit] = React.useState(false);
-
const { classes } = useStyles();
const history = useHistory();
const { data: profile } = useProfile();
@@ -98,86 +95,64 @@ const CloseAccountDialog = ({ closeDialog, open }: Props) => {
}
return (
-
- }
+ onClick={handleCancelAccount}
+ loading={isClosingAccount}
+ inputRef={inputRef}
+ disabled={!canSubmit}
+ textFieldStyle={{ maxWidth: '415px' }}
>
-
-
- Warning: 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.
-
-
- setUsername(input)}
- inputRef={inputRef}
- aria-label="username field"
- value={inputtedUsername}
- visible
- hideInstructions
- placeholder="Username"
- />
+ {errors ? : null}
+
+
+
+ Warning: 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.
+
+
+
We’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’ll be directed to a quick survey so we can better gauge your
feedback.
- setComments(e.target.value)}
- optional
- placeholder="Provide Feedback"
- rows={1}
- value={comments}
- aria-label="Optional comments field"
- />
-
+
+ setComments(e.target.value)}
+ optional
+ placeholder="Provide Feedback"
+ rows={1}
+ value={comments}
+ aria-label="Optional comments field"
+ />
+
+
);
};
-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 (
-
-
- Cancel
-
-
- Close Account
-
-
- );
-};
+const StyledCommentSectionWrapper = styled('div')(() => ({
+ order: 2,
+}));
export default React.memo(CloseAccountDialog);
diff --git a/packages/manager/src/features/Account/EnableObjectStorage.tsx b/packages/manager/src/features/Account/EnableObjectStorage.tsx
index f54136a074a..c71cb335369 100644
--- a/packages/manager/src/features/Account/EnableObjectStorage.tsx
+++ b/packages/manager/src/features/Account/EnableObjectStorage.tsx
@@ -3,21 +3,17 @@ import { cancelObjectStorage } from '@linode/api-v4/lib/object-storage';
import { APIError } from '@linode/api-v4/lib/types';
import * as React from 'react';
import { Link } from 'react-router-dom';
+import { TypeToConfirmDialog } from 'src/components/TypeToConfirmDialog/TypeToConfirmDialog';
import { Accordion } from 'src/components/Accordion';
-import ActionsPanel from 'src/components/ActionsPanel';
import { Button } from 'src/components/Button/Button';
-import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog';
import { Notice } from 'src/components/Notice/Notice';
-import { TypeToConfirm } from 'src/components/TypeToConfirm/TypeToConfirm';
import { Typography } from 'src/components/Typography';
import ExternalLink from 'src/components/ExternalLink';
import Grid from '@mui/material/Unstable_Grid2';
import { updateAccountSettingsData } from 'src/queries/accountSettings';
-import { usePreferences } from 'src/queries/preferences';
import { useProfile } from 'src/queries/profile';
import { queryKey } from 'src/queries/objectStorage';
import { useQueryClient } from 'react-query';
-
interface Props {
object_storage: AccountSettings['object_storage'];
}
@@ -73,13 +69,9 @@ export const EnableObjectStorage = (props: Props) => {
const [isOpen, setOpen] = React.useState(false);
const [error, setError] = React.useState();
const [isLoading, setLoading] = React.useState(false);
- const [confirmText, setConfirmText] = React.useState('');
- const { data: preferences } = usePreferences();
const { data: profile } = useProfile();
const queryClient = useQueryClient();
const username = profile?.username;
- const disabledConfirm =
- preferences?.type_to_confirm !== false && confirmText !== username;
const handleClose = () => {
setOpen(false);
@@ -104,28 +96,6 @@ export const EnableObjectStorage = (props: Props) => {
.catch(handleError);
};
- const actions = (
-
-
- Cancel
-
-
-
- Confirm Cancellation
-
-
- );
-
return (
<>
@@ -134,35 +104,30 @@ export const EnableObjectStorage = (props: Props) => {
openConfirmationModal={() => setOpen(true)}
/>
- handleClose()}
- title="Cancel Object Storage"
- actions={actions}
+ loading={isLoading}
+ onClose={handleClose}
+ onClick={handleSubmit}
>
+ {error ? : null}
-
+
Warning: Canceling Object Storage will permanently
delete all buckets and their objects. Object Storage Access Keys
will be revoked.
- setConfirmText(input)}
- expand
- value={confirmText}
- confirmationText={
-
- To confirm cancellation, type your username ({username}) in
- the field below:
-
- }
- visible={preferences?.type_to_confirm}
- />
-
+
>
);
};
diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseBackups/RestoreFromBackupDialog.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseBackups/RestoreFromBackupDialog.tsx
index 4575fdd1644..455be5a9532 100644
--- a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseBackups/RestoreFromBackupDialog.tsx
+++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseBackups/RestoreFromBackupDialog.tsx
@@ -2,15 +2,11 @@ import { Database, DatabaseBackup } from '@linode/api-v4/lib/databases';
import { useSnackbar } from 'notistack';
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 { DialogProps } from 'src/components/Dialog/Dialog';
import { Notice } from 'src/components/Notice/Notice';
-import { TypeToConfirm } from 'src/components/TypeToConfirm/TypeToConfirm';
import { Typography } from 'src/components/Typography';
import { useRestoreFromBackupMutation } from 'src/queries/databases';
-import { usePreferences } from 'src/queries/preferences';
import { useProfile } from 'src/queries/profile';
import { getAPIErrorOrDefault } from 'src/utilities/errorUtils';
import formatDate from 'src/utilities/formatDate';
@@ -23,14 +19,9 @@ interface Props extends Omit {
}
export const RestoreFromBackupDialog: React.FC = (props) => {
- const { database, backup, onClose, open, ...rest } = props;
-
+ const { database, backup, onClose, open } = props;
const history = useHistory();
const { enqueueSnackbar } = useSnackbar();
-
- const [confirmationText, setConfirmationText] = React.useState('');
-
- const { data: preferences } = usePreferences();
const { data: profile } = useProfile();
const {
@@ -49,40 +40,23 @@ export const RestoreFromBackupDialog: React.FC = (props) => {
});
};
- const actions = (
-
-
- Cancel
-
-
- Restore Database
-
-
- );
-
- React.useEffect(() => {
- if (open) {
- setConfirmationText('');
- }
- }, [open]);
-
return (
-
{error ? (
= (props) => {
existing data on this cluster.
-
- To confirm restoration, type the name of the database cluster (
- {database.label}) in the field below.
-
- }
- onChange={(input) => setConfirmationText(input)}
- value={confirmationText}
- label="Database Label"
- visible={preferences?.type_to_confirm}
- placeholder={database.label}
- />
-
+
);
};
diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSettings/DatabaseSettingsDeleteClusterDialog.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSettings/DatabaseSettingsDeleteClusterDialog.tsx
index 5367d1f4b21..ade1e5b0ed2 100644
--- a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSettings/DatabaseSettingsDeleteClusterDialog.tsx
+++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSettings/DatabaseSettingsDeleteClusterDialog.tsx
@@ -2,15 +2,11 @@ import { Engine } from '@linode/api-v4/lib/databases';
import { useSnackbar } from 'notistack';
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 { Typography } from 'src/components/Typography';
import { Notice } from 'src/components/Notice/Notice';
-import { TypeToConfirm } from 'src/components/TypeToConfirm/TypeToConfirm';
+import { TypeToConfirmDialog } from 'src/components/TypeToConfirmDialog/TypeToConfirmDialog';
import { useDeleteDatabaseMutation } from 'src/queries/databases';
import { getAPIErrorOrDefault } from 'src/utilities/errorUtils';
-import { usePreferences } from 'src/queries/preferences';
interface Props {
open: boolean;
@@ -20,49 +16,17 @@ interface Props {
databaseLabel: string;
}
-const renderActions = (
- disabled: boolean,
- loading: boolean,
- onClose: () => void,
- onDelete: () => void
-) => (
-
-
- Cancel
-
-
- Delete Cluster
-
-
-);
-
export const DatabaseSettingsDeleteClusterDialog: React.FC = (props) => {
const { open, onClose, databaseID, databaseEngine, databaseLabel } = props;
const { enqueueSnackbar } = useSnackbar();
- const { data: preferences } = usePreferences();
const { mutateAsync: deleteDatabase } = useDeleteDatabaseMutation(
databaseEngine,
databaseID
);
const defaultError = 'There was an error deleting this Database Cluster.';
const [error, setError] = React.useState('');
- const [confirmText, setConfirmText] = React.useState('');
const [isLoading, setIsLoading] = React.useState(false);
const { push } = useHistory();
- const disabled =
- preferences?.type_to_confirm !== false && confirmText !== databaseLabel;
const onDeleteCluster = () => {
setIsLoading(true);
@@ -82,13 +46,22 @@ export const DatabaseSettingsDeleteClusterDialog: React.FC = (props) => {
};
return (
-
+ {error ? : null}
Warning: Deleting your entire database will delete
@@ -96,21 +69,7 @@ export const DatabaseSettingsDeleteClusterDialog: React.FC = (props) => {
may result in permanent data loss. This action cannot be undone.
- setConfirmText(input)}
- expand
- value={confirmText}
- confirmationText={
-
- To confirm deletion, type the name of the database cluster (
- {databaseLabel}) in the field below:
-
- }
- visible={preferences?.type_to_confirm}
- />
-
+
);
};
diff --git a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/DeleteKubernetesClusterDialog.tsx b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/DeleteKubernetesClusterDialog.tsx
index 3fb71969fa2..b234dd01d10 100644
--- a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/DeleteKubernetesClusterDialog.tsx
+++ b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/DeleteKubernetesClusterDialog.tsx
@@ -1,11 +1,7 @@
import * as React from 'react';
-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 { Typography } from 'src/components/Typography';
-import { TypeToConfirm } from 'src/components/TypeToConfirm/TypeToConfirm';
import { Notice } from 'src/components/Notice/Notice';
-import { usePreferences } from 'src/queries/preferences';
import { useDeleteKubernetesClusterMutation } from 'src/queries/kubernetes';
import { KubeNodePoolResponse } from '@linode/api-v4';
import { useHistory } from 'react-router-dom';
@@ -25,24 +21,12 @@ export const getTotalLinodes = (pools: KubeNodePoolResponse[]) => {
export const DeleteKubernetesClusterDialog = (props: Props) => {
const { clusterLabel, clusterId, open, onClose } = props;
-
const {
mutateAsync: deleteCluster,
isLoading: isDeleting,
error,
} = useDeleteKubernetesClusterMutation();
-
const history = useHistory();
- const { data: preferences } = usePreferences();
- const [confirmText, setConfirmText] = React.useState('');
- const disabled =
- preferences?.type_to_confirm !== false && confirmText !== clusterLabel;
-
- React.useEffect(() => {
- if (open && confirmText !== '') {
- setConfirmText('');
- }
- }, [open]);
const onDelete = () => {
deleteCluster({ id: clusterId }).then(() => {
@@ -51,37 +35,23 @@ export const DeleteKubernetesClusterDialog = (props: Props) => {
});
};
- const actions = (
-
-
- Cancel
-
-
- Delete Cluster
-
-
- );
-
return (
-
+ {error ? : null}
Warning:
@@ -94,23 +64,6 @@ export const DeleteKubernetesClusterDialog = (props: Props) => {
-
- To confirm deletion, type the name of the cluster (
- {clusterLabel}) in the field below:
-
- }
- value={confirmText}
- typographyStyle={{ marginTop: '10px' }}
- data-testid={'dialog-confirm-text-input'}
- expand
- onChange={(input) => {
- setConfirmText(input);
- }}
- visible={preferences?.type_to_confirm}
- />
-
+
);
};
diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/LinodeSettingsDeletePanel.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/LinodeSettingsDeletePanel.tsx
index 9d975ed5a0a..9a1da6e09fe 100644
--- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/LinodeSettingsDeletePanel.tsx
+++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/LinodeSettingsDeletePanel.tsx
@@ -52,7 +52,13 @@ export const LinodeSettingsDeletePanel = ({ linodeId, isReadOnly }: Props) => {
{
return (
{
{
delete: onDelete,
}[props.mode];
- const action = {
- detach: {
- verb: 'Detach',
- noun: 'detachment',
- },
- delete: {
- verb: 'Delete',
- noun: 'deletion',
- },
- }[props.mode];
-
const loading = {
detach: detachLoading,
delete: deleteLoading,
@@ -111,17 +100,17 @@ export const DestructiveVolumeDialog = (props: Props) => {
return (
- To confirm {action.noun}, type the name of the Volume ({label})
- in the field below:
-
- }
typographyStyle={{ marginTop: '10px' }}
>
{error && }