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 ability to customize the success notification message in delete buttons #9868

42 changes: 22 additions & 20 deletions docs/Buttons.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,15 +246,16 @@ export const PostList = () => (

![Bulk Delete button](./img/bulk-delete-button.png)

| Prop | Required | Type | Default | Description |
|-------------------|----------|-----------------------------------------|--------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| `confirmContent` | Optional | React node | - | Lets you customize the content of the confirm dialog. Only used in `'pessimistic'` or `'optimistic'` mutation modes |
| `confirmTitle` | Optional | `string` | - | Lets you customize the title of the confirm dialog. Only used in `'pessimistic'` or `'optimistic'` mutation modes |
| `confirmColor` | Optional | <code>'primary' &#124; 'warning'</code> | 'primary' | Lets you customize the color of the confirm dialog's "Confirm" button. Only used in `'pessimistic'` or `'optimistic'` mutation modes |
| `label` | Optional | `string` | 'ra.action.delete' | label or translation message to use |
| `icon` | Optional | `ReactElement` | `<DeleteIcon>` | iconElement, e.g. `<CommentIcon />` |
| `mutationMode` | Optional | `string` | `'undoable'` | Mutation mode (`'undoable'`, `'pessimistic'` or `'optimistic'`) |
| `mutationOptions` | Optional | `object` | null | options for react-query `useMutation` hook |
| Prop | Required | Type | Default | Description |
|-------------------|----------|-----------------------------------------|--------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| `confirmContent` | Optional | React node | - | Lets you customize the content of the confirm dialog. Only used in `'pessimistic'` or `'optimistic'` mutation modes |
| `confirmTitle` | Optional | `string` | - | Lets you customize the title of the confirm dialog. Only used in `'pessimistic'` or `'optimistic'` mutation modes |
| `confirmColor` | Optional | <code>'primary' &#124; 'warning'</code> | 'primary' | Lets you customize the color of the confirm dialog's "Confirm" button. Only used in `'pessimistic'` or `'optimistic'` mutation modes |
| `label` | Optional | `string` | 'ra.action.delete' | label or translation message to use |
| `icon` | Optional | `ReactElement` | `<DeleteIcon>` | iconElement, e.g. `<CommentIcon />` |
| `mutationMode` | Optional | `string` | `'undoable'` | Mutation mode (`'undoable'`, `'pessimistic'` or `'optimistic'`) |
| `mutationOptions` | Optional | `object` | null | options for react-query `useMutation` hook |
| `successMessage` | Optional | `string` | 'ra.notification.deleted'| Lets you customize the success notification message. |

**Tip:** If you choose the `'pessimistic'` or `'optimistic'` mutation mode, a confirm dialog will be displayed to the user before the mutation is executed.

Expand Down Expand Up @@ -659,17 +660,18 @@ See [its documentation](./UpdateButton.md) for more details.

Delete the current record after a confirm dialog has been accepted. To be used inside a `<Toolbar/>` component.

| Prop | Required | Type | Default | Description |
|--------------------|----------|--------------------------------------------------|-----------------------------|-------------------------------------------------------------------------|
| `className` | Optional | `string` | - | Class name to customize the look and feel of the button element itself |
| `label` | Optional | `string` | 'ra.action.delete' | label or translation message to use |
| `icon` | Optional | `ReactElement` | `<DeleteIcon>` | iconElement, e.g. `<CommentIcon />` |
| `confirmTitle` | Optional | `ReactNode` | 'ra.message.delete_title' | Title of the confirm dialog |
| `confirmContent` | Optional | `ReactNode` | 'ra.message.delete_content' | Message or React component to be used as the body of the confirm dialog |
| `confirmColor` | Optional | <code>'primary' &#124; 'warning'</code> | 'primary' | The color of the confirm dialog's "Confirm" button |
| `redirect` | Optional | <code>string &#124; false &#124; Function</code> | 'list' | Custom redirection after success side effect |
| `translateOptions` | Optional | `{ id?: string, name?: string }` | {} | Custom id and name to be used in the confirm dialog's title |
| `mutationOptions` | Optional | | null | options for react-query `useMutation` hook |
| Prop Required | Type | Default | Description |
|-----------------------------|--------------------------------------------------|-----------------------------|-------------------------------------------------------------------------|
| `className` Optional | `string` | - | Class name to customize the look and feel of the button element itself |
| `label` Optional | `string` | 'ra.action.delete' | label or translation message to use |
| `icon` Optional | `ReactElement` | `<DeleteIcon>` | iconElement, e.g. `<CommentIcon />` |
| `confirmTitle` Optional | `ReactNode` | 'ra.message.delete_title' | Title of the confirm dialog |
| `confirmContent` Optional | `ReactNode` | 'ra.message.delete_content' | Message or React component to be used as the body of the confirm dialog |
| `confirmColor` Optional | <code>'primary' &#124; 'warning'</code> | 'primary' | The color of the confirm dialog's "Confirm" button |
| `redirect` Optional | <code>string &#124; false &#124; Function</code> | 'list' | Custom redirection after success side effect |
| `translateOptions` Optional | `{ id?: string, name?: string }` | {} | Custom id and name to be used in the confirm dialog's title |
| `mutationOptions` Optional | | null | options for react-query `useMutation` hook |
| `successMessage` Optional | `string` | 'ra.notification.deleted' | Lets you customize the success notification message. |

{% raw %}
```jsx
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import useDeleteWithConfirmController, {
} from './useDeleteWithConfirmController';

import { TestMemoryRouter } from '../../routing';
import { useNotificationContext } from '../../notification';

describe('useDeleteWithConfirmController', () => {
it('should call the dataProvider.delete() function with the meta param', async () => {
Expand Down Expand Up @@ -47,4 +48,54 @@ describe('useDeleteWithConfirmController', () => {
timeout: 1000,
});
});

it('should display success message after successful deletion', async () => {
const successMessage = 'Test Message';
const dataProvider = testDataProvider({
delete: jest.fn().mockResolvedValue({ data: {} }),
});

const MockComponent = () => {
const { handleDelete } = useDeleteWithConfirmController({
record: { id: 1 },
resource: 'posts',
successMessage,
} as UseDeleteWithConfirmControllerParams);
return <button onClick={handleDelete}>Delete</button>;
};

let notificationsSpy;
const Notification = () => {
const { notifications } = useNotificationContext();
React.useEffect(() => {
notificationsSpy = notifications;
}, [notifications]);
return null;
};

render(
<TestMemoryRouter>
<CoreAdminContext dataProvider={dataProvider}>
<MockComponent />
<Notification />
</CoreAdminContext>
</TestMemoryRouter>
);

const button = screen.getByText('Delete');
fireEvent.click(button);

await waitFor(() => {
expect(notificationsSpy).toEqual([
{
message: successMessage,
type: 'info',
notificationOptions: {
messageArgs: { smart_count: 1 },
undoable: false,
},
},
]);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const useDeleteWithConfirmController = <RecordType extends RaRecord = any>(
mutationMode,
onClick,
mutationOptions = {},
successMessage = 'ra.notification.deleted',
} = props;
const { meta: mutationMeta, ...otherMutationOptions } = mutationOptions;
const resource = useResourceContext(props);
Expand All @@ -85,7 +86,7 @@ const useDeleteWithConfirmController = <RecordType extends RaRecord = any>(
{
onSuccess: () => {
setOpen(false);
notify('ra.notification.deleted', {
notify(successMessage, {
type: 'info',
messageArgs: { smart_count: 1 },
undoable: mutationMode === 'undoable',
Expand Down Expand Up @@ -185,6 +186,7 @@ export interface UseDeleteWithConfirmControllerParams<
MutationOptionsError,
DeleteParams<RecordType>
>;
successMessage?: string;
}

export interface UseDeleteWithConfirmControllerReturn {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import useDeleteWithUndoController, {
} from './useDeleteWithConfirmController';

import { TestMemoryRouter } from '../../routing';
import { useNotificationContext } from '../../notification';

describe('useDeleteWithUndoController', () => {
it('should call the dataProvider.delete() function with the meta param', async () => {
Expand Down Expand Up @@ -47,4 +48,54 @@ describe('useDeleteWithUndoController', () => {
timeout: 1000,
});
});

it('should display success message after successful deletion', async () => {
const successMessage = 'Test Message';
const dataProvider = testDataProvider({
delete: jest.fn().mockResolvedValue({ data: {} }),
});

const MockComponent = () => {
const { handleDelete } = useDeleteWithUndoController({
record: { id: 1 },
resource: 'posts',
successMessage,
} as UseDeleteWithConfirmControllerParams);
return <button onClick={handleDelete}>Delete</button>;
};

let notificationsSpy;
const Notification = () => {
const { notifications } = useNotificationContext();
React.useEffect(() => {
notificationsSpy = notifications;
}, [notifications]);
return null;
};

render(
<TestMemoryRouter>
<CoreAdminContext dataProvider={dataProvider}>
<MockComponent />
<Notification />
</CoreAdminContext>
</TestMemoryRouter>
);

const button = screen.getByText('Delete');
fireEvent.click(button);

await waitFor(() => {
expect(notificationsSpy).toEqual([
{
message: successMessage,
type: 'info',
notificationOptions: {
messageArgs: { smart_count: 1 },
undoable: false,
ethembynkr marked this conversation as resolved.
Show resolved Hide resolved
},
},
]);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const useDeleteWithUndoController = <RecordType extends RaRecord = any>(
redirect: redirectTo = 'list',
onClick,
mutationOptions = {},
successMessage = 'ra.notification.deleted',
} = props;
const { meta: mutationMeta, ...otherMutationOptions } = mutationOptions;
const resource = useResourceContext(props);
Expand All @@ -62,7 +63,7 @@ const useDeleteWithUndoController = <RecordType extends RaRecord = any>(
undefined,
{
onSuccess: () => {
notify('ra.notification.deleted', {
notify(successMessage, {
type: 'info',
messageArgs: { smart_count: 1 },
undoable: true,
Expand Down Expand Up @@ -141,6 +142,7 @@ export interface UseDeleteWithUndoControllerParams<
MutationOptionsError,
DeleteParams<RecordType>
>;
successMessage?: string;
}

export interface UseDeleteWithUndoControllerReturn {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import * as React from 'react';
import { screen, render, waitFor, fireEvent } from '@testing-library/react';
import expect from 'expect';
import {
CoreAdminContext,
testDataProvider,
ListContextProvider,
useNotificationContext,
} from 'ra-core';
import { createTheme, ThemeProvider } from '@mui/material/styles';

import { BulkDeleteWithConfirmButton } from './BulkDeleteWithConfirmButton';

const theme = createTheme();

describe('<BulkDeleteWithConfirmButton />', () => {
it('should display success message after successful deletion', async () => {
const dataProvider = testDataProvider({
deleteMany: jest
.fn()
.mockResolvedValueOnce({ data: [{ id: 123 }] }),
});

let notificationsSpy;
const Notification = () => {
const { notifications } = useNotificationContext();
React.useEffect(() => {
notificationsSpy = notifications;
}, [notifications]);
return null;
};

const successMessage = 'Test Message';
render(
<CoreAdminContext dataProvider={dataProvider}>
<ThemeProvider theme={theme}>
<ListContextProvider
value={{
selectedIds: [123],
onUnselectItems: jest.fn(),
}}
>
<BulkDeleteWithConfirmButton
resource="test"
mutationMode={'pessimistic'}
successMessage={successMessage}
/>
<Notification />
</ListContextProvider>
</ThemeProvider>
</CoreAdminContext>
);
fireEvent.click(screen.getByLabelText('ra.action.delete'));
fireEvent.click(screen.getByText('ra.action.confirm'));

await waitFor(() => {
expect(notificationsSpy).toEqual([
{
message: successMessage,
type: 'info',
notificationOptions: {
messageArgs: { smart_count: 1 },
undoable: false,
},
},
]);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const BulkDeleteWithConfirmButton = (
label = 'ra.action.delete',
mutationMode = 'pessimistic',
mutationOptions = {},
successMessage = 'ra.notification.deleted',
onClick,
...rest
} = props;
Expand All @@ -48,7 +49,7 @@ export const BulkDeleteWithConfirmButton = (
{
onSuccess: () => {
refresh();
notify('ra.notification.deleted', {
notify(successMessage, {
type: 'info',
messageArgs: { smart_count: selectedIds.length },
undoable: mutationMode === 'undoable',
Expand Down Expand Up @@ -157,6 +158,7 @@ export interface BulkDeleteWithConfirmButtonProps<
MutationOptionsError,
DeleteManyParams<RecordType>
> & { meta?: any };
successMessage?: string;
}

const PREFIX = 'RaBulkDeleteWithConfirmButton';
Expand Down
Loading