feat: container delete confirmation modal (#2145)

Update container delete confirmation modal based on #1982 and #1981
This commit is contained in:
Navin Karkera
2025-06-24 19:37:14 +02:00
committed by GitHub
parent 60cebf703d
commit 4905f3bbc7
21 changed files with 802 additions and 256 deletions

View File

@@ -20,6 +20,7 @@ import {
import { canEditComponent } from './ComponentEditorModal';
import ComponentDeleter from './ComponentDeleter';
import messages from './messages';
import containerMessages from '../containers/messages';
import { useLibraryRoutes } from '../routes';
import { useRunOnNextRender } from '../../utils';
@@ -58,9 +59,9 @@ export const ComponentMenu = ({ usageKey }: { usageKey: string }) => {
// Close sidebar if current component is open
closeLibrarySidebar();
}
showToast(intl.formatMessage(messages.removeComponentFromCollectionSuccess));
showToast(intl.formatMessage(containerMessages.removeComponentFromCollectionSuccess));
}).catch(() => {
showToast(intl.formatMessage(messages.removeComponentFromCollectionFailure));
showToast(intl.formatMessage(containerMessages.removeComponentFromCollectionFailure));
});
};
@@ -139,11 +140,11 @@ export const ComponentMenu = ({ usageKey }: { usageKey: string }) => {
</Dropdown.Item>
{insideCollection && (
<Dropdown.Item onClick={removeFromCollection}>
<FormattedMessage {...messages.menuRemoveFromCollection} />
<FormattedMessage {...containerMessages.menuRemoveFromCollection} />
</Dropdown.Item>
)}
<Dropdown.Item onClick={showManageCollections}>
<FormattedMessage {...messages.menuAddToCollection} />
<FormattedMessage {...containerMessages.menuAddToCollection} />
</Dropdown.Item>
</Dropdown.Menu>
{isConfirmingDelete && (

View File

@@ -1,7 +0,0 @@
.container-card-preview-text {
display: -webkit-box; /* stylelint-disable-line value-no-vendor-prefix */
line-clamp: 3;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
};

View File

@@ -1,390 +0,0 @@
import userEvent from '@testing-library/user-event';
import type MockAdapter from 'axios-mock-adapter';
import {
initializeMocks, render as baseRender, screen, waitFor,
fireEvent,
} from '../../testUtils';
import { LibraryProvider } from '../common/context/LibraryContext';
import { mockContentLibrary } from '../data/api.mocks';
import { type ContainerHit, PublishStatus } from '../../search-manager';
import ContainerCard from './ContainerCard';
import { getLibraryContainerApiUrl, getLibraryContainerRestoreApiUrl } from '../data/api';
import { ContainerType } from '../../generic/key-utils';
let axiosMock: MockAdapter;
let mockShowToast;
const mockNavigate = jest.fn();
const libraryId = 'lib:Axim:TEST';
const getContainerHitSample = (containerType: ContainerType = ContainerType.Unit) => ({
id: `lctorg1democourse-${containerType}-display-name-123`,
type: 'library_container',
contextKey: libraryId,
usageKey: `lct:org1:Demo_Course:${containerType}:${containerType}-display-name-123`,
org: 'org1',
blockId: `${containerType}-display-name-123`,
blockType: containerType,
breadcrumbs: [{ displayName: 'Demo Lib' }],
displayName: `${containerType} Display Name`,
formatted: {
displayName: `${containerType} Display Formated Name`,
published: {
displayName: `Published ${containerType} Display Name`,
},
},
created: 1722434322294,
modified: 1722434322294,
numChildren: 2,
published: {
numChildren: 1,
},
tags: {},
publishStatus: PublishStatus.Published,
} as ContainerHit);
mockContentLibrary.applyMock();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => mockNavigate,
}));
const render = (ui: React.ReactElement, showOnlyPublished: boolean = false) => baseRender(ui, {
path: '/library/:libraryId',
params: { libraryId },
extraWrapper: ({ children }) => (
<LibraryProvider
libraryId={libraryId}
showOnlyPublished={showOnlyPublished}
>
{children}
</LibraryProvider>
),
});
describe('<ContainerCard />', () => {
beforeEach(() => {
({ axiosMock, mockShowToast } = initializeMocks());
});
test.each([
{
label: 'should render the unit card with title',
containerType: ContainerType.Unit,
displayName: 'unit Display Formated Name',
},
{
label: 'should render the subsection card with title',
containerType: ContainerType.Subsection,
displayName: 'subsection Display Formated Name',
},
{
label: 'should render the section card with title',
containerType: ContainerType.Section,
displayName: 'section Display Formated Name',
},
])('$label', ({ containerType, displayName }) => {
const container = getContainerHitSample(containerType);
render(<ContainerCard hit={container} />);
expect(screen.getByText(displayName)).toBeInTheDocument();
expect(screen.queryByText('2')).toBeInTheDocument(); // Component count
});
test.each([
{
label: 'sould render published content of unit card',
containerType: ContainerType.Unit,
displayName: 'Published unit Display Name',
},
{
label: 'sould render published content of subsection card',
containerType: ContainerType.Subsection,
displayName: 'Published subsection Display Name',
},
{
label: 'sould render published content of section card',
containerType: ContainerType.Section,
displayName: 'Published section Display Name',
},
])('$label', ({ containerType, displayName }) => {
const container = getContainerHitSample(containerType);
render(<ContainerCard hit={container} />, true);
expect(screen.getByText(displayName)).toBeInTheDocument();
expect(screen.queryByText('1')).toBeInTheDocument(); // Published Component Count
});
test.each([
{
label: 'should navigate to the unit if the open menu clicked',
containerType: ContainerType.Unit,
},
{
label: 'should navigate to the section if the open menu clicked',
containerType: ContainerType.Section,
},
{
label: 'should navigate to the subsection if the open menu clicked',
containerType: ContainerType.Subsection,
},
])('$label', async ({ containerType }) => {
render(<ContainerCard hit={getContainerHitSample(containerType)} />);
// Open menu
expect(screen.getByTestId('container-card-menu-toggle')).toBeInTheDocument();
userEvent.click(screen.getByTestId('container-card-menu-toggle'));
// Open menu item
const openMenuItem = await screen.findByRole('button', { name: 'Open' });
expect(openMenuItem).toBeInTheDocument();
userEvent.click(openMenuItem);
expect(mockNavigate).toHaveBeenCalledWith({
pathname: `/library/${libraryId}/${containerType}/${getContainerHitSample(containerType).usageKey}`,
search: '',
});
});
test.each([
{
label: 'should navigate to the unit if the card is double clicked',
containerType: ContainerType.Unit,
},
{
label: 'should navigate to the section if the card is double clicked',
containerType: ContainerType.Section,
},
{
label: 'should navigate to the subsection if the card is double clicked',
containerType: ContainerType.Subsection,
},
])('$label', async ({ containerType }) => {
render(<ContainerCard hit={getContainerHitSample(containerType)} />);
// Open menu item
const cardItem = await screen.findByText(`${containerType} Display Formated Name`);
expect(cardItem).toBeInTheDocument();
userEvent.click(cardItem, undefined, { clickCount: 2 });
expect(mockNavigate).toHaveBeenCalledWith({
pathname: `/library/${libraryId}/${containerType}/${getContainerHitSample(containerType).usageKey}`,
search: '',
});
});
it('should delete the container from the menu & restore the container', async () => {
axiosMock.onDelete(getLibraryContainerApiUrl(getContainerHitSample().usageKey)).reply(200);
render(<ContainerCard hit={getContainerHitSample()} />);
// Open menu
expect(screen.getByTestId('container-card-menu-toggle')).toBeInTheDocument();
userEvent.click(screen.getByTestId('container-card-menu-toggle'));
// Click on Delete Item
const deleteMenuItem = screen.getByRole('button', { name: 'Delete' });
expect(deleteMenuItem).toBeInTheDocument();
fireEvent.click(deleteMenuItem);
// Confirm delete Modal is open
expect(await screen.findByText('Delete Unit')).toBeInTheDocument();
const deleteButton = screen.getByRole('button', { name: /delete/i });
fireEvent.click(deleteButton);
await waitFor(() => {
expect(axiosMock.history.delete.length).toBe(1);
});
expect(mockShowToast).toHaveBeenCalled();
// Get restore / undo func from the toast
const restoreFn = mockShowToast.mock.calls[0][1].onClick;
const restoreUrl = getLibraryContainerRestoreApiUrl(getContainerHitSample().usageKey);
axiosMock.onPost(restoreUrl).reply(200);
// restore collection
restoreFn();
await waitFor(() => {
expect(axiosMock.history.post.length).toEqual(1);
});
expect(mockShowToast).toHaveBeenCalledWith('Undo successful');
});
it('should show error on delete the container from the menu', async () => {
axiosMock.onDelete(getLibraryContainerApiUrl(getContainerHitSample().usageKey)).reply(400);
render(<ContainerCard hit={getContainerHitSample()} />);
// Open menu
expect(screen.getByTestId('container-card-menu-toggle')).toBeInTheDocument();
userEvent.click(screen.getByTestId('container-card-menu-toggle'));
// Click on Delete Item
const deleteMenuItem = screen.getByRole('button', { name: 'Delete' });
expect(deleteMenuItem).toBeInTheDocument();
fireEvent.click(deleteMenuItem);
// Confirm delete Modal is open
expect(screen.getByText('Delete Unit'));
const deleteButton = screen.getByRole('button', { name: /delete/i });
fireEvent.click(deleteButton);
await waitFor(() => {
expect(axiosMock.history.delete.length).toBe(1);
});
expect(mockShowToast).toHaveBeenCalledWith('Failed to delete unit');
});
it('should render no child blocks in unit card preview', async () => {
render(<ContainerCard hit={getContainerHitSample()} />);
expect(screen.queryByTitle('lb:org1:Demo_course:html:text-0')).not.toBeInTheDocument();
expect(screen.queryByText('+0')).not.toBeInTheDocument();
});
it('should render <=5 child blocks in unit card preview', async () => {
const containerWith5Children = {
...getContainerHitSample(),
content: {
childUsageKeys: Array(5).fill('').map((_child, idx) => `lb:org1:Demo_course:html:text-${idx}`),
},
} satisfies ContainerHit;
render(<ContainerCard hit={containerWith5Children} />);
expect((await screen.findAllByTitle(/lb:org1:Demo_course:html:text-*/)).length).toBe(5);
expect(screen.queryByText('+0')).not.toBeInTheDocument();
});
it('should render >5 child blocks with +N in unit card preview', async () => {
const containerWith6Children = {
...getContainerHitSample(),
content: {
childUsageKeys: Array(6).fill('').map((_child, idx) => `lb:org1:Demo_course:html:text-${idx}`),
},
} satisfies ContainerHit;
render(<ContainerCard hit={containerWith6Children} />);
expect((await screen.findAllByTitle(/lb:org1:Demo_course:html:text-*/)).length).toBe(4);
expect(screen.queryByText('+2')).toBeInTheDocument();
});
it('should render published child blocks when rendering a published unit card preview', async () => {
const containerWithPublishedChildren = {
...getContainerHitSample(),
content: {
childUsageKeys: Array(6).fill('').map((_child, idx) => `lb:org1:Demo_course:html:text-${idx}`),
},
published: {
content: {
childUsageKeys: Array(2).fill('').map((_child, idx) => `lb:org1:Demo_course:html:text-${idx}`),
},
},
} satisfies ContainerHit;
render(
<ContainerCard hit={containerWithPublishedChildren} />,
true,
);
expect((await screen.findAllByTitle(/lb:org1:Demo_course:html:text-*/)).length).toBe(2);
expect(screen.queryByText('+2')).not.toBeInTheDocument();
});
test.each([
{
label: 'should render published child in subsection card preview',
containerType: ContainerType.Subsection,
childrenType: 'unit',
displayName: 'Published subsection Display Name',
expected: /contains unit 0, unit 1\./i,
},
{
label: 'should render published child in section card preview',
containerType: ContainerType.Section,
childrenType: 'subsection',
displayName: 'Published section Display Name',
expected: /contains subsection 0, subsection 1\./i,
},
])('$label', ({
containerType,
childrenType,
displayName,
expected,
}) => {
const containerWithChildren = {
...getContainerHitSample(containerType),
content: {
childUsageKeys: Array(6).fill('').map(
(_child, idx) => `lct:org1:Demo_Course:${childrenType}:${childrenType}-${idx}`,
),
childDisplayNames: Array(6).fill('').map((_child, idx) => `${childrenType} ${idx}`),
},
published: {
content: {
childUsageKeys: Array(2).fill('').map(
(_child, idx) => `lct:org1:Demo_Course:${childrenType}:${childrenType}-${idx}`,
),
childDisplayNames: Array(2).fill('').map((_child, idx) => `${childrenType} ${idx}`),
},
},
} satisfies ContainerHit;
render(<ContainerCard hit={containerWithChildren} />, true);
expect(screen.getByText(displayName)).toBeInTheDocument();
expect(screen.getByText(expected)).toBeInTheDocument();
});
test.each([
{
label: 'should render subsection card preview with children',
containerType: ContainerType.Subsection,
childrenType: 'unit',
displayName: 'subsection Display Formated Name',
expected: /contains unit 0, unit 1\./i,
},
{
label: 'should render section card preview with children',
containerType: ContainerType.Section,
childrenType: 'subsection',
displayName: 'section Display Formated Name',
expected: /contains subsection 0, subsection 1\./i,
},
])('$label', ({
containerType,
childrenType,
displayName,
expected,
}) => {
const containerWithChildren = {
...getContainerHitSample(containerType),
content: {
childUsageKeys: Array(2).fill('').map(
(_child, idx) => `lct:org1:Demo_Course:${childrenType}:${childrenType}-${idx}`,
),
childDisplayNames: Array(2).fill('').map((_child, idx) => `${childrenType} ${idx}`),
},
} satisfies ContainerHit;
render(<ContainerCard hit={containerWithChildren} />);
expect(screen.getByText(displayName)).toBeInTheDocument();
expect(screen.getByText(expected)).toBeInTheDocument();
});
test.each([
{
label: 'should render subsection card preview without children',
containerType: ContainerType.Subsection,
displayName: 'subsection Display Formated Name',
},
{
label: 'should render section card preview without children',
containerType: ContainerType.Section,
displayName: 'section Display Formated Name',
},
])('$label', ({ containerType, displayName }) => {
const container = getContainerHitSample(containerType);
render(<ContainerCard hit={container} />);
expect(screen.getByText(displayName)).toBeInTheDocument();
expect(screen.queryByText(/contains/i)).not.toBeInTheDocument();
});
});

View File

@@ -1,279 +0,0 @@
import { ReactNode, useCallback, useContext } from 'react';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import {
ActionRow,
Dropdown,
Icon,
IconButton,
useToggle,
Stack,
} from '@openedx/paragon';
import { MoreVert } from '@openedx/paragon/icons';
import { getItemIcon, getComponentStyleColor } from '../../generic/block-type-utils';
import { getBlockType } from '../../generic/key-utils';
import { ToastContext } from '../../generic/toast-context';
import { type ContainerHit, Highlight, PublishStatus } from '../../search-manager';
import { useComponentPickerContext } from '../common/context/ComponentPickerContext';
import { useLibraryContext } from '../common/context/LibraryContext';
import { SidebarActions, useSidebarContext } from '../common/context/SidebarContext';
import { useRemoveItemsFromCollection } from '../data/apiHooks';
import { useLibraryRoutes } from '../routes';
import AddComponentWidget from './AddComponentWidget';
import BaseCard from './BaseCard';
import messages from './messages';
import ContainerDeleter from './ContainerDeleter';
import { useRunOnNextRender } from '../../utils';
type ContainerMenuProps = {
containerKey: string;
displayName: string;
};
export const ContainerMenu = ({ containerKey, displayName } : ContainerMenuProps) => {
const intl = useIntl();
const { libraryId, collectionId } = useLibraryContext();
const {
sidebarItemInfo,
closeLibrarySidebar,
setSidebarAction,
} = useSidebarContext();
const { showToast } = useContext(ToastContext);
const [isConfirmingDelete, confirmDelete, cancelDelete] = useToggle(false);
const { navigateTo, insideCollection } = useLibraryRoutes();
const removeComponentsMutation = useRemoveItemsFromCollection(libraryId, collectionId);
const removeFromCollection = () => {
removeComponentsMutation.mutateAsync([containerKey]).then(() => {
if (sidebarItemInfo?.id === containerKey) {
// Close sidebar if current component is open
closeLibrarySidebar();
}
showToast(intl.formatMessage(messages.removeComponentFromCollectionSuccess));
}).catch(() => {
showToast(intl.formatMessage(messages.removeComponentFromCollectionFailure));
});
};
const scheduleJumpToCollection = useRunOnNextRender(() => {
// TODO: Ugly hack to make sure sidebar shows add to collection section
// This needs to run after all changes to url takes place to avoid conflicts.
setTimeout(() => setSidebarAction(SidebarActions.JumpToManageCollections));
});
const showManageCollections = useCallback(() => {
navigateTo({ selectedItemId: containerKey });
scheduleJumpToCollection();
}, [scheduleJumpToCollection, navigateTo, containerKey]);
const openContainer = useCallback(() => {
navigateTo({ containerId: containerKey });
}, [navigateTo, containerKey]);
return (
<>
<Dropdown id="container-card-dropdown">
<Dropdown.Toggle
id="container-card-menu-toggle"
as={IconButton}
src={MoreVert}
iconAs={Icon}
variant="primary"
alt={intl.formatMessage(messages.containerCardMenuAlt)}
data-testid="container-card-menu-toggle"
/>
<Dropdown.Menu>
<Dropdown.Item onClick={openContainer}>
<FormattedMessage {...messages.menuOpen} />
</Dropdown.Item>
<Dropdown.Item onClick={confirmDelete}>
<FormattedMessage {...messages.menuDeleteContainer} />
</Dropdown.Item>
{insideCollection && (
<Dropdown.Item onClick={removeFromCollection}>
<FormattedMessage {...messages.menuRemoveFromCollection} />
</Dropdown.Item>
)}
<Dropdown.Item onClick={showManageCollections}>
<FormattedMessage {...messages.menuAddToCollection} />
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<ContainerDeleter
isOpen={isConfirmingDelete}
close={cancelDelete}
containerId={containerKey}
displayName={displayName}
/>
</>
);
};
type UnitCardPreviewProps = {
childKeys: Array<string>;
showMaxChildren?: number;
};
const UnitcardPreview = ({ childKeys, showMaxChildren = 5 }: UnitCardPreviewProps) => {
const hiddenChildren = childKeys.length - showMaxChildren;
return (
<Stack direction="horizontal" gap={2}>
{
childKeys.slice(0, showMaxChildren).map((usageKey, idx) => {
const blockType = getBlockType(usageKey);
let blockPreview: ReactNode;
let classNames;
if (idx < showMaxChildren - 1 || hiddenChildren <= 0) {
// Show the first N-1 blocks as item icons
// (or all N blocks if no hidden children)
classNames = `rounded p-1 ${getComponentStyleColor(blockType)}`;
blockPreview = (
<Icon
src={getItemIcon(blockType)}
screenReaderText={blockType}
title={usageKey}
/>
);
} else {
// Container has more blocks than can fit in the preview, so show "+N"
blockPreview = (
<FormattedMessage
{...messages.containerPreviewMoreBlocks}
values={{ count: hiddenChildren + 1 }}
/>
);
}
return (
<div
// A container can have multiple instances of the same block
// eslint-disable-next-line react/no-array-index-key
key={`${usageKey}-${idx}`}
className={classNames}
>
{blockPreview}
</div>
);
})
}
</Stack>
);
};
type ContainerCardPreviewProps = {
hit: ContainerHit,
};
const ContainerCardPreview = ({ hit }: ContainerCardPreviewProps) => {
const intl = useIntl();
const { showOnlyPublished } = useLibraryContext();
const {
blockType: itemType,
published,
content,
} = hit;
if (itemType === 'unit') {
const childKeys: Array<string> = (
showOnlyPublished ? published?.content?.childUsageKeys : content?.childUsageKeys
) ?? [];
return <UnitcardPreview childKeys={childKeys} />;
}
// TODO Section highlights
const childNames: Array<string> = (
showOnlyPublished ? published?.content?.childDisplayNames : content?.childDisplayNames
) ?? [];
if (childNames.length > 0) {
// Preview with a truncated text with all children display names
const childrenText = intl.formatMessage(
messages.containerPreviewText,
{
children: childNames.join(', '),
},
);
return (
<div className="container-card-preview-text">
<Highlight text={childrenText} />
</div>
);
}
// Empty preview
return null;
};
type ContainerCardProps = {
hit: ContainerHit,
};
const ContainerCard = ({ hit } : ContainerCardProps) => {
const { componentPickerMode } = useComponentPickerContext();
const { showOnlyPublished } = useLibraryContext();
const { openContainerInfoSidebar, sidebarItemInfo } = useSidebarContext();
const {
blockType: itemType,
formatted,
tags,
numChildren,
published,
publishStatus,
usageKey: containerKey,
} = hit;
const numChildrenCount = showOnlyPublished ? (
published?.numChildren || 0
) : numChildren;
const displayName: string = (
showOnlyPublished ? formatted.published?.displayName : formatted.displayName
) ?? '';
const selected = sidebarItemInfo?.id === containerKey;
const { navigateTo } = useLibraryRoutes();
const selectContainer = useCallback((e?: React.MouseEvent) => {
const doubleClicked = (e?.detail || 0) > 1;
if (componentPickerMode) {
// In component picker mode, we want to open the sidebar
// without changing the URL
openContainerInfoSidebar(containerKey);
} else if (!doubleClicked) {
navigateTo({ selectedItemId: containerKey });
} else {
navigateTo({ containerId: containerKey });
}
}, [containerKey, openContainerInfoSidebar, navigateTo]);
return (
<BaseCard
itemType={itemType}
displayName={displayName}
preview={<ContainerCardPreview hit={hit} />}
tags={tags}
numChildren={numChildrenCount}
actions={(
<ActionRow>
{componentPickerMode ? (
<AddComponentWidget usageKey={containerKey} blockType={itemType} />
) : (
<ContainerMenu
containerKey={containerKey}
displayName={hit.displayName}
/>
)}
</ActionRow>
)}
hasUnpublishedChanges={publishStatus !== PublishStatus.Published}
onSelect={selectContainer}
selected={selected}
/>
);
};
export default ContainerCard;

View File

@@ -1,96 +0,0 @@
import { useCallback, useContext } from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Icon } from '@openedx/paragon';
import { Warning, School, Widgets } from '@openedx/paragon/icons';
import DeleteModal from '../../generic/delete-modal/DeleteModal';
import { useSidebarContext } from '../common/context/SidebarContext';
import { ToastContext } from '../../generic/toast-context';
import { useDeleteContainer, useRestoreContainer } from '../data/apiHooks';
import messages from './messages';
type ContainerDeleterProps = {
isOpen: boolean,
close: () => void,
containerId: string,
displayName: string,
};
const ContainerDeleter = ({
isOpen,
close,
containerId,
displayName,
}: ContainerDeleterProps) => {
const intl = useIntl();
const {
sidebarItemInfo,
closeLibrarySidebar,
} = useSidebarContext();
const deleteContainerMutation = useDeleteContainer(containerId);
const restoreContainerMutation = useRestoreContainer(containerId);
const { showToast } = useContext(ToastContext);
// TODO: support other container types besides 'unit'
const deleteWarningTitle = intl.formatMessage(messages.deleteUnitWarningTitle);
const deleteText = intl.formatMessage(messages.deleteUnitConfirm, {
unitName: <b>{displayName}</b>,
message: (
<>
<div className="d-flex mt-2">
<Icon className="mr-2" src={School} />
{intl.formatMessage(messages.deleteUnitConfirmMsg1)}
</div>
<div className="d-flex mt-2">
<Icon className="mr-2" src={Widgets} />
{intl.formatMessage(messages.deleteUnitConfirmMsg2)}
</div>
</>
),
});
const deleteSuccess = intl.formatMessage(messages.deleteUnitSuccess);
const deleteError = intl.formatMessage(messages.deleteUnitFailed);
const undoDeleteError = messages.undoDeleteUnitToastFailed;
const restoreComponent = useCallback(async () => {
try {
await restoreContainerMutation.mutateAsync();
showToast(intl.formatMessage(messages.undoDeleteContainerToastMessage));
} catch (e) {
showToast(intl.formatMessage(undoDeleteError));
}
}, []);
const onDelete = useCallback(async () => {
await deleteContainerMutation.mutateAsync().then(() => {
if (sidebarItemInfo?.id === containerId) {
closeLibrarySidebar();
}
showToast(
deleteSuccess,
{
label: intl.formatMessage(messages.undoDeleteContainerToastAction),
onClick: restoreComponent,
},
);
}).catch(() => {
showToast(deleteError);
}).finally(() => {
close();
});
}, [sidebarItemInfo, showToast, deleteContainerMutation]);
return (
<DeleteModal
isOpen={isOpen}
close={close}
variant="warning"
title={deleteWarningTitle}
icon={Warning}
description={deleteText}
onDeleteSubmit={onDelete}
/>
);
};
export default ContainerDeleter;

View File

@@ -1,2 +1 @@
@import "./BaseCard.scss";
@import "./ContainerCard.scss";

View File

@@ -11,11 +11,6 @@ const messages = defineMessages({
defaultMessage: 'Collection actions menu',
description: 'Alt/title text for the collection card menu button.',
},
containerCardMenuAlt: {
id: 'course-authoring.library-authoring.container.menu',
defaultMessage: 'Container actions menu',
description: 'Alt/title text for the container card menu button.',
},
menuOpen: {
id: 'course-authoring.library-authoring.menu.open',
defaultMessage: 'Open',
@@ -36,26 +31,6 @@ const messages = defineMessages({
defaultMessage: 'Delete',
description: 'Menu item for deleting a component.',
},
menuAddToCollection: {
id: 'course-authoring.library-authoring.component.menu.add',
defaultMessage: 'Add to collection',
description: 'Menu item for add a component to collection.',
},
menuRemoveFromCollection: {
id: 'course-authoring.library-authoring.component.menu.remove',
defaultMessage: 'Remove from collection',
description: 'Menu item for remove an item from collection.',
},
removeComponentFromCollectionSuccess: {
id: 'course-authoring.library-authoring.component.remove-from-collection-success',
defaultMessage: 'Item successfully removed',
description: 'Message for successful removal of an item from collection.',
},
removeComponentFromCollectionFailure: {
id: 'course-authoring.library-authoring.component.remove-from-collection-failure',
defaultMessage: 'Failed to remove item',
description: 'Message for failure of removal of an item from collection.',
},
deleteComponentWarningTitle: {
id: 'course-authoring.library-authoring.component.delete-confirmation-title',
defaultMessage: 'Delete Component',
@@ -191,61 +166,6 @@ const messages = defineMessages({
defaultMessage: 'This component can be synced in courses after publish.',
description: 'Alert text of the modal to confirm publish a component in a library.',
},
menuDeleteContainer: {
id: 'course-authoring.library-authoring.container.delete-menu-text',
defaultMessage: 'Delete',
description: 'Menu item to delete a container.',
},
deleteUnitWarningTitle: {
id: 'course-authoring.library-authoring.unit.delete-confirmation-title',
defaultMessage: 'Delete Unit',
description: 'Title text for the warning displayed before deleting a Unit',
},
deleteUnitConfirm: {
id: 'course-authoring.library-authoring.unit.delete-confirmation-text',
defaultMessage: 'Delete {unitName}? {message}',
description: 'Confirmation text to display before deleting a unit',
},
deleteUnitConfirmMsg1: {
id: 'course-authoring.library-authoring.unit.delete-confirmation-msg-1',
defaultMessage: 'Any course instances will stop receiving updates.',
description: 'First part of confirmation message to display before deleting a unit',
},
deleteUnitConfirmMsg2: {
id: 'course-authoring.library-authoring.unit.delete-confirmation-msg-2',
defaultMessage: 'Any components will remain in the library.',
description: 'Second part of confirmation message to display before deleting a unit',
},
deleteUnitSuccess: {
id: 'course-authoring.library-authoring.unit.delete.success',
defaultMessage: 'Unit deleted',
description: 'Message to display on delete unit success',
},
deleteUnitFailed: {
id: 'course-authoring.library-authoring.unit.delete-failed-error',
defaultMessage: 'Failed to delete unit',
description: 'Message to display on failure to delete a unit',
},
undoDeleteContainerToastAction: {
id: 'course-authoring.library-authoring.container.undo-delete-container-toast-button',
defaultMessage: 'Undo',
description: 'Toast message to undo deletion of container',
},
undoDeleteContainerToastMessage: {
id: 'course-authoring.library-authoring.container.undo-delete-container-toast-text',
defaultMessage: 'Undo successful',
description: 'Message to display on undo delete container success',
},
undoDeleteUnitToastFailed: {
id: 'course-authoring.library-authoring.unit.undo-delete-unit-failed',
defaultMessage: 'Failed to undo delete Unit operation',
description: 'Message to display on failure to undo delete unit',
},
containerPreviewMoreBlocks: {
id: 'course-authoring.library-authoring.component.container-card-preview.more-blocks',
defaultMessage: '+{count}',
description: 'Count shown when a container has more blocks than will fit on the card preview.',
},
removeComponentFromUnitMenu: {
id: 'course-authoring.library-authoring.unit.component.remove.button',
defaultMessage: 'Remove from unit',
@@ -276,10 +196,5 @@ const messages = defineMessages({
defaultMessage: 'Failed to undo remove component operation',
description: 'Message to display on failure to undo delete component',
},
containerPreviewText: {
id: 'course-authoring.library-authoring.container.preview.text',
defaultMessage: 'Contains {children}.',
description: 'Preview message for section/subsections with the names of children separated by commas',
},
});
export default messages;