Skip to content

Commit 56d3eed

Browse files
authored
feat: Adding loading and done states to the publish library button [FC-0097] (#2237)
Adds loading and done states to the publish library button.
1 parent c5de944 commit 56d3eed

File tree

4 files changed

+85
-19
lines changed

4 files changed

+85
-19
lines changed

src/library-authoring/generic/status-widget/index.tsx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ import {
44
FormattedTime,
55
useIntl,
66
} from '@edx/frontend-platform/i18n';
7-
import { Button, Container, Stack } from '@openedx/paragon';
7+
import {
8+
Button,
9+
Container,
10+
Icon,
11+
Stack,
12+
StatefulButton,
13+
} from '@openedx/paragon';
14+
import { SpinnerSimple } from '@openedx/paragon/icons';
815
import classNames from 'classnames';
9-
1016
import messages from './messages';
1117

1218
const CustomFormattedDate = ({ date }: { date: string }) => (
@@ -84,8 +90,9 @@ type StatusWidgedProps = {
8490
created: string | null;
8591
publishedBy: string | null;
8692
numBlocks?: number;
87-
onCommit?: () => void;
93+
onCommit?: () => Promise<void>;
8894
onCommitLabel?: string;
95+
onCommitStatus?: 'pending' | 'error' | 'idle' | 'success';
8996
onRevert?: () => void;
9097
};
9198

@@ -116,6 +123,7 @@ const StatusWidget = ({
116123
numBlocks,
117124
onCommit,
118125
onCommitLabel,
126+
onCommitStatus,
119127
onRevert,
120128
}: StatusWidgedProps) => {
121129
const intl = useIntl();
@@ -125,6 +133,7 @@ const StatusWidget = ({
125133
let statusMessage: string;
126134
let extraStatusMessage: string | undefined;
127135
let bodyMessage: React.ReactNode | undefined;
136+
let publishButtonState: 'pending' | 'complete' | 'default' = 'default';
128137

129138
if (!lastPublished) {
130139
// Entity is never published (new)
@@ -167,6 +176,12 @@ const StatusWidget = ({
167176
}
168177
}
169178

179+
if (onCommitStatus === 'pending') {
180+
publishButtonState = 'pending';
181+
} else if (isPublished) {
182+
publishButtonState = 'complete';
183+
}
184+
170185
return (
171186
<Stack>
172187
<Container className={classNames('status-widget', {
@@ -189,9 +204,21 @@ const StatusWidget = ({
189204
{bodyMessage}
190205
</span>
191206
{onCommit && (
192-
<Button disabled={isPublished} onClick={onCommit}>
193-
{onCommitLabel || intl.formatMessage(messages.publishButtonLabel)}
194-
</Button>
207+
<StatefulButton
208+
variant="primary"
209+
state={publishButtonState}
210+
labels={{
211+
default: onCommitLabel || intl.formatMessage(messages.publishButtonLabel),
212+
complete: intl.formatMessage(messages.publishedStatusLabel),
213+
pending: intl.formatMessage(messages.publishingButtonLabelState),
214+
}}
215+
icons={{
216+
complete: null,
217+
pending: <Icon src={SpinnerSimple} className="icon-spin" />,
218+
}}
219+
disabledStates={['pending', 'complete']}
220+
onClick={onCommit}
221+
/>
195222
)}
196223
{onRevert && (
197224
<div className="d-flex justify-content-end">

src/library-authoring/generic/status-widget/messages.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ const messages = defineMessages({
4646
defaultMessage: 'Publish',
4747
description: 'Label of publish button for an entity.',
4848
},
49+
publishingButtonLabelState: {
50+
id: 'course-authoring.library-authoring.generic.status-widget.publishing-button',
51+
defaultMessage: 'Publishing',
52+
description: 'Label of publish button for an entity in the publishing state.',
53+
},
4954
discardChangesButtonLabel: {
5055
id: 'course-authoring.library-authoring.generic.status-widget.discard-button',
5156
defaultMessage: 'Discard Changes',

src/library-authoring/library-info/LibraryInfo.test.tsx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import {
66
screen,
77
waitFor,
88
initializeMocks,
9-
} from '../../testUtils';
9+
} from '@src/testUtils';
1010
import { mockContentLibrary } from '../data/api.mocks';
1111
import { getCommitLibraryChangesUrl } from '../data/api';
1212
import { LibraryProvider } from '../common/context/LibraryContext';
1313
import LibraryInfo from './LibraryInfo';
14+
import * as apiHooks from '../data/apiHooks';
1415

1516
const {
1617
libraryId: mockLibraryId,
@@ -105,7 +106,10 @@ describe('<LibraryInfo />', () => {
105106

106107
expect(await screen.findByText(libraryData.org)).toBeInTheDocument();
107108

108-
expect(screen.getByText('Published')).toBeInTheDocument();
109+
// First 'Published' from the state
110+
expect(screen.getAllByText('Published')[0]).toBeInTheDocument();
111+
// Second 'Published' from the published button
112+
expect(screen.getAllByText('Published')[1]).toBeInTheDocument();
109113
expect(screen.getByText('July 26, 2024')).toBeInTheDocument();
110114
expect(screen.getByText('staff')).toBeInTheDocument();
111115
});
@@ -115,7 +119,10 @@ describe('<LibraryInfo />', () => {
115119

116120
expect(await screen.findByText(libraryData.org)).toBeInTheDocument();
117121

118-
expect(screen.getByText('Published')).toBeInTheDocument();
122+
// First 'Published' from the state
123+
expect(screen.getAllByText('Published')[0]).toBeInTheDocument();
124+
// Second 'Published' from the published button
125+
expect(screen.getAllByText('Published')[1]).toBeInTheDocument();
119126
expect(screen.getByText('July 26, 2024')).toBeInTheDocument();
120127
expect(screen.queryByText('staff')).not.toBeInTheDocument();
121128
});
@@ -136,6 +143,31 @@ describe('<LibraryInfo />', () => {
136143
});
137144
});
138145

146+
it('should publish library 2', async () => {
147+
const useCommitLibraryChangesSpy = jest
148+
.spyOn(apiHooks, 'useCommitLibraryChanges')
149+
.mockReturnValue(
150+
// @ts-ignore
151+
{
152+
mutate: jest.fn(),
153+
mutateAsync: jest.fn(),
154+
status: 'pending',
155+
},
156+
);
157+
158+
render();
159+
160+
expect(await screen.findByText(libraryData.org)).toBeInTheDocument();
161+
162+
const publishButton = screen.getByRole('button', { name: /publish/i });
163+
fireEvent.click(publishButton);
164+
165+
await waitFor(() => {
166+
expect(screen.getByText(/publishing/i)).toBeInTheDocument();
167+
});
168+
useCommitLibraryChangesSpy.mockRestore();
169+
});
170+
139171
it('should show error on publish library', async () => {
140172
const url = getCommitLibraryChangesUrl(libraryData.id);
141173
axiosMock.onPost(url).reply(500);

src/library-authoring/library-info/LibraryPublishStatus.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { useCallback, useContext } from 'react';
22
import { useIntl } from '@edx/frontend-platform/i18n';
3-
43
import { useToggle } from '@openedx/paragon';
5-
import { ToastContext } from '../../generic/toast-context';
4+
5+
import { ToastContext } from '@src/generic/toast-context';
6+
import DeleteModal from '@src/generic/delete-modal/DeleteModal';
7+
68
import { useLibraryContext } from '../common/context/LibraryContext';
79
import { useCommitLibraryChanges, useRevertLibraryChanges } from '../data/apiHooks';
810
import StatusWidget from '../generic/status-widget';
911
import messages from './messages';
10-
import DeleteModal from '../../generic/delete-modal/DeleteModal';
1112

1213
const LibraryPublishStatus = () => {
1314
const intl = useIntl();
@@ -18,14 +19,14 @@ const LibraryPublishStatus = () => {
1819
const revertLibraryChanges = useRevertLibraryChanges();
1920
const { showToast } = useContext(ToastContext);
2021

21-
const commit = useCallback(() => {
22+
const commit = useCallback(async () => {
2223
if (libraryData) {
23-
commitLibraryChanges.mutateAsync(libraryData.id)
24-
.then(() => {
25-
showToast(intl.formatMessage(messages.publishSuccessMsg));
26-
}).catch(() => {
27-
showToast(intl.formatMessage(messages.publishErrorMsg));
28-
});
24+
try {
25+
await commitLibraryChanges.mutateAsync(libraryData.id);
26+
showToast(intl.formatMessage(messages.publishSuccessMsg));
27+
} catch (e) {
28+
showToast(intl.formatMessage(messages.publishErrorMsg));
29+
}
2930
}
3031
}, [libraryData]);
3132

@@ -51,6 +52,7 @@ const LibraryPublishStatus = () => {
5152
<StatusWidget
5253
{...libraryData}
5354
onCommit={!readOnly ? commit : undefined}
55+
onCommitStatus={commitLibraryChanges.status}
5456
onCommitLabel={intl.formatMessage(messages.publishLibraryButtonLabel)}
5557
onRevert={!readOnly ? openConfirmModal : undefined}
5658
/>

0 commit comments

Comments
 (0)