@strapi/admin 4.10.4 → 4.11.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/admin/src/content-manager/components/DynamicTable/BulkActionsBar/index.js +307 -0
  2. package/admin/src/content-manager/components/DynamicTable/index.js +20 -4
  3. package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +3 -2
  4. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/index.js +0 -1
  5. package/admin/src/content-manager/pages/ListView/index.js +118 -2
  6. package/admin/src/content-manager/utils/index.js +2 -0
  7. package/admin/src/content-manager/{components/EditViewDataManagerProvider/utils → utils}/schema.js +1 -1
  8. package/admin/src/injectionZones.js +6 -1
  9. package/admin/src/translations/en.json +5 -1
  10. package/build/{1387.437eb420.chunk.js → 1387.84b454d3.chunk.js} +1 -1
  11. package/build/{1657.85034334.chunk.js → 1657.45231968.chunk.js} +52 -52
  12. package/build/3081.bcf9a12f.chunk.js +108 -0
  13. package/build/3562.57745ccd.chunk.js +50 -0
  14. package/build/462.8fff7f3b.chunk.js +71 -0
  15. package/build/{4628.9cbb6df5.chunk.js → 4628.20631dd1.chunk.js} +1 -1
  16. package/build/5542.b8240e3f.chunk.js +70 -0
  17. package/build/{5563.44e0ef15.chunk.js → 5563.986609ed.chunk.js} +22 -22
  18. package/build/6404.68405699.chunk.js +100 -0
  19. package/build/6970.5159b068.chunk.js +1 -0
  20. package/build/{7259.3ed2b60e.chunk.js → 7259.7a48aa2f.chunk.js} +1 -1
  21. package/build/7725.1633e06f.chunk.js +213 -0
  22. package/build/{Admin-authenticatedApp.dbb99d34.chunk.js → Admin-authenticatedApp.27a2329f.chunk.js} +2 -2
  23. package/build/Admin_homePage.9f7c0bb1.chunk.js +73 -0
  24. package/build/Admin_marketplace.5f7b89e5.chunk.js +55 -0
  25. package/build/Admin_pluginsPage.9e6fa51c.chunk.js +6 -0
  26. package/build/{Admin_profilePage.1687246a.chunk.js → Admin_profilePage.1b337b73.chunk.js} +1 -1
  27. package/build/Admin_settingsPage.9d0419bc.chunk.js +79 -0
  28. package/build/{Upload_ConfigureTheView.aa64ed9a.chunk.js → Upload_ConfigureTheView.d429a7fc.chunk.js} +1 -1
  29. package/build/{admin-app.d4fd9379.chunk.js → admin-app.8e1f56bc.chunk.js} +5 -5
  30. package/build/{admin-edit-roles-page.6fe9de49.chunk.js → admin-edit-roles-page.d0c9497b.chunk.js} +41 -43
  31. package/build/{admin-edit-users.49363035.chunk.js → admin-edit-users.2aae89f5.chunk.js} +2 -2
  32. package/build/admin-roles-list.c759daa3.chunk.js +31 -0
  33. package/build/{admin-users.16bb6e77.chunk.js → admin-users.04a823ca.chunk.js} +15 -15
  34. package/build/audit-logs-settings-page.45cb4fb5.chunk.js +76 -0
  35. package/build/content-manager.cf467ecc.chunk.js +1123 -0
  36. package/build/content-type-builder-list-view.5ff685ec.chunk.js +214 -0
  37. package/build/{content-type-builder.51c64fe5.chunk.js → content-type-builder.4737a30c.chunk.js} +4 -4
  38. package/build/email-settings-page.dc07d518.chunk.js +10 -0
  39. package/build/{en-json.1f6af924.chunk.js → en-json.7edb00f6.chunk.js} +1 -1
  40. package/build/i18n-settings-page.8219dd99.chunk.js +60 -0
  41. package/build/i18n-translation-en-json.1ec7becf.chunk.js +1 -0
  42. package/build/index.html +1 -1
  43. package/build/main.14dca275.js +2592 -0
  44. package/build/{review-workflows-settings.26409ce4.chunk.js → review-workflows-settings.6a662ebd.chunk.js} +2 -2
  45. package/build/{runtime~main.a096f3d0.js → runtime~main.1074f2bb.js} +2 -2
  46. package/build/{sso-settings-page.c9d7c8df.chunk.js → sso-settings-page.f44d95d8.chunk.js} +1 -1
  47. package/build/upload-settings.dd2d987c.chunk.js +13 -0
  48. package/build/upload.c8479232.chunk.js +33 -0
  49. package/build/users-advanced-settings-page.c36cfd59.chunk.js +8 -0
  50. package/build/users-email-settings-page.2716ce8e.chunk.js +23 -0
  51. package/build/users-providers-settings-page.0d6304a5.chunk.js +28 -0
  52. package/build/webhook-edit-page.f4db86f3.chunk.js +75 -0
  53. package/build/webhook-list-page.30d73114.chunk.js +71 -0
  54. package/ee/admin/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn.js +6 -1
  55. package/package.json +11 -11
  56. package/admin/src/content-manager/components/DynamicTable/ConfirmDialogDeleteAll/index.js +0 -73
  57. package/build/3081.c2cdfac8.chunk.js +0 -108
  58. package/build/3816.2b58bc1a.chunk.js +0 -214
  59. package/build/462.a073ff1f.chunk.js +0 -71
  60. package/build/5542.5328b662.chunk.js +0 -71
  61. package/build/617.ce23dea8.chunk.js +0 -155
  62. package/build/6858.85d76858.chunk.js +0 -50
  63. package/build/6970.e8422af3.chunk.js +0 -1
  64. package/build/Admin_homePage.e15dcf28.chunk.js +0 -73
  65. package/build/Admin_marketplace.87f51b29.chunk.js +0 -55
  66. package/build/Admin_pluginsPage.7df6b5a9.chunk.js +0 -6
  67. package/build/Admin_settingsPage.20884a40.chunk.js +0 -79
  68. package/build/admin-roles-list.a323aa9f.chunk.js +0 -31
  69. package/build/audit-logs-settings-page.48b921f9.chunk.js +0 -129
  70. package/build/content-manager.a837bfcd.chunk.js +0 -1123
  71. package/build/content-type-builder-list-view.1e821eb9.chunk.js +0 -215
  72. package/build/email-settings-page.5bc22e5a.chunk.js +0 -11
  73. package/build/i18n-settings-page.1581894e.chunk.js +0 -114
  74. package/build/i18n-translation-en-json.60af6722.chunk.js +0 -1
  75. package/build/main.31bdf27e.js +0 -2679
  76. package/build/upload-settings.4d962053.chunk.js +0 -14
  77. package/build/upload.4dcd513f.chunk.js +0 -34
  78. package/build/users-advanced-settings-page.dd2a4954.chunk.js +0 -9
  79. package/build/users-email-settings-page.510648ab.chunk.js +0 -24
  80. package/build/users-providers-settings-page.0ee7d54f.chunk.js +0 -29
  81. package/build/webhook-edit-page.36c9f20d.chunk.js +0 -128
  82. package/build/webhook-list-page.0983d83f.chunk.js +0 -71
@@ -0,0 +1,307 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Button, Dialog, DialogBody, DialogFooter, Flex, Typography } from '@strapi/design-system';
4
+ import { Check, ExclamationMarkCircle, Trash } from '@strapi/icons';
5
+ import { useIntl } from 'react-intl';
6
+ import { useSelector } from 'react-redux';
7
+ import { useTracking } from '@strapi/helper-plugin';
8
+ import { getTrad } from '../../../utils';
9
+ import InjectionZoneList from '../../InjectionZoneList';
10
+ import { listViewDomain } from '../../../pages/ListView/selectors';
11
+
12
+ const ConfirmBulkActionDialog = ({ onToggleDialog, isOpen, dialogBody, endAction }) => {
13
+ const { formatMessage } = useIntl();
14
+
15
+ return (
16
+ <Dialog
17
+ onClose={onToggleDialog}
18
+ title={formatMessage({
19
+ id: 'app.components.ConfirmDialog.title',
20
+ defaultMessage: 'Confirmation',
21
+ })}
22
+ labelledBy="confirmation"
23
+ describedBy="confirm-description"
24
+ isOpen={isOpen}
25
+ >
26
+ <DialogBody icon={<ExclamationMarkCircle />}>
27
+ <Flex direction="column" alignItems="stretch" gap={2}>
28
+ {dialogBody}
29
+ </Flex>
30
+ </DialogBody>
31
+ <DialogFooter
32
+ startAction={
33
+ <Button onClick={onToggleDialog} variant="tertiary">
34
+ {formatMessage({
35
+ id: 'app.components.Button.cancel',
36
+ defaultMessage: 'Cancel',
37
+ })}
38
+ </Button>
39
+ }
40
+ endAction={endAction}
41
+ />
42
+ </Dialog>
43
+ );
44
+ };
45
+
46
+ ConfirmBulkActionDialog.propTypes = {
47
+ isOpen: PropTypes.bool.isRequired,
48
+ onToggleDialog: PropTypes.func.isRequired,
49
+ dialogBody: PropTypes.node.isRequired,
50
+ endAction: PropTypes.node.isRequired,
51
+ };
52
+
53
+ const confirmDialogsPropTypes = {
54
+ isConfirmButtonLoading: PropTypes.bool.isRequired,
55
+ isOpen: PropTypes.bool.isRequired,
56
+ onConfirm: PropTypes.func.isRequired,
57
+ onToggleDialog: PropTypes.func.isRequired,
58
+ };
59
+
60
+ const ConfirmDialogPublishAll = ({ isOpen, onToggleDialog, isConfirmButtonLoading, onConfirm }) => {
61
+ const { formatMessage } = useIntl();
62
+
63
+ return (
64
+ <ConfirmBulkActionDialog
65
+ isOpen={isOpen}
66
+ onToggleDialog={onToggleDialog}
67
+ dialogBody={
68
+ <>
69
+ <Typography id="confirm-description" textAlign="center">
70
+ {formatMessage({
71
+ id: getTrad('popUpWarning.bodyMessage.contentType.publish.all'),
72
+ defaultMessage: 'Are you sure you want to publish these entries?',
73
+ })}
74
+ </Typography>
75
+ <InjectionZoneList area="contentManager.listView.publishModalAdditionalInfos" />
76
+ </>
77
+ }
78
+ endAction={
79
+ <Button
80
+ onClick={onConfirm}
81
+ variant="secondary"
82
+ startIcon={<Check />}
83
+ loading={isConfirmButtonLoading}
84
+ >
85
+ {formatMessage({
86
+ id: 'app.utils.publish',
87
+ defaultMessage: 'Publish',
88
+ })}
89
+ </Button>
90
+ }
91
+ />
92
+ );
93
+ };
94
+
95
+ ConfirmDialogPublishAll.propTypes = confirmDialogsPropTypes;
96
+
97
+ const ConfirmDialogUnpublishAll = ({
98
+ isOpen,
99
+ onToggleDialog,
100
+ isConfirmButtonLoading,
101
+ onConfirm,
102
+ }) => {
103
+ const { formatMessage } = useIntl();
104
+
105
+ return (
106
+ <ConfirmBulkActionDialog
107
+ isOpen={isOpen}
108
+ onToggleDialog={onToggleDialog}
109
+ dialogBody={
110
+ <>
111
+ <Typography id="confirm-description" textAlign="center">
112
+ {formatMessage({
113
+ id: getTrad('popUpWarning.bodyMessage.contentType.unpublish.all'),
114
+ defaultMessage: 'Are you sure you want to unpublish these entries?',
115
+ })}
116
+ </Typography>
117
+ <InjectionZoneList area="contentManager.listView.unpublishModalAdditionalInfos" />
118
+ </>
119
+ }
120
+ endAction={
121
+ <Button
122
+ onClick={onConfirm}
123
+ variant="secondary"
124
+ startIcon={<Check />}
125
+ loading={isConfirmButtonLoading}
126
+ >
127
+ {formatMessage({
128
+ id: 'app.utils.unpublish',
129
+ defaultMessage: 'Unpublish',
130
+ })}
131
+ </Button>
132
+ }
133
+ />
134
+ );
135
+ };
136
+
137
+ ConfirmDialogUnpublishAll.propTypes = confirmDialogsPropTypes;
138
+
139
+ const ConfirmDialogDeleteAll = ({ isOpen, onToggleDialog, isConfirmButtonLoading, onConfirm }) => {
140
+ const { formatMessage } = useIntl();
141
+
142
+ return (
143
+ <ConfirmBulkActionDialog
144
+ isOpen={isOpen}
145
+ onToggleDialog={onToggleDialog}
146
+ dialogBody={
147
+ <>
148
+ <Typography id="confirm-description" textAlign="center">
149
+ {formatMessage({
150
+ id: getTrad('popUpWarning.bodyMessage.contentType.delete.all'),
151
+ defaultMessage: 'Are you sure you want to delete these entries?',
152
+ })}
153
+ </Typography>
154
+ <InjectionZoneList area="contentManager.listView.deleteModalAdditionalInfos" />
155
+ </>
156
+ }
157
+ endAction={
158
+ <Button
159
+ onClick={onConfirm}
160
+ variant="danger-light"
161
+ startIcon={<Trash />}
162
+ id="confirm-delete"
163
+ loading={isConfirmButtonLoading}
164
+ >
165
+ {formatMessage({
166
+ id: 'app.components.Button.confirm',
167
+ defaultMessage: 'Confirm',
168
+ })}
169
+ </Button>
170
+ }
171
+ />
172
+ );
173
+ };
174
+
175
+ ConfirmDialogDeleteAll.propTypes = confirmDialogsPropTypes;
176
+
177
+ const BulkActionsBar = ({
178
+ showPublish,
179
+ showDelete,
180
+ onConfirmDeleteAll,
181
+ onConfirmPublishAll,
182
+ onConfirmUnpublishAll,
183
+ selectedEntries,
184
+ clearSelectedEntries,
185
+ }) => {
186
+ const { formatMessage } = useIntl();
187
+ const { trackUsage } = useTracking();
188
+ const { data } = useSelector(listViewDomain());
189
+
190
+ const [isConfirmButtonLoading, setIsConfirmButtonLoading] = useState(false);
191
+ const [dialogToOpen, setDialogToOpen] = useState(null);
192
+
193
+ // Filters for Bulk actions
194
+ const selectedEntriesObjects = data.filter((entry) => selectedEntries.includes(entry.id));
195
+ const publishButtonIsShown =
196
+ showPublish && selectedEntriesObjects.some((entry) => !entry.publishedAt);
197
+ const unpublishButtonIsShown =
198
+ showPublish && selectedEntriesObjects.some((entry) => entry.publishedAt);
199
+
200
+ const toggleDeleteModal = () => {
201
+ if (dialogToOpen === 'delete') {
202
+ setDialogToOpen(null);
203
+ } else {
204
+ setDialogToOpen('delete');
205
+ trackUsage('willBulkDeleteEntries');
206
+ }
207
+ };
208
+
209
+ const togglePublishModal = () => {
210
+ if (dialogToOpen === 'publish') {
211
+ setDialogToOpen(null);
212
+ } else {
213
+ setDialogToOpen('publish');
214
+ trackUsage('willBulkPublishEntries');
215
+ }
216
+ };
217
+
218
+ const toggleUnpublishModal = () => {
219
+ if (dialogToOpen === 'unpublish') {
220
+ setDialogToOpen(null);
221
+ } else {
222
+ setDialogToOpen('unpublish');
223
+ trackUsage('willBulkUnpublishEntries');
224
+ }
225
+ };
226
+
227
+ const handleBulkAction = async (confirmAction, toggleModal) => {
228
+ try {
229
+ setIsConfirmButtonLoading(true);
230
+ await confirmAction(selectedEntries);
231
+ setIsConfirmButtonLoading(false);
232
+ toggleModal();
233
+ clearSelectedEntries();
234
+ } catch (error) {
235
+ setIsConfirmButtonLoading(false);
236
+ toggleModal();
237
+ }
238
+ };
239
+
240
+ const handleBulkDelete = () => handleBulkAction(onConfirmDeleteAll, toggleDeleteModal);
241
+ const handleBulkPublish = () => handleBulkAction(onConfirmPublishAll, togglePublishModal);
242
+ const handleBulkUnpublish = () => handleBulkAction(onConfirmUnpublishAll, toggleUnpublishModal);
243
+
244
+ return (
245
+ <>
246
+ {publishButtonIsShown && (
247
+ <>
248
+ <Button variant="tertiary" onClick={togglePublishModal}>
249
+ {formatMessage({ id: 'app.utils.publish', defaultMessage: 'Publish' })}
250
+ </Button>
251
+ <ConfirmDialogPublishAll
252
+ isOpen={dialogToOpen === 'publish'}
253
+ onToggleDialog={togglePublishModal}
254
+ isConfirmButtonLoading={isConfirmButtonLoading}
255
+ onConfirm={handleBulkPublish}
256
+ />
257
+ </>
258
+ )}
259
+ {unpublishButtonIsShown && (
260
+ <>
261
+ <Button variant="tertiary" onClick={toggleUnpublishModal}>
262
+ {formatMessage({ id: 'app.utils.unpublish', defaultMessage: 'Unpublish' })}
263
+ </Button>
264
+ <ConfirmDialogUnpublishAll
265
+ isOpen={dialogToOpen === 'unpublish'}
266
+ onToggleDialog={toggleUnpublishModal}
267
+ isConfirmButtonLoading={isConfirmButtonLoading}
268
+ onConfirm={handleBulkUnpublish}
269
+ />
270
+ </>
271
+ )}
272
+ {showDelete && (
273
+ <>
274
+ <Button variant="danger-light" onClick={toggleDeleteModal}>
275
+ {formatMessage({ id: 'global.delete', defaultMessage: 'Delete' })}
276
+ </Button>
277
+ <ConfirmDialogDeleteAll
278
+ isOpen={dialogToOpen === 'delete'}
279
+ onToggleDialog={toggleDeleteModal}
280
+ isConfirmButtonLoading={isConfirmButtonLoading}
281
+ onConfirm={handleBulkDelete}
282
+ />
283
+ </>
284
+ )}
285
+ </>
286
+ );
287
+ };
288
+
289
+ BulkActionsBar.defaultProps = {
290
+ showPublish: false,
291
+ showDelete: false,
292
+ onConfirmDeleteAll() {},
293
+ onConfirmPublishAll() {},
294
+ onConfirmUnpublishAll() {},
295
+ };
296
+
297
+ BulkActionsBar.propTypes = {
298
+ showPublish: PropTypes.bool,
299
+ showDelete: PropTypes.bool,
300
+ onConfirmDeleteAll: PropTypes.func,
301
+ onConfirmPublishAll: PropTypes.func,
302
+ onConfirmUnpublishAll: PropTypes.func,
303
+ selectedEntries: PropTypes.array.isRequired,
304
+ clearSelectedEntries: PropTypes.func.isRequired,
305
+ };
306
+
307
+ export default BulkActionsBar;
@@ -9,19 +9,22 @@ import { INJECT_COLUMN_IN_TABLE } from '../../../exposedHooks';
9
9
  import { selectDisplayedHeaders } from '../../pages/ListView/selectors';
10
10
  import { getTrad } from '../../utils';
11
11
  import TableRows from './TableRows';
12
- import ConfirmDialogDeleteAll from './ConfirmDialogDeleteAll';
13
12
  import ConfirmDialogDelete from './ConfirmDialogDelete';
14
13
  import { PublicationState } from './CellContent/PublicationState/PublicationState';
14
+ import BulkActionsBar from './BulkActionsBar';
15
15
 
16
16
  const DynamicTable = ({
17
17
  canCreate,
18
18
  canDelete,
19
+ canPublish,
19
20
  contentTypeName,
20
21
  action,
21
22
  isBulkable,
22
23
  isLoading,
23
24
  onConfirmDelete,
24
25
  onConfirmDeleteAll,
26
+ onConfirmPublishAll,
27
+ onConfirmUnpublishAll,
25
28
  layout,
26
29
  rows,
27
30
  }) => {
@@ -89,17 +92,27 @@ const DynamicTable = ({
89
92
 
90
93
  return (
91
94
  <Table
92
- components={{ ConfirmDialogDelete, ConfirmDialogDeleteAll }}
95
+ components={{ ConfirmDialogDelete }}
93
96
  contentType={contentTypeName}
94
97
  action={action}
95
98
  isLoading={isLoading}
96
99
  headers={tableHeaders}
97
100
  onConfirmDelete={onConfirmDelete}
98
- onConfirmDeleteAll={onConfirmDeleteAll}
99
101
  onOpenDeleteAllModalTrackedEvent="willBulkDeleteEntries"
100
102
  rows={rows}
101
103
  withBulkActions
102
- withMainAction={canDelete && isBulkable}
104
+ withMainAction={(canDelete || canPublish) && isBulkable}
105
+ renderBulkActionsBar={({ selectedEntries, clearSelectedEntries }) => (
106
+ <BulkActionsBar
107
+ showPublish={canPublish && hasDraftAndPublish}
108
+ showDelete={canDelete}
109
+ onConfirmDeleteAll={onConfirmDeleteAll}
110
+ onConfirmPublishAll={onConfirmPublishAll}
111
+ onConfirmUnpublishAll={onConfirmUnpublishAll}
112
+ selectedEntries={selectedEntries}
113
+ clearSelectedEntries={clearSelectedEntries}
114
+ />
115
+ )}
103
116
  >
104
117
  <TableRows
105
118
  canCreate={canCreate}
@@ -121,6 +134,7 @@ DynamicTable.defaultProps = {
121
134
  DynamicTable.propTypes = {
122
135
  canCreate: PropTypes.bool.isRequired,
123
136
  canDelete: PropTypes.bool.isRequired,
137
+ canPublish: PropTypes.bool.isRequired,
124
138
  contentTypeName: PropTypes.string.isRequired,
125
139
  action: PropTypes.node,
126
140
  isBulkable: PropTypes.bool.isRequired,
@@ -139,6 +153,8 @@ DynamicTable.propTypes = {
139
153
  }).isRequired,
140
154
  onConfirmDelete: PropTypes.func.isRequired,
141
155
  onConfirmDeleteAll: PropTypes.func.isRequired,
156
+ onConfirmPublishAll: PropTypes.func.isRequired,
157
+ onConfirmUnpublishAll: PropTypes.func.isRequired,
142
158
  rows: PropTypes.array.isRequired,
143
159
  };
144
160
 
@@ -21,12 +21,13 @@ import {
21
21
  getAPIInnerErrors,
22
22
  } from '@strapi/helper-plugin';
23
23
 
24
- import { getTrad } from '../../utils';
24
+ import { createYupSchema, getTrad } from '../../utils';
25
25
 
26
26
  import selectCrudReducer from '../../sharedReducers/crudReducer/selectors';
27
27
 
28
28
  import reducer, { initialState } from './reducer';
29
- import { cleanData, createYupSchema } from './utils';
29
+ import { cleanData } from './utils';
30
+
30
31
  import { clearSetModifiedDataOnly } from '../../sharedReducers/crudReducer/actions';
31
32
  import { usePrev } from '../../hooks';
32
33
 
@@ -1,4 +1,3 @@
1
1
  export { default as moveFields } from './moveFields';
2
2
  export { default as cleanData } from './cleanData';
3
- export { default as createYupSchema } from './schema';
4
3
  export { findAllAndReplace } from './findAllAndReplace';
@@ -21,6 +21,7 @@ import {
21
21
  useTracking,
22
22
  Link,
23
23
  useAPIErrorHandler,
24
+ getYupInnerErrors,
24
25
  } from '@strapi/helper-plugin';
25
26
 
26
27
  import {
@@ -35,6 +36,7 @@ import {
35
36
  } from '@strapi/design-system';
36
37
 
37
38
  import { ArrowLeft, Plus, Cog } from '@strapi/icons';
39
+ import { useMutation } from 'react-query';
38
40
 
39
41
  import DynamicTable from '../../components/DynamicTable';
40
42
  import AttributeFilter from '../../components/AttributeFilter';
@@ -42,7 +44,7 @@ import { InjectionZone } from '../../../shared/components';
42
44
 
43
45
  import permissions from '../../../permissions';
44
46
 
45
- import { getRequestUrl, getTrad } from '../../utils';
47
+ import { createYupSchema, getRequestUrl, getTrad } from '../../utils';
46
48
 
47
49
  import FieldPicker from './FieldPicker';
48
50
  import PaginationFooter from './PaginationFooter';
@@ -64,6 +66,7 @@ function ListView({
64
66
  canCreate,
65
67
  canDelete,
66
68
  canRead,
69
+ canPublish,
67
70
  data,
68
71
  getData,
69
72
  getDataSucceeded,
@@ -100,6 +103,50 @@ function ListView({
100
103
  const fetchClient = useFetchClient();
101
104
  const { post, del } = fetchClient;
102
105
 
106
+ const bulkPublishMutation = useMutation(
107
+ (data) =>
108
+ post(`/content-manager/collection-types/${contentType.uid}/actions/bulkPublish`, data),
109
+ {
110
+ onSuccess() {
111
+ toggleNotification({
112
+ type: 'success',
113
+ message: { id: 'content-manager.success.record.publish', defaultMessage: 'Published' },
114
+ });
115
+
116
+ fetchData(`/content-manager/collection-types/${slug}${params}`);
117
+ },
118
+ onError(error) {
119
+ toggleNotification({
120
+ type: 'warning',
121
+ message: formatAPIError(error),
122
+ });
123
+ },
124
+ }
125
+ );
126
+ const bulkUnpublishMutation = useMutation(
127
+ (data) =>
128
+ post(`/content-manager/collection-types/${contentType.uid}/actions/bulkUnpublish`, data),
129
+ {
130
+ onSuccess() {
131
+ toggleNotification({
132
+ type: 'success',
133
+ message: {
134
+ id: 'content-manager.success.record.unpublish',
135
+ defaultMessage: 'Unpublished',
136
+ },
137
+ });
138
+
139
+ fetchData(`/content-manager/collection-types/${slug}${params}`);
140
+ },
141
+ onError(error) {
142
+ toggleNotification({
143
+ type: 'warning',
144
+ message: formatAPIError(error),
145
+ });
146
+ },
147
+ }
148
+ );
149
+
103
150
  // FIXME
104
151
  // Using a ref to avoid requests being fired multiple times on slug on change
105
152
  // We need it because the hook as mulitple dependencies so it may run before the permissions have checked
@@ -200,6 +247,70 @@ function ListView({
200
247
  [slug, params, fetchData, toggleNotification, formatAPIError, del]
201
248
  );
202
249
 
250
+ /**
251
+ * @param {number[]} selectedEntries - Array of ids to publish
252
+ * @returns {{validIds: number[], errors: Object.<number, string>}} - Returns an object with the valid ids and the errors
253
+ */
254
+ const validateEntriesToPublish = async (selectedEntries) => {
255
+ const validations = { validIds: [], errors: {} };
256
+ // Create the validation schema based on the contentType
257
+ const schema = createYupSchema(
258
+ contentType,
259
+ { components: layout.components },
260
+ { isDraft: false }
261
+ );
262
+ // Get the selected entries
263
+ const entries = data.filter((entry) => {
264
+ return selectedEntries.includes(entry.id);
265
+ });
266
+ // Validate each entry and map the unresolved promises
267
+ const validationPromises = entries.map((entry) =>
268
+ schema.validate(entry, { abortEarly: false })
269
+ );
270
+ // Resolve all the promises in one go
271
+ const resolvedPromises = await Promise.allSettled(validationPromises);
272
+ // Set the validations
273
+ resolvedPromises.forEach((promise) => {
274
+ if (promise.status === 'rejected') {
275
+ const entityId = promise.reason.value.id;
276
+ validations.errors[entityId] = getYupInnerErrors(promise.reason);
277
+ }
278
+
279
+ if (promise.status === 'fulfilled') {
280
+ validations.validIds.push(promise.value.id);
281
+ }
282
+ });
283
+
284
+ return validations;
285
+ };
286
+
287
+ const handleConfirmPublishAllData = async (selectedEntries) => {
288
+ const validations = await validateEntriesToPublish(selectedEntries);
289
+
290
+ if (Object.values(validations.errors).length) {
291
+ toggleNotification({
292
+ type: 'warning',
293
+ title: {
294
+ id: 'content-manager.listView.validation.errors.title',
295
+ defaultMessage: 'Action required',
296
+ },
297
+ message: {
298
+ id: 'content-manager.listView.validation.errors.message',
299
+ defaultMessage:
300
+ 'Please make sure all fields are valid before publishing (required field, min/max character limit, etc.)',
301
+ },
302
+ });
303
+
304
+ throw new Error('Validation error');
305
+ }
306
+
307
+ return bulkPublishMutation.mutateAsync({ ids: selectedEntries });
308
+ };
309
+
310
+ const handleConfirmUnpublishAllData = (selectedEntries) => {
311
+ return bulkUnpublishMutation.mutateAsync({ ids: selectedEntries });
312
+ };
313
+
203
314
  useEffect(() => {
204
315
  const CancelToken = axios.CancelToken;
205
316
  const source = CancelToken.source();
@@ -331,9 +442,12 @@ function ListView({
331
442
  <DynamicTable
332
443
  canCreate={canCreate}
333
444
  canDelete={canDelete}
445
+ canPublish={canPublish}
334
446
  contentTypeName={headerLayoutTitle}
335
- onConfirmDeleteAll={handleConfirmDeleteAllData}
336
447
  onConfirmDelete={handleConfirmDeleteData}
448
+ onConfirmDeleteAll={handleConfirmDeleteAllData}
449
+ onConfirmPublishAll={handleConfirmPublishAllData}
450
+ onConfirmUnpublishAll={handleConfirmUnpublishAllData}
337
451
  isBulkable={isBulkable}
338
452
  isLoading={isLoading}
339
453
  // FIXME: remove the layout props drilling
@@ -355,10 +469,12 @@ ListView.propTypes = {
355
469
  canCreate: PropTypes.bool.isRequired,
356
470
  canDelete: PropTypes.bool.isRequired,
357
471
  canRead: PropTypes.bool.isRequired,
472
+ canPublish: PropTypes.bool.isRequired,
358
473
  data: PropTypes.array.isRequired,
359
474
  layout: PropTypes.exact({
360
475
  components: PropTypes.object.isRequired,
361
476
  contentType: PropTypes.shape({
477
+ uid: PropTypes.string.isRequired,
362
478
  attributes: PropTypes.object.isRequired,
363
479
  metadatas: PropTypes.object.isRequired,
364
480
  info: PropTypes.shape({ displayName: PropTypes.string.isRequired }).isRequired,
@@ -18,3 +18,5 @@ export { default as mergeMetasWithSchema } from './mergeMetasWithSchema';
18
18
 
19
19
  export { default as removeKeyInObject } from './removeKeyInObject';
20
20
  export { default as removePasswordFieldsFromData } from './removePasswordFieldsFromData';
21
+
22
+ export { default as createYupSchema } from './schema';
@@ -7,7 +7,7 @@ import toNumber from 'lodash/toNumber';
7
7
  import * as yup from 'yup';
8
8
  import { translatedErrors as errorsTrads } from '@strapi/helper-plugin';
9
9
 
10
- import isFieldTypeNumber from '../../../utils/isFieldTypeNumber';
10
+ import isFieldTypeNumber from './isFieldTypeNumber';
11
11
 
12
12
  yup.addMethod(yup.mixed, 'defined', function () {
13
13
  return this.test('defined', errorsTrads.required, (value) => value !== undefined);
@@ -13,7 +13,12 @@ const injectionZones = {
13
13
  },
14
14
  contentManager: {
15
15
  editView: { informations: [], 'right-links': [] },
16
- listView: { actions: [], deleteModalAdditionalInfos: [] },
16
+ listView: {
17
+ actions: [],
18
+ deleteModalAdditionalInfos: [],
19
+ publishModalAdditionalInfos: [],
20
+ unpublishModalAdditionalInfos: [],
21
+ },
17
22
  },
18
23
  };
19
24
 
@@ -793,7 +793,9 @@
793
793
  "content-manager.plugin.description.long": "Quick way to see, edit and delete the data in your database.",
794
794
  "content-manager.plugin.description.short": "Quick way to see, edit and delete the data in your database.",
795
795
  "content-manager.popUpWarning.bodyMessage.contentType.delete": "Are you sure to delete Content-Type?",
796
- "content-manager.popUpWarning.bodyMessage.contentType.delete.all": "Are you sure to delete all Content-Types?",
796
+ "content-manager.popUpWarning.bodyMessage.contentType.delete.all": "Are you sure you want to delete these entries?",
797
+ "content-manager.popUpWarning.bodyMessage.contentType.publish.all": "Are you sure you want to publish these entries?",
798
+ "content-manager.popUpWarning.bodyMessage.contentType.unpublish.all": "Are you sure you want to unpublish these entries?",
797
799
  "content-manager.popUpWarning.warning.has-draft-relations.title": "Confirmation",
798
800
  "content-manager.popUpWarning.warning.publish-question": "Do you still want to publish?",
799
801
  "content-manager.popUpWarning.warning.unpublish": "If you don't publish this content, it will automatically turn into a Draft.",
@@ -816,6 +818,8 @@
816
818
  "content-manager.success.record.save": "Saved",
817
819
  "content-manager.success.record.unpublish": "Unpublished",
818
820
  "content-manager.utils.data-loaded": "The {number, plural, =1 {entry has} other {entries have}} successfully been loaded",
821
+ "content-manager.listView.validation.errors.title": "Action required",
822
+ "content-manager.listView.validation.errors.message": "Please make sure all fields are valid before publishing (required field, min/max character limit, etc.)",
819
823
  "dark": "Dark",
820
824
  "form.button.continue": "Continue",
821
825
  "form.button.done": "Done",