fix: unit pages ux bugs [FC-0083] (#1884)

This PR fixes some UX bugs related to the unit pages:

* Sort for "recently modified" on unit tab does not update after adding new components to units
* Change component delete warning message
This commit is contained in:
Rômulo Penido
2025-05-06 22:34:07 -03:00
committed by GitHub
parent 208b0c9195
commit d7173036a5
7 changed files with 61 additions and 47 deletions

View File

@@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import {
ActionRow,
Button,
@@ -9,17 +8,29 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import messages from './messages';
import LoadingButton from '../loading-button';
interface DeleteModalProps {
isOpen: boolean;
close: () => void;
category?: string;
onDeleteSubmit: () => void | Promise<void>;
title?: string;
description?: React.ReactNode | React.ReactNode[];
variant?: string;
btnLabel?: string;
icon?: React.ElementType;
}
const DeleteModal = ({
category,
category = '',
isOpen,
close,
onDeleteSubmit,
title,
description,
variant,
variant = 'default',
btnLabel,
icon,
}) => {
}: DeleteModalProps) => {
const intl = useIntl();
const modalTitle = title || intl.formatMessage(messages.title, { category });
@@ -62,28 +73,4 @@ const DeleteModal = ({
);
};
DeleteModal.defaultProps = {
category: '',
title: '',
description: '',
variant: 'default',
btnLabel: '',
icon: null,
};
DeleteModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
close: PropTypes.func.isRequired,
category: PropTypes.string,
onDeleteSubmit: PropTypes.func.isRequired,
title: PropTypes.string,
description: PropTypes.oneOfType([
PropTypes.element,
PropTypes.string,
]),
variant: PropTypes.string,
btnLabel: PropTypes.string,
icon: PropTypes.elementType,
};
export default DeleteModal;

View File

@@ -256,7 +256,7 @@ const LibraryAuthoringPage = ({
[ContentType.units]: intl.formatMessage(messages.unitsTab),
};
const visibleTabsToRender = visibleTabs.map((contentType) => (
<Tab eventKey={contentType} title={tabTitles[contentType]} />
<Tab key={contentType} eventKey={contentType} title={tabTitles[contentType]} />
));
return (

View File

@@ -1,6 +1,7 @@
import React, { useCallback, useContext } from 'react';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { Warning } from '@openedx/paragon/icons';
import { Icon } from '@openedx/paragon';
import { CalendarViewDay, School, Warning } from '@openedx/paragon/icons';
import { useSidebarContext } from '../common/context/SidebarContext';
import { useDeleteLibraryBlock, useLibraryBlockMetadata, useRestoreLibraryBlock } from '../data/apiHooks';
@@ -66,6 +67,22 @@ const ComponentDeleter = ({ usageKey, ...props }: Props) => {
return null;
}
const deleteText = intl.formatMessage(messages.deleteComponentConfirm, {
componentName: <b><BlockName usageKey={usageKey} /></b>,
message: (
<>
<div className="d-flex mt-2">
<Icon className="mr-2" src={School} />
{intl.formatMessage(messages.deleteComponentConfirmMsg1)}
</div>
<div className="d-flex mt-2">
<Icon className="mr-2" src={CalendarViewDay} />
{intl.formatMessage(messages.deleteComponentConfirmMsg2)}
</div>
</>
),
});
return (
<DeleteModal
isOpen
@@ -73,16 +90,7 @@ const ComponentDeleter = ({ usageKey, ...props }: Props) => {
variant="warning"
title={intl.formatMessage(messages.deleteComponentWarningTitle)}
icon={Warning}
description={(
<FormattedMessage
{...messages.deleteComponentConfirm}
values={{
componentName: (
<strong><BlockName usageKey={usageKey} /></strong>
),
}}
/>
)}
description={deleteText}
onDeleteSubmit={doDelete}
/>
);

View File

@@ -1,4 +1,4 @@
import { ReactNode, useCallback, useContext } from 'react';
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';
@@ -47,7 +47,7 @@ const ContainerDeleter = ({
</div>
</>
),
}) as ReactNode as string;
});
const deleteSuccess = intl.formatMessage(messages.deleteUnitSuccess);
const deleteError = intl.formatMessage(messages.deleteUnitFailed);
const undoDeleteError = messages.undoDeleteUnitToastFailed;

View File

@@ -68,9 +68,19 @@ const messages = defineMessages({
},
deleteComponentConfirm: {
id: 'course-authoring.library-authoring.component.delete-confirmation-text',
defaultMessage: 'Delete {componentName}? If this component has been used in a course, those copies won\'t be deleted, but they will no longer receive updates from the library.',
defaultMessage: 'Delete {componentName}? {message}',
description: 'Confirmation text to display before deleting a component',
},
deleteComponentConfirmMsg1: {
id: 'course-authoring.library-authoring.component.delete-confirmation-msg-1',
defaultMessage: 'If this component has been used in a course, those copies won\'t be deleted, but they will no longer receive updates from the library.',
description: 'First part of confirmation message to display before deleting a component',
},
deleteComponentConfirmMsg2: {
id: 'course-authoring.library-authoring.component.delete-confirmation-msg-2',
defaultMessage: 'If this component has been used in any units, it will also be deleted from those units.',
description: 'Second part of confirmation message to display before deleting a component',
},
deleteComponentCancelButton: {
id: 'course-authoring.library-authoring.component.cancel-delete-button',
defaultMessage: 'Cancel',

View File

@@ -642,13 +642,22 @@ export const useAddComponentsToContainer = (containerId?: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (componentIds: string[]) => {
if (containerId !== undefined) {
return api.addComponentsToContainer(containerId, componentIds);
// istanbul ignore if: this should never happen
if (!containerId) {
return undefined;
}
return undefined;
return api.addComponentsToContainer(containerId, componentIds);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.containerChildren(containerId!) });
// istanbul ignore if: this should never happen
if (!containerId) {
return;
}
// NOTE: We invalidate the library query here because we need to update the library's
// container list.
const libraryId = getLibraryId(containerId);
queryClient.invalidateQueries({ queryKey: libraryAuthoringQueryKeys.containerChildren(containerId) });
queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, libraryId) });
},
});
};