Files
frontend-app-authoring/src/library-authoring/containers/ContainerInfo.tsx
Jillian 2f6e510b09 Display Container Publish status and confirm before publish (#2186)
Updates the Container sidebar to display:

* A confirmation step before publishing the container.
* Text + a full hierarchy to better demonstrate what will be published when the container is published.
2025-08-20 13:22:30 -05:00

184 lines
5.4 KiB
TypeScript

import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import {
Button,
Stack,
Tab,
Tabs,
Dropdown,
Icon,
IconButton,
useToggle,
} from '@openedx/paragon';
import React, { useCallback } from 'react';
import { Link } from 'react-router-dom';
import { MoreVert } from '@openedx/paragon/icons';
import { ContainerType, getBlockType } from '@src/generic/key-utils';
import { useComponentPickerContext } from '../common/context/ComponentPickerContext';
import { useLibraryContext } from '../common/context/LibraryContext';
import {
type ContainerInfoTab,
CONTAINER_INFO_TABS,
isContainerInfoTab,
useSidebarContext,
} from '../common/context/SidebarContext';
import ContainerOrganize from './ContainerOrganize';
import ContainerUsage from './ContainerUsage';
import { useLibraryRoutes } from '../routes';
import { LibraryUnitBlocks } from '../units/LibraryUnitBlocks';
import { LibraryContainerChildren } from '../section-subsections/LibraryContainerChildren';
import messages from './messages';
import { useContainer } from '../data/apiHooks';
import ContainerDeleter from './ContainerDeleter';
import ContainerPublishStatus from './ContainerPublishStatus';
type ContainerPreviewProps = {
containerId: string,
};
const ContainerMenu = ({ containerId }: ContainerPreviewProps) => {
const intl = useIntl();
const [isConfirmingDelete, confirmDelete, cancelDelete] = useToggle(false);
return (
<>
<Dropdown id="container-info-dropdown">
<Dropdown.Toggle
id="container-info-menu-toggle"
as={IconButton}
src={MoreVert}
iconAs={Icon}
variant="primary"
alt={intl.formatMessage(messages.containerCardMenuAlt)}
data-testid="container-info-menu-toggle"
/>
<Dropdown.Menu>
<Dropdown.Item onClick={confirmDelete}>
<FormattedMessage {...messages.menuDeleteContainer} />
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<ContainerDeleter
isOpen={isConfirmingDelete}
close={cancelDelete}
containerId={containerId}
/>
</>
);
};
const ContainerPreview = ({ containerId } : ContainerPreviewProps) => {
const containerType = getBlockType(containerId);
if (containerType === ContainerType.Unit) {
return <LibraryUnitBlocks unitId={containerId} readOnly />;
}
return <LibraryContainerChildren containerKey={containerId} readOnly />;
};
const ContainerInfo = () => {
const intl = useIntl();
const { libraryId } = useLibraryContext();
const { componentPickerMode } = useComponentPickerContext();
const {
defaultTab,
hiddenTabs,
sidebarTab,
setSidebarTab,
sidebarItemInfo,
resetSidebarAction,
} = useSidebarContext();
const { insideUnit, insideSubsection, insideSection } = useLibraryRoutes();
const containerId = sidebarItemInfo?.id;
const containerType = containerId ? getBlockType(containerId) : undefined;
const { data: container } = useContainer(containerId);
const defaultContainerTab = defaultTab.container;
const tab: ContainerInfoTab = (
sidebarTab && isContainerInfoTab(sidebarTab)
) ? sidebarTab : defaultContainerTab;
const showOpenButton = !componentPickerMode && !(
insideUnit || insideSubsection || insideSection
);
/* istanbul ignore next */
const handleTabChange = (newTab: ContainerInfoTab) => {
resetSidebarAction();
setSidebarTab(newTab);
};
const renderTab = useCallback((infoTab: ContainerInfoTab, title: string, component?: React.ReactNode) => {
if (hiddenTabs.includes(infoTab)) {
// For some reason, returning anything other than empty list breaks the tab style
return [];
}
return (
<Tab eventKey={infoTab} title={title}>
{component}
</Tab>
);
}, [hiddenTabs, defaultContainerTab, containerId]);
if (!container || !containerId || !containerType) {
return null;
}
return (
<Stack>
<div className="d-flex flex-wrap">
{showOpenButton && (
<Button
variant="outline-primary"
className="m-1 text-nowrap flex-grow-1"
as={Link}
to={`/library/${libraryId}/${containerType}/${containerId}`}
>
{intl.formatMessage(messages.openButton)}
</Button>
)}
{!showOpenButton && !componentPickerMode && (
<ContainerPublishStatus
containerId={containerId}
/>
)}
{showOpenButton && (
<ContainerMenu containerId={containerId} />
)}
</div>
<Tabs
variant="tabs"
className="my-3 d-flex justify-content-around"
defaultActiveKey={defaultContainerTab}
activeKey={tab}
onSelect={handleTabChange}
>
{renderTab(
CONTAINER_INFO_TABS.Preview,
intl.formatMessage(messages.previewTabTitle),
<ContainerPreview containerId={containerId} />,
)}
{renderTab(
CONTAINER_INFO_TABS.Manage,
intl.formatMessage(messages.manageTabTitle),
<ContainerOrganize />,
)}
{renderTab(
CONTAINER_INFO_TABS.Usage,
intl.formatMessage(messages.usageTabTitle),
<ContainerUsage />,
)}
{renderTab(
CONTAINER_INFO_TABS.Settings,
intl.formatMessage(messages.settingsTabTitle),
// TODO: container settings component
)}
</Tabs>
</Stack>
);
};
export default ContainerInfo;