@strapi/admin 4.10.6 → 4.11.0-beta.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.
- package/admin/src/content-manager/components/DynamicTable/BulkActionsBar/index.js +307 -0
- package/admin/src/content-manager/components/DynamicTable/index.js +20 -4
- package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +3 -2
- package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/index.js +0 -1
- package/admin/src/content-manager/pages/ListView/index.js +118 -2
- package/admin/src/content-manager/utils/index.js +2 -0
- package/admin/src/content-manager/{components/EditViewDataManagerProvider/utils → utils}/schema.js +1 -1
- package/admin/src/hooks/index.js +1 -1
- package/admin/src/hooks/useAdminUsers/__mocks__/index.js +5 -0
- package/admin/src/hooks/useAdminUsers/index.js +1 -0
- package/admin/src/hooks/useAdminUsers/useAdminUsers.js +38 -0
- package/admin/src/hooks/useContentTypes/__mocks__/index.js +6 -0
- package/admin/src/hooks/useContentTypes/index.js +1 -0
- package/admin/src/hooks/useContentTypes/useContentTypes.js +47 -0
- package/admin/src/injectionZones.js +6 -1
- package/admin/src/pages/HomePage/index.js +3 -2
- package/admin/src/pages/MarketplacePage/components/NpmPackageCard/InstallPluginButton.js +12 -8
- package/admin/src/pages/SettingsPage/components/Tokens/TokenBox/index.js +28 -26
- package/admin/src/pages/SettingsPage/pages/Users/EditPage/index.js +36 -26
- package/admin/src/pages/SettingsPage/pages/Users/EditPage/utils/api.js +1 -8
- package/admin/src/pages/SettingsPage/pages/Users/ListPage/ModalForm/index.js +6 -7
- package/admin/src/pages/SettingsPage/pages/Users/ListPage/index.js +54 -63
- package/admin/src/pages/SettingsPage/pages/Users/components/MagicLink/MagicLinkWrapper.js +11 -9
- package/admin/src/pages/SettingsPage/pages/Webhooks/EditView/index.js +2 -2
- package/admin/src/translations/en.json +5 -1
- package/build/3562.e0b1a0b3.chunk.js +50 -0
- package/build/5563.79950369.chunk.js +79 -0
- package/build/6970.7ea35fbd.chunk.js +1 -0
- package/build/7259.5cc67413.chunk.js +1 -0
- package/build/{Admin-authenticatedApp.0318dfb3.chunk.js → Admin-authenticatedApp.65172a0c.chunk.js} +2 -2
- package/build/{Admin_homePage.e15dcf28.chunk.js → Admin_homePage.107a9fe0.chunk.js} +9 -9
- package/build/{Admin_marketplace.f446ba2b.chunk.js → Admin_marketplace.717bd7ca.chunk.js} +11 -11
- package/build/{Admin_profilePage.1687246a.chunk.js → Admin_profilePage.a8fa3a56.chunk.js} +1 -1
- package/build/{Admin_settingsPage.f8c46a9a.chunk.js → Admin_settingsPage.bd715ed3.chunk.js} +2 -2
- package/build/admin-app.9c79b484.chunk.js +63 -0
- package/build/{admin-edit-roles-page.02c3b136.chunk.js → admin-edit-roles-page.0d12b741.chunk.js} +3 -3
- package/build/admin-edit-users.f9ce7844.chunk.js +10 -0
- package/build/{admin-roles-list.a323aa9f.chunk.js → admin-roles-list.e8bf9685.chunk.js} +1 -1
- package/build/admin-users.751b28b2.chunk.js +34 -0
- package/build/audit-logs-settings-page.3c6cea81.chunk.js +129 -0
- package/build/content-manager.bf060d8e.chunk.js +1123 -0
- package/build/{en-json.d965e364.chunk.js → en-json.19e9ff9b.chunk.js} +1 -1
- package/build/i18n-translation-en-json.1ec7becf.chunk.js +1 -0
- package/build/index.html +1 -1
- package/build/main.576a9d22.js +2630 -0
- package/build/review-workflows-settings.4b39b837.chunk.js +61 -0
- package/build/{runtime~main.0201a49b.js → runtime~main.96d92f16.js} +2 -2
- package/build/{sso-settings-page.c9d7c8df.chunk.js → sso-settings-page.265e3d72.chunk.js} +1 -1
- package/build/{webhook-edit-page.cb2cf1a5.chunk.js → webhook-edit-page.ddd5963d.chunk.js} +13 -13
- package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +1 -2
- package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/hooks/useAuditLogsData.js +36 -28
- package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getDisplayedFilters.js +1 -1
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ReviewWorkflows.js +3 -3
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js +32 -21
- package/ee/server/services/audit-logs.js +5 -1
- package/package.json +10 -11
- package/webpack.alias.js +0 -1
- package/admin/src/content-manager/components/DynamicTable/ConfirmDialogDeleteAll/index.js +0 -73
- package/admin/src/hooks/useModels/index.js +0 -58
- package/admin/src/hooks/useModels/reducer.js +0 -45
- package/admin/src/pages/SettingsPage/pages/Users/ListPage/utils/api.js +0 -20
- package/build/5563.2c8334ef.chunk.js +0 -79
- package/build/6858.85d76858.chunk.js +0 -50
- package/build/6970.36d3ffff.chunk.js +0 -1
- package/build/7259.116a9960.chunk.js +0 -1
- package/build/admin-app.6d48536c.chunk.js +0 -63
- package/build/admin-edit-users.49363035.chunk.js +0 -10
- package/build/admin-users.69f4900a.chunk.js +0 -34
- package/build/audit-logs-settings-page.482909d7.chunk.js +0 -129
- package/build/content-manager.cd71cb6e.chunk.js +0 -1123
- package/build/i18n-translation-en-json.60af6722.chunk.js +0 -1
- package/build/main.adab8b96.js +0 -2630
- package/build/review-workflows-settings.26409ce4.chunk.js +0 -61
|
@@ -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
|
|
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
|
|
29
|
+
import { cleanData } from './utils';
|
|
30
|
+
|
|
30
31
|
import { clearSetModifiedDataOnly } from '../../sharedReducers/crudReducer/actions';
|
|
31
32
|
import { usePrev } from '../../hooks';
|
|
32
33
|
|
|
@@ -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
|
|
@@ -199,6 +246,70 @@ function ListView({
|
|
|
199
246
|
[slug, params, fetchData, toggleNotification, formatAPIError, del]
|
|
200
247
|
);
|
|
201
248
|
|
|
249
|
+
/**
|
|
250
|
+
* @param {number[]} selectedEntries - Array of ids to publish
|
|
251
|
+
* @returns {{validIds: number[], errors: Object.<number, string>}} - Returns an object with the valid ids and the errors
|
|
252
|
+
*/
|
|
253
|
+
const validateEntriesToPublish = async (selectedEntries) => {
|
|
254
|
+
const validations = { validIds: [], errors: {} };
|
|
255
|
+
// Create the validation schema based on the contentType
|
|
256
|
+
const schema = createYupSchema(
|
|
257
|
+
contentType,
|
|
258
|
+
{ components: layout.components },
|
|
259
|
+
{ isDraft: false }
|
|
260
|
+
);
|
|
261
|
+
// Get the selected entries
|
|
262
|
+
const entries = data.filter((entry) => {
|
|
263
|
+
return selectedEntries.includes(entry.id);
|
|
264
|
+
});
|
|
265
|
+
// Validate each entry and map the unresolved promises
|
|
266
|
+
const validationPromises = entries.map((entry) =>
|
|
267
|
+
schema.validate(entry, { abortEarly: false })
|
|
268
|
+
);
|
|
269
|
+
// Resolve all the promises in one go
|
|
270
|
+
const resolvedPromises = await Promise.allSettled(validationPromises);
|
|
271
|
+
// Set the validations
|
|
272
|
+
resolvedPromises.forEach((promise) => {
|
|
273
|
+
if (promise.status === 'rejected') {
|
|
274
|
+
const entityId = promise.reason.value.id;
|
|
275
|
+
validations.errors[entityId] = getYupInnerErrors(promise.reason);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (promise.status === 'fulfilled') {
|
|
279
|
+
validations.validIds.push(promise.value.id);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
return validations;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const handleConfirmPublishAllData = async (selectedEntries) => {
|
|
287
|
+
const validations = await validateEntriesToPublish(selectedEntries);
|
|
288
|
+
|
|
289
|
+
if (Object.values(validations.errors).length) {
|
|
290
|
+
toggleNotification({
|
|
291
|
+
type: 'warning',
|
|
292
|
+
title: {
|
|
293
|
+
id: 'content-manager.listView.validation.errors.title',
|
|
294
|
+
defaultMessage: 'Action required',
|
|
295
|
+
},
|
|
296
|
+
message: {
|
|
297
|
+
id: 'content-manager.listView.validation.errors.message',
|
|
298
|
+
defaultMessage:
|
|
299
|
+
'Please make sure all fields are valid before publishing (required field, min/max character limit, etc.)',
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
throw new Error('Validation error');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return bulkPublishMutation.mutateAsync({ ids: selectedEntries });
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const handleConfirmUnpublishAllData = (selectedEntries) => {
|
|
310
|
+
return bulkUnpublishMutation.mutateAsync({ ids: selectedEntries });
|
|
311
|
+
};
|
|
312
|
+
|
|
202
313
|
useEffect(() => {
|
|
203
314
|
const CancelToken = axios.CancelToken;
|
|
204
315
|
const source = CancelToken.source();
|
|
@@ -330,9 +441,12 @@ function ListView({
|
|
|
330
441
|
<DynamicTable
|
|
331
442
|
canCreate={canCreate}
|
|
332
443
|
canDelete={canDelete}
|
|
444
|
+
canPublish={canPublish}
|
|
333
445
|
contentTypeName={headerLayoutTitle}
|
|
334
|
-
onConfirmDeleteAll={handleConfirmDeleteAllData}
|
|
335
446
|
onConfirmDelete={handleConfirmDeleteData}
|
|
447
|
+
onConfirmDeleteAll={handleConfirmDeleteAllData}
|
|
448
|
+
onConfirmPublishAll={handleConfirmPublishAllData}
|
|
449
|
+
onConfirmUnpublishAll={handleConfirmUnpublishAllData}
|
|
336
450
|
isBulkable={isBulkable}
|
|
337
451
|
isLoading={isLoading}
|
|
338
452
|
// FIXME: remove the layout props drilling
|
|
@@ -354,10 +468,12 @@ ListView.propTypes = {
|
|
|
354
468
|
canCreate: PropTypes.bool.isRequired,
|
|
355
469
|
canDelete: PropTypes.bool.isRequired,
|
|
356
470
|
canRead: PropTypes.bool.isRequired,
|
|
471
|
+
canPublish: PropTypes.bool.isRequired,
|
|
357
472
|
data: PropTypes.array.isRequired,
|
|
358
473
|
layout: PropTypes.exact({
|
|
359
474
|
components: PropTypes.object.isRequired,
|
|
360
475
|
contentType: PropTypes.shape({
|
|
476
|
+
uid: PropTypes.string.isRequired,
|
|
361
477
|
attributes: PropTypes.object.isRequired,
|
|
362
478
|
metadatas: PropTypes.object.isRequired,
|
|
363
479
|
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';
|
package/admin/src/content-manager/{components/EditViewDataManagerProvider/utils → utils}/schema.js
RENAMED
|
@@ -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 '
|
|
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);
|
package/admin/src/hooks/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { default as useConfigurations } from './useConfigurations';
|
|
2
|
-
export {
|
|
2
|
+
export { useContentTypes } from './useContentTypes';
|
|
3
3
|
export { default as useFetchPermissionsLayout } from './useFetchPermissionsLayout';
|
|
4
4
|
export { default as useFetchRole } from './useFetchRole';
|
|
5
5
|
export { default as useMenu } from './useMenu';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useAdminUsers';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useQuery } from 'react-query';
|
|
2
|
+
import { useFetchClient } from '@strapi/helper-plugin';
|
|
3
|
+
import { stringify } from 'qs';
|
|
4
|
+
|
|
5
|
+
export function useAdminUsers(params = {}, queryOptions = {}) {
|
|
6
|
+
const { id = '', ...queryParams } = params;
|
|
7
|
+
const queryString = stringify(queryParams, { encode: false });
|
|
8
|
+
|
|
9
|
+
const { get } = useFetchClient();
|
|
10
|
+
|
|
11
|
+
const { data, isError, isLoading, refetch } = useQuery(
|
|
12
|
+
['users', id, queryParams],
|
|
13
|
+
async () => {
|
|
14
|
+
const {
|
|
15
|
+
data: { data },
|
|
16
|
+
} = await get(`/admin/users/${id}${queryString ? `?${queryString}` : ''}`);
|
|
17
|
+
|
|
18
|
+
return data;
|
|
19
|
+
},
|
|
20
|
+
queryOptions
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
let users = [];
|
|
24
|
+
|
|
25
|
+
if (id && data) {
|
|
26
|
+
users = [data];
|
|
27
|
+
} else if (Array.isArray(data?.results)) {
|
|
28
|
+
users = data.results;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
users,
|
|
33
|
+
pagination: data?.pagination ?? null,
|
|
34
|
+
isLoading,
|
|
35
|
+
isError,
|
|
36
|
+
refetch,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useContentTypes';
|