@strapi/admin 4.11.3 → 4.11.4

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 (64) hide show
  1. package/admin/src/components/AuthenticatedApp/index.js +2 -2
  2. package/admin/src/content-manager/components/RelationInput/RelationInput.js +99 -178
  3. package/admin/src/content-manager/components/RelationInput/components/Option.js +17 -15
  4. package/admin/src/content-manager/components/RelationInput/components/RelationList.js +2 -2
  5. package/admin/src/content-manager/pages/ListView/index.js +12 -0
  6. package/admin/src/pages/SettingsPage/components/Tokens/Table/index.js +15 -1
  7. package/admin/src/pages/SettingsPage/pages/Roles/ProtectedEditPage/index.js +4 -10
  8. package/admin/src/pages/SettingsPage/pages/Users/EditPage/index.js +2 -2
  9. package/admin/src/pages/SettingsPage/pages/Webhooks/EditView/components/WebhookForm/utils/makeWebhookValidationSchema.js +11 -5
  10. package/admin/src/translations/ca.json +1 -0
  11. package/admin/src/translations/en.json +4 -1
  12. package/admin/src/translations/es.json +5 -0
  13. package/admin/src/translations/fr.json +1 -0
  14. package/build/{1799.84268ad3.chunk.js → 1799.44d2e264.chunk.js} +1 -1
  15. package/build/{5542.64b623c9.chunk.js → 5542.c62d0daf.chunk.js} +1 -1
  16. package/build/{6405.27e1bee5.chunk.js → 970.89601f27.chunk.js} +2 -2
  17. package/build/Admin-authenticatedApp.cb649fc1.chunk.js +79 -0
  18. package/build/{admin-edit-roles-page.2040034a.chunk.js → admin-edit-roles-page.3fdd6b9d.chunk.js} +11 -11
  19. package/build/admin-edit-users.200551e3.chunk.js +10 -0
  20. package/build/api-tokens-list-page.a103f526.chunk.js +16 -0
  21. package/build/audit-logs-settings-page.f538490f.chunk.js +1 -0
  22. package/build/ca-json.1fed5d8b.chunk.js +1 -0
  23. package/build/content-manager.c40f5ff9.chunk.js +1088 -0
  24. package/build/{content-type-builder-list-view.0c3ceb4e.chunk.js → content-type-builder-list-view.a200a358.chunk.js} +1 -1
  25. package/build/{content-type-builder.e1b6d13b.chunk.js → content-type-builder.bd1bbff1.chunk.js} +15 -15
  26. package/build/{email-settings-page.6b38222d.chunk.js → email-settings-page.45695daa.chunk.js} +1 -1
  27. package/build/en-json.fb9f6ddd.chunk.js +1 -0
  28. package/build/es-json.42096084.chunk.js +1 -0
  29. package/build/fr-json.69789980.chunk.js +1 -0
  30. package/build/{i18n-settings-page.ff863f20.chunk.js → i18n-settings-page.29308d0b.chunk.js} +1 -1
  31. package/build/index.html +1 -1
  32. package/build/main.ee36abd9.js +2927 -0
  33. package/build/{runtime~main.20c3cac6.js → runtime~main.efd966f6.js} +1 -1
  34. package/build/sso-settings-page.0cdb96a6.chunk.js +1 -0
  35. package/build/transfer-tokens-list-page.7237443d.chunk.js +16 -0
  36. package/build/{upload-settings.43cf16cd.chunk.js → upload-settings.cb6c14c3.chunk.js} +1 -1
  37. package/build/{upload.72f8f8fc.chunk.js → upload.7e629643.chunk.js} +2 -2
  38. package/build/users-advanced-settings-page.750b1f76.chunk.js +9 -0
  39. package/build/{users-email-settings-page.33359797.chunk.js → users-email-settings-page.e9bcd865.chunk.js} +1 -1
  40. package/build/{users-providers-settings-page.1e7a4a71.chunk.js → users-providers-settings-page.a94253e9.chunk.js} +1 -1
  41. package/build/{users-roles-settings-page.235378b6.chunk.js → users-roles-settings-page.1f505119.chunk.js} +5 -5
  42. package/build/webhook-edit-page.77ef4f1a.chunk.js +33 -0
  43. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/index.js +4 -9
  44. package/ee/admin/pages/SettingsPage/pages/SingleSignOn/index.js +4 -9
  45. package/ee/server/services/review-workflows/entity-service-decorator.js +20 -11
  46. package/package.json +12 -12
  47. package/server/content-types/User.js +10 -0
  48. package/server/strategies/api-token.js +9 -5
  49. package/server/strategies/data-transfer.js +9 -5
  50. package/admin/src/content-manager/components/RelationInput/components/Relation.js +0 -53
  51. package/build/Admin-authenticatedApp.69855f1b.chunk.js +0 -79
  52. package/build/admin-edit-users.53e4290a.chunk.js +0 -10
  53. package/build/api-tokens-list-page.201fb67a.chunk.js +0 -16
  54. package/build/audit-logs-settings-page.b07ad202.chunk.js +0 -1
  55. package/build/ca-json.43e14418.chunk.js +0 -1
  56. package/build/content-manager.66cec770.chunk.js +0 -1094
  57. package/build/en-json.f5fa476a.chunk.js +0 -1
  58. package/build/es-json.715b6fd8.chunk.js +0 -1
  59. package/build/fr-json.73494bf5.chunk.js +0 -1
  60. package/build/main.83edb3fc.js +0 -2926
  61. package/build/sso-settings-page.35b67909.chunk.js +0 -1
  62. package/build/transfer-tokens-list-page.217573c3.chunk.js +0 -16
  63. package/build/users-advanced-settings-page.1911adf5.chunk.js +0 -9
  64. package/build/webhook-edit-page.1ee02c4b.chunk.js +0 -33
@@ -38,7 +38,7 @@ const AuthenticatedApp = () => {
38
38
  const [
39
39
  { data: appInfos, status },
40
40
  { data: tagName, isLoading },
41
- { data: permissions, status: fetchPermissionsStatus, refetch, isFetched, isFetching },
41
+ { data: permissions, status: fetchPermissionsStatus, refetch, isFetching },
42
42
  { data: userRoles },
43
43
  ] = useQueries([
44
44
  { queryKey: 'app-infos', queryFn: fetchAppInfo },
@@ -86,7 +86,7 @@ const AuthenticatedApp = () => {
86
86
  // We don't need to wait for the release query to be fetched before rendering the plugins
87
87
  // however, we need the appInfos and the permissions
88
88
  const shouldShowNotDependentQueriesLoader =
89
- (isFetching && isFetched) || status === 'loading' || fetchPermissionsStatus === 'loading';
89
+ isFetching || status === 'loading' || fetchPermissionsStatus === 'loading';
90
90
 
91
91
  const shouldShowLoader = isLoading || shouldShowNotDependentQueriesLoader;
92
92
 
@@ -1,24 +1,17 @@
1
- import React, { useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { useRef, useState, useMemo, useEffect } from 'react';
2
2
 
3
3
  import {
4
+ Status,
4
5
  Box,
5
- Field,
6
- FieldError,
7
- FieldHint,
8
- FieldLabel,
9
- Icon,
10
6
  Link,
11
- Status,
7
+ Icon,
8
+ Flex,
12
9
  TextButton,
13
- Tooltip,
14
10
  Typography,
11
+ Tooltip,
15
12
  VisuallyHidden,
13
+ Combobox,
16
14
  } from '@strapi/design-system';
17
- /**
18
- * TODO: this will come in another PR.
19
- */
20
- // eslint-disable-next-line no-restricted-imports
21
- import { ReactSelect } from '@strapi/helper-plugin';
22
15
  import { Cross, Refresh } from '@strapi/icons';
23
16
  import PropTypes from 'prop-types';
24
17
  import { FixedSizeList as List } from 'react-window';
@@ -27,7 +20,6 @@ import styled from 'styled-components';
27
20
  import { usePrev } from '../../hooks';
28
21
 
29
22
  import { Option } from './components/Option';
30
- import { Relation } from './components/Relation';
31
23
  import { RelationItem } from './components/RelationItem';
32
24
  import { RelationList } from './components/RelationList';
33
25
  import { RELATION_GUTTER, RELATION_ITEM_HEIGHT } from './constants';
@@ -88,7 +80,7 @@ const RelationInput = ({
88
80
  searchResults,
89
81
  size,
90
82
  }) => {
91
- const [value, setValue] = useState(null);
83
+ const [textValue, setTextValue] = useState('');
92
84
  const [overflow, setOverflow] = useState('');
93
85
 
94
86
  const listRef = useRef();
@@ -158,71 +150,10 @@ const RelationInput = ({
158
150
  };
159
151
  }, [paginatedRelations, relations, numberOfRelationsToDisplay, totalNumberOfRelations]);
160
152
 
161
- /**
162
- * --- ReactSelect Workaround START ---
163
- */
164
- /**
165
- * This code is being isolated because it's a hack to fix a placement bug in
166
- * `react-select` where when the options prop is updated the position of the
167
- * menu is not recalculated.
168
- */
169
- const [isMenuOpen, setIsMenuOpen] = useState(false);
170
-
171
- const timeoutRef = useRef();
172
- const previousOptions = useRef([]);
173
-
174
- useEffect(() => {
175
- /**
176
- * We only really want this effect to fire once when the options
177
- * change from an empty array to an array with values.
178
- * Otherwise, it'll fire when the infinite scrolling happens causing
179
- * the menu to jump to the top all the time when loading more.
180
- */
181
- if (options.length > 0 && previousOptions.current.length === 0) {
182
- setIsMenuOpen((isCurrentlyOpened) => {
183
- /**
184
- * If we're currently open and the options changed
185
- * we want to close and open to ensure the menu's
186
- * position is correctly calculated
187
- */
188
- if (isCurrentlyOpened) {
189
- timeoutRef.current = setTimeout(() => {
190
- setIsMenuOpen(true);
191
- }, 10);
192
-
193
- return false;
194
- }
195
-
196
- return false;
197
- });
153
+ const handleMenuOpen = (isOpen) => {
154
+ if (isOpen) {
155
+ onSearch();
198
156
  }
199
-
200
- return () => {
201
- previousOptions.current = options || [];
202
- };
203
- }, [options]);
204
-
205
- useEffect(() => {
206
- return () => {
207
- /**
208
- * If the component unmounts and a timer is set we should clear that timer
209
- */
210
- if (timeoutRef.current) {
211
- clearTimeout(timeoutRef.current);
212
- }
213
- };
214
- }, []);
215
-
216
- const handleMenuClose = () => {
217
- setIsMenuOpen(false);
218
- };
219
- /**
220
- * --- ReactSelect Workaround END ---
221
- */
222
-
223
- const handleMenuOpen = () => {
224
- setIsMenuOpen(true);
225
- onSearch();
226
157
  };
227
158
 
228
159
  /**
@@ -250,122 +181,112 @@ const RelationInput = ({
250
181
  };
251
182
 
252
183
  useEffect(() => {
184
+ if (updatedRelationsWith.current === 'onChange') {
185
+ setTextValue('');
186
+ }
187
+
253
188
  if (
254
189
  updatedRelationsWith.current === 'onChange' &&
255
190
  relations.length !== previewRelationsLength
256
191
  ) {
257
192
  listRef.current.scrollToItem(relations.length, 'end');
193
+ updatedRelationsWith.current = undefined;
258
194
  } else if (
259
195
  updatedRelationsWith.current === 'loadMore' &&
260
196
  relations.length !== previewRelationsLength
261
197
  ) {
262
198
  listRef.current.scrollToItem(0, 'start');
199
+ updatedRelationsWith.current = undefined;
263
200
  }
264
-
265
- updatedRelationsWith.current = undefined;
266
201
  }, [previewRelationsLength, relations]);
267
202
 
268
203
  const ariaDescriptionId = `${name}-item-instructions`;
269
204
 
270
205
  return (
271
- <Field error={error} name={name} hint={description} id={id} required={required}>
272
- <Relation
273
- totalNumberOfRelations={totalNumberOfRelations}
274
- size={size}
275
- search={
276
- <>
277
- <FieldLabel action={labelAction}>{label}</FieldLabel>
278
- <ReactSelect
279
- // position fixed doesn't update position on scroll
280
- // react select doesn't update menu position on options change
281
- menuPosition="absolute"
282
- menuPlacement="auto"
283
- components={{ Option }}
284
- options={options}
285
- isDisabled={disabled}
286
- isLoading={searchResults.isLoading}
287
- error={error}
288
- inputId={id}
289
- isSearchable
290
- isClear
291
- loadingMessage={() => loadingMessage}
292
- onChange={(relation) => {
293
- setValue(null);
294
- onRelationConnect(relation);
295
- updatedRelationsWith.current = 'onChange';
296
- }}
297
- onInputChange={(value) => {
298
- setValue(value);
299
- onSearch(value);
300
- }}
301
- onMenuClose={handleMenuClose}
302
- onMenuOpen={handleMenuOpen}
303
- menuIsOpen={isMenuOpen}
304
- noOptionsMessage={() => noRelationsMessage}
305
- onMenuScrollToBottom={() => {
306
- if (searchResults.hasNextPage) {
307
- onSearchNextPage();
308
- }
309
- }}
310
- placeholder={placeholder}
311
- name={name}
312
- value={value}
313
- />
314
- </>
315
- }
316
- loadMore={
317
- shouldDisplayLoadMoreButton && (
318
- <TextButton
319
- disabled={paginatedRelations.isLoading || paginatedRelations.isFetchingNextPage}
320
- onClick={handleLoadMore}
321
- loading={paginatedRelations.isLoading || paginatedRelations.isFetchingNextPage}
322
- startIcon={<Refresh />}
323
- >
324
- {labelLoadMore}
325
- </TextButton>
326
- )
327
- }
328
- >
329
- {relations.length > 0 && (
330
- <RelationList overflow={overflow}>
331
- <VisuallyHidden id={ariaDescriptionId}>{listAriaDescription}</VisuallyHidden>
332
- <VisuallyHidden aria-live="assertive">{liveText}</VisuallyHidden>
333
- <List
334
- height={dynamicListHeight}
335
- ref={listRef}
336
- outerRef={outerListRef}
337
- itemCount={totalNumberOfRelations}
338
- itemSize={RELATION_ITEM_HEIGHT + RELATION_GUTTER}
339
- itemData={{
340
- name,
341
- ariaDescribedBy: ariaDescriptionId,
342
- canDrag: canReorder,
343
- disabled,
344
- handleCancel: onCancel,
345
- handleDropItem: onDropItem,
346
- handleGrabItem: onGrabItem,
347
- iconButtonAriaLabel,
348
- labelDisconnectRelation,
349
- onRelationDisconnect,
350
- publicationStateTranslations,
351
- relations,
352
- updatePositionOfRelation: handleUpdatePositionOfRelation,
353
- }}
354
- itemKey={(index) => `${relations[index].mainField}_${relations[index].id}`}
355
- innerElementType="ol"
356
- >
357
- {ListItem}
358
- </List>
359
- </RelationList>
360
- )}
361
- {(description || error) && (
362
- <Box paddingTop={2}>
363
- <FieldHint />
364
- <FieldError />
365
- </Box>
206
+ <Flex gap={3} justifyContent="space-between" alignItems="end" wrap="wrap">
207
+ <Flex direction="column" alignItems="stretch" basis={size <= 6 ? '100%' : '70%'} gap={2}>
208
+ <Combobox
209
+ autocomplete="list"
210
+ error={error}
211
+ name={name}
212
+ hint={description}
213
+ id={id}
214
+ required={required}
215
+ label={label}
216
+ labelAction={labelAction}
217
+ disabled={disabled}
218
+ placeholder={placeholder}
219
+ hasMoreItems={searchResults.hasNextPage}
220
+ loading={searchResults.isLoading}
221
+ onOpenChange={handleMenuOpen}
222
+ noOptionsMessage={() => noRelationsMessage}
223
+ loadingMessage={loadingMessage}
224
+ onLoadMore={() => {
225
+ onSearchNextPage();
226
+ }}
227
+ textValue={textValue}
228
+ onChange={(relationId) => {
229
+ if (!relationId) {
230
+ return;
231
+ }
232
+ onRelationConnect(options.find((opt) => opt.id === relationId));
233
+ updatedRelationsWith.current = 'onChange';
234
+ }}
235
+ onTextValueChange={(text) => {
236
+ setTextValue(text);
237
+ }}
238
+ onInputChange={(event) => {
239
+ onSearch(event.currentTarget.value);
240
+ }}
241
+ >
242
+ {options.map((opt) => {
243
+ return <Option key={opt.id} {...opt} />;
244
+ })}
245
+ </Combobox>
246
+ {shouldDisplayLoadMoreButton && (
247
+ <TextButton
248
+ disabled={paginatedRelations.isLoading || paginatedRelations.isFetchingNextPage}
249
+ onClick={handleLoadMore}
250
+ loading={paginatedRelations.isLoading || paginatedRelations.isFetchingNextPage}
251
+ startIcon={<Refresh />}
252
+ >
253
+ {labelLoadMore}
254
+ </TextButton>
366
255
  )}
367
- </Relation>
368
- </Field>
256
+ </Flex>
257
+ {relations.length > 0 && (
258
+ <RelationList overflow={overflow}>
259
+ <VisuallyHidden id={ariaDescriptionId}>{listAriaDescription}</VisuallyHidden>
260
+ <VisuallyHidden aria-live="assertive">{liveText}</VisuallyHidden>
261
+ <List
262
+ height={dynamicListHeight}
263
+ ref={listRef}
264
+ outerRef={outerListRef}
265
+ itemCount={totalNumberOfRelations}
266
+ itemSize={RELATION_ITEM_HEIGHT + RELATION_GUTTER}
267
+ itemData={{
268
+ name,
269
+ ariaDescribedBy: ariaDescriptionId,
270
+ canDrag: canReorder,
271
+ disabled,
272
+ handleCancel: onCancel,
273
+ handleDropItem: onDropItem,
274
+ handleGrabItem: onGrabItem,
275
+ iconButtonAriaLabel,
276
+ labelDisconnectRelation,
277
+ onRelationDisconnect,
278
+ publicationStateTranslations,
279
+ relations,
280
+ updatePositionOfRelation: handleUpdatePositionOfRelation,
281
+ }}
282
+ itemKey={(index) => `${relations[index].mainField}_${relations[index].id}`}
283
+ innerElementType="ol"
284
+ >
285
+ {ListItem}
286
+ </List>
287
+ </RelationList>
288
+ )}
289
+ </Flex>
369
290
  );
370
291
  };
371
292
 
@@ -1,10 +1,9 @@
1
1
  import React from 'react';
2
2
 
3
- import { Flex, Typography } from '@strapi/design-system';
3
+ import { Flex, Typography, ComboboxOption } from '@strapi/design-system';
4
4
  import { pxToRem } from '@strapi/helper-plugin';
5
5
  import PropTypes from 'prop-types';
6
6
  import { useIntl } from 'react-intl';
7
- import { components } from 'react-select';
8
7
  import styled from 'styled-components';
9
8
 
10
9
  import { getTrad } from '../../../utils';
@@ -19,10 +18,8 @@ const StyledBullet = styled.div`
19
18
  border-radius: 50%;
20
19
  `;
21
20
 
22
- export const Option = (props) => {
21
+ export const Option = ({ publicationState, mainField, id }) => {
23
22
  const { formatMessage } = useIntl();
24
- const Component = components.Option;
25
- const { publicationState, mainField, id } = props.data;
26
23
 
27
24
  if (publicationState) {
28
25
  const isDraft = publicationState === 'draft';
@@ -37,24 +34,29 @@ export const Option = (props) => {
37
34
  const title = isDraft ? formatMessage(draftMessage) : formatMessage(publishedMessage);
38
35
 
39
36
  return (
40
- <Component {...props}>
37
+ <ComboboxOption value={id} textValue={mainField ?? id}>
41
38
  <Flex>
42
39
  <StyledBullet title={title} isDraft={isDraft} />
43
40
  <Typography ellipsis>{mainField ?? id}</Typography>
44
41
  </Flex>
45
- </Component>
42
+ </ComboboxOption>
46
43
  );
47
44
  }
48
45
 
49
- return <Component {...props}>{mainField ?? id}</Component>;
46
+ return (
47
+ <ComboboxOption value={id} textValue={mainField ?? id}>
48
+ {mainField ?? id}
49
+ </ComboboxOption>
50
+ );
51
+ };
52
+
53
+ Option.defaultProps = {
54
+ mainField: undefined,
55
+ publicationState: undefined,
50
56
  };
51
57
 
52
58
  Option.propTypes = {
53
- isFocused: PropTypes.bool.isRequired,
54
- data: PropTypes.shape({
55
- id: PropTypes.number.isRequired,
56
- isDraft: PropTypes.bool,
57
- mainField: PropTypes.string,
58
- publicationState: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
59
- }).isRequired,
59
+ id: PropTypes.number.isRequired,
60
+ mainField: PropTypes.string,
61
+ publicationState: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
60
62
  };
@@ -6,8 +6,8 @@ import styled from 'styled-components';
6
6
 
7
7
  const ShadowBox = styled(Box)`
8
8
  position: relative;
9
- overflow-x: hidden;
10
- overflow-y: auto;
9
+ overflow: hidden;
10
+ flex: 1;
11
11
 
12
12
  &:before,
13
13
  &:after {
@@ -33,6 +33,7 @@ import {
33
33
  } from '@strapi/helper-plugin';
34
34
  import { ArrowLeft, Cog, Plus } from '@strapi/icons';
35
35
  import axios from 'axios';
36
+ import getReviewWorkflowsColumn from 'ee_else_ce/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumn';
36
37
  import isEqual from 'lodash/isEqual';
37
38
  import PropTypes from 'prop-types';
38
39
  import { stringify } from 'qs';
@@ -391,6 +392,17 @@ function ListView({
391
392
  return formattedHeaders;
392
393
  }
393
394
 
395
+ // this should not exist. Ideally we would use registerHook() similar to what has been done
396
+ // in the i18n plugin. In order to do that review-workflows should have been a plugin. In
397
+ // a future iteration we need to find a better pattern.
398
+
399
+ // In CE this will return null - in EE a column definition including the custom formatting component.
400
+ const reviewWorkflowColumn = getReviewWorkflowsColumn(layout);
401
+
402
+ if (reviewWorkflowColumn) {
403
+ formattedHeaders.push(reviewWorkflowColumn);
404
+ }
405
+
394
406
  return [
395
407
  ...formattedHeaders,
396
408
  {
@@ -10,6 +10,7 @@ import {
10
10
  useTracking,
11
11
  } from '@strapi/helper-plugin';
12
12
  import PropTypes from 'prop-types';
13
+ import { useIntl } from 'react-intl';
13
14
  import { useHistory } from 'react-router-dom';
14
15
 
15
16
  import DeleteButton from './DeleteButton';
@@ -28,6 +29,7 @@ const Table = ({
28
29
  const { canDelete, canUpdate, canRead } = permissions;
29
30
  const withBulkActions = canDelete || canUpdate || canRead;
30
31
  const [{ query }] = useQueryParams();
32
+ const { formatMessage } = useIntl();
31
33
  const [, sortOrder] = query ? query.sort.split(':') : 'ASC';
32
34
  const {
33
35
  push,
@@ -83,7 +85,19 @@ const Table = ({
83
85
  <Td>
84
86
  {token.lastUsedAt && (
85
87
  <Typography textColor="neutral800">
86
- <RelativeTime timestamp={new Date(token.lastUsedAt)} />
88
+ <RelativeTime
89
+ timestamp={new Date(token.lastUsedAt)}
90
+ customIntervals={[
91
+ {
92
+ unit: 'hours',
93
+ threshold: 1,
94
+ text: formatMessage({
95
+ id: 'Settings.apiTokens.lastHour',
96
+ defaultMessage: 'last hour',
97
+ }),
98
+ },
99
+ ]}
100
+ />
87
101
  </Typography>
88
102
  )}
89
103
  </Td>
@@ -10,19 +10,13 @@ import EditPage from '../EditPage';
10
10
  const ProtectedEditPage = () => {
11
11
  const permissions = useSelector(selectAdminPermissions);
12
12
 
13
- // TODO: this is necessary because otherwise we run into an
14
- // infinite rendering loop
15
- const permissionsMemoized = React.useMemo(() => {
16
- return {
17
- read: permissions.settings.roles.read,
18
- update: permissions.settings.roles.update,
19
- };
20
- }, [permissions.settings.roles.read, permissions.settings.roles.update]);
21
-
22
13
  const {
23
14
  isLoading,
24
15
  allowedActions: { canRead, canUpdate },
25
- } = useRBAC(permissionsMemoized);
16
+ } = useRBAC({
17
+ read: permissions.settings.roles.read,
18
+ update: permissions.settings.roles.update,
19
+ });
26
20
 
27
21
  if (isLoading) {
28
22
  return <LoadingIndicatorPage />;
@@ -183,13 +183,13 @@ const EditPage = ({ canUpdate }) => {
183
183
  validateOnChange={false}
184
184
  validationSchema={editValidation}
185
185
  >
186
- {({ errors, values, handleChange, isSubmitting }) => {
186
+ {({ errors, values, handleChange, isSubmitting, dirty }) => {
187
187
  return (
188
188
  <Form>
189
189
  <HeaderLayout
190
190
  primaryAction={
191
191
  <Button
192
- disabled={isSubmitting || !canUpdate}
192
+ disabled={(isSubmitting || !canUpdate) ? true : !dirty}
193
193
  startIcon={<Check />}
194
194
  loading={isSubmitting}
195
195
  type="submit"
@@ -1,4 +1,3 @@
1
- import { translatedErrors } from '@strapi/helper-plugin';
2
1
  import * as yup from 'yup';
3
2
 
4
3
  const NAME_REGEX = /(^$)|(^[A-Za-z][_0-9A-Za-z ]*$)/;
@@ -10,11 +9,18 @@ export const makeWebhookValidationSchema = ({ formatMessage }) =>
10
9
  .string()
11
10
  .required(
12
11
  formatMessage({
13
- id: 'Settings.webhooks.validation.name',
12
+ id: 'Settings.webhooks.validation.name.required',
14
13
  defaultMessage: 'Name is required',
15
14
  })
16
15
  )
17
- .matches(NAME_REGEX, translatedErrors.regex),
16
+ .matches(
17
+ NAME_REGEX,
18
+ formatMessage({
19
+ id: 'Settings.webhooks.validation.name.regex',
20
+ defaultMessage:
21
+ 'The name must start with a letter and only contain letters, numbers, spaces and underscores',
22
+ })
23
+ ),
18
24
  url: yup
19
25
  .string()
20
26
  .required(
@@ -26,8 +32,8 @@ export const makeWebhookValidationSchema = ({ formatMessage }) =>
26
32
  .matches(
27
33
  URL_REGEX,
28
34
  formatMessage({
29
- id: translatedErrors.regex,
30
- defaultMessage: 'The value does not match the regex',
35
+ id: 'Settings.webhooks.validation.url.regex',
36
+ defaultMessage: 'The value must be a valid Url',
31
37
  })
32
38
  ),
33
39
  headers: yup.lazy((array) => {
@@ -88,6 +88,7 @@
88
88
  "Settings.apiTokens.emptyStateLayout": "Encara no tens cap contingut...",
89
89
  "Settings.tokens.notification.copied": "Token copiat al porta-retalls.",
90
90
  "Settings.apiTokens.title": "Tokens d'API",
91
+ "Settings.apiTokens.lastHour": "darrera hora",
91
92
  "Settings.tokens.types.full-access": "Accés complet",
92
93
  "Settings.tokens.types.read-only": "Només lectura",
93
94
  "Settings.application.Strapi-version": "versió de Strapi",
@@ -97,6 +97,7 @@
97
97
  "Settings.apiTokens.emptyStateLayout": "You don’t have any content yet...",
98
98
  "Settings.apiTokens.regenerate": "Regenerate",
99
99
  "Settings.apiTokens.title": "API Tokens",
100
+ "Settings.apiTokens.lastHour": "last hour",
100
101
  "Settings.application.customization": "Customization",
101
102
  "Settings.application.customization.auth-logo.carousel-hint": "Replace the logo in the authentication pages",
102
103
  "Settings.application.customization.carousel-hint": "Change the admin panel logo (Max dimension: {dimension}x{dimension}, Max file size: {size}KB)",
@@ -318,8 +319,10 @@
318
319
  "Settings.webhooks.trigger.test": "Test-trigger",
319
320
  "Settings.webhooks.trigger.title": "Save before Trigger",
320
321
  "Settings.webhooks.value": "Value",
321
- "Settings.webhooks.validation.name": "Name is required",
322
+ "Settings.webhooks.validation.name.required": "Name is required",
323
+ "Settings.webhooks.validation.name.regex": "The name must start with a letter and only contain letters, numbers, spaces and underscores",
322
324
  "Settings.webhooks.validation.url.required": "Url is required",
325
+ "Settings.webhooks.validation.url.regex": "The value must be a valid Url",
323
326
  "Settings.webhooks.validation.key": "Key is required",
324
327
  "Settings.webhooks.validation.value": "Value is required",
325
328
  "Usecase.back-end": "Back-end developer",
@@ -86,6 +86,11 @@
86
86
  "Settings.apiTokens.emptyStateLayout": "Aún no tienes ningún contenido ...",
87
87
  "Settings.tokens.notification.copied": "Token copiado al portapapeles.",
88
88
  "Settings.apiTokens.title": "Tokens de API",
89
+ "Settings.apiTokens.lastHour": "última hora",
90
+ "Settings.tokens.ListView.headers.createdAt": "Creado en",
91
+ "Settings.tokens.ListView.headers.description": "Descripción",
92
+ "Settings.tokens.ListView.headers.lastUsedAt": "Último uso",
93
+ "Settings.tokens.ListView.headers.name": "Nombre",
89
94
  "Settings.tokens.types.full-access": "Acceso completo",
90
95
  "Settings.tokens.types.read-only": "Solo lectura",
91
96
  "Settings.application.description": "Información global del panel de administración",
@@ -100,6 +100,7 @@
100
100
  "Settings.apiTokens.createPage.BoundRoute.title": "Route rattachée à",
101
101
  "Settings.apiTokens.createPage.permissions.header.title": "Paramètres avancés",
102
102
  "Settings.apiTokens.createPage.permissions.header.hint": "Sélectionner les actions de l'application ou du plugin et sur l'icône de la roue crantée pour afficher la route rattachée",
103
+ "Settings.apiTokens.lastHour": "dernière heure",
103
104
  "Settings.tokens.duration.30-days": "30 jours",
104
105
  "Settings.tokens.duration.7-days": "7 jours",
105
106
  "Settings.tokens.duration.90-days": "90 jours",
@@ -15,7 +15,7 @@ Try polyfilling it using "@formatjs/intl-listformat"
15
15
  Try polyfilling it using "@formatjs/intl-displaynames"
16
16
  `,Y.jK.MISSING_INTL_API));var L=(0,S.L6)(s,Te);try{return g(d,L).of(l)}catch(W){p(new C.Qe("Error formatting display name.",d,W))}}function ge(u){var g=u?u[Object.keys(u)[0]]:void 0;return typeof g=="string"}function re(u){u.onWarn&&u.defaultRichTextElements&&ge(u.messages||{})&&u.onWarn(`[@formatjs/intl] "defaultRichTextElements" was specified but "message" was not pre-compiled.
17
17
  Please consider using "@formatjs/cli" to pre-compile your messages for performance.
18
- For more details see https://formatjs.io/docs/getting-started/message-distribution`)}function q(u,g){var l=(0,S.ax)(g),s=(0,v.pi)((0,v.pi)({},S.Z0),u),d=s.locale,p=s.defaultLocale,w=s.onError;return d?!Intl.NumberFormat.supportedLocalesOf(d).length&&w?w(new C.gb('Missing locale data for locale: "'.concat(d,'" in Intl.NumberFormat. Using default locale: "').concat(p,'" as fallback. See https://formatjs.io/docs/react-intl#runtime-requirements for more details'))):!Intl.DateTimeFormat.supportedLocalesOf(d).length&&w&&w(new C.gb('Missing locale data for locale: "'.concat(d,'" in Intl.DateTimeFormat. Using default locale: "').concat(p,'" as fallback. See https://formatjs.io/docs/react-intl#runtime-requirements for more details'))):(w&&w(new C.OV('"locale" was not configured, using "'.concat(p,'" as fallback. See https://formatjs.io/docs/react-intl/api#intlshape for more details'))),s.locale=s.defaultLocale||"en"),re(s),(0,v.pi)((0,v.pi)({},s),{formatters:l,formatNumber:N.bind(null,s,l.getNumberFormat),formatNumberToParts:_.bind(null,s,l.getNumberFormat),formatRelativeTime:ce.bind(null,s,l.getRelativeTimeFormat),formatDate:Ne.bind(null,s,l.getDateTimeFormat),formatDateToParts:Be.bind(null,s,l.getDateTimeFormat),formatTime:Ye.bind(null,s,l.getDateTimeFormat),formatDateTimeRange:Me.bind(null,s,l.getDateTimeFormat),formatTimeToParts:He.bind(null,s,l.getDateTimeFormat),formatPlural:ze.bind(null,s,l.getPluralRules),formatMessage:J.bind(null,s,l),$t:J.bind(null,s,l),formatList:Ze.bind(null,s,l.getListFormat),formatListToParts:de.bind(null,s,l.getListFormat),formatDisplayName:Re.bind(null,s,l.getDisplayNames)})}var se=y(33961);function H(u){return{locale:u.locale,timeZone:u.timeZone,fallbackOnEmptyString:u.fallbackOnEmptyString,formats:u.formats,textComponent:u.textComponent,messages:u.messages,defaultLocale:u.defaultLocale,defaultFormats:u.defaultFormats,onError:u.onError,onWarn:u.onWarn,wrapRichTextChunksInFragment:u.wrapRichTextChunksInFragment,defaultRichTextElements:u.defaultRichTextElements}}function V(u){return u&&Object.keys(u).reduce(function(g,l){var s=u[l];return g[l]=(0,se.Gt)(s)?(0,E.dt)(s):s,g},{})}var Fe=function(u,g,l,s){for(var d=[],p=4;p<arguments.length;p++)d[p-4]=arguments[p];var w=V(s),L=J.apply(void 0,(0,v.ev)([u,g,l,w],d,!1));return Array.isArray(L)?R.Children.toArray(L):L},je=function(u,g){var l=u.defaultRichTextElements,s=(0,v._T)(u,["defaultRichTextElements"]),d=V(l),p=q((0,v.pi)((0,v.pi)((0,v.pi)({},E.Z0),s),{defaultRichTextElements:d}),g),w={locale:p.locale,timeZone:p.timeZone,fallbackOnEmptyString:p.fallbackOnEmptyString,formats:p.formats,defaultLocale:p.defaultLocale,defaultFormats:p.defaultFormats,messages:p.messages,onError:p.onError,defaultRichTextElements:d};return(0,v.pi)((0,v.pi)({},p),{formatMessage:Fe.bind(null,w,p.formatters),$t:Fe.bind(null,w,p.formatters)})},We=function(u){(0,v.ZT)(g,u);function g(){var l=u!==null&&u.apply(this,arguments)||this;return l.cache=(0,S.Sn)(),l.state={cache:l.cache,intl:je(H(l.props),l.cache),prevConfig:H(l.props)},l}return g.getDerivedStateFromProps=function(l,s){var d=s.prevConfig,p=s.cache,w=H(l);return(0,E.wU)(d,w)?null:{intl:je(w,p),prevConfig:w}},g.prototype.render=function(){return(0,E.lq)(this.state.intl),R.createElement(Z.zt,{value:this.state.intl},this.props.children)},g.displayName="IntlProvider",g.defaultProps=E.Z0,g}(R.PureComponent),Ve=We},17e3:function(B,X,y){"use strict";y.d(X,{D:function(){return Z}});var v=y(74512),R=y(72850);const Z=({children:E})=>(0,v.jsx)(R.x,{paddingLeft:10,paddingRight:10,children:E})},82055:function(B,X,y){"use strict";y.d(X,{T:function(){return he}});var v=y(74512),R=y(32735),Z=y(8471);const E=S=>{const D=(0,R.useRef)(null),[z,N]=(0,R.useState)(!0),_=([Y])=>{N(Y.isIntersecting)};return(0,R.useEffect)(()=>{const Y=D.current,le=new IntersectionObserver(_,S);return Y&&le.observe(D.current),()=>{Y&&le.disconnect()}},[D,S]),[D,z]};var k=y(63060);const G=(S,D)=>{const z=(0,k.W)(D);(0,R.useLayoutEffect)(()=>{const N=new ResizeObserver(z);return Array.isArray(S)?S.forEach(_=>{_.current&&N.observe(_.current)}):S.current&&N.observe(S.current),()=>{N.disconnect()}},[S,z])};var C=y(72850),oe=y(87933),$=y(49372);const he=S=>{const D=(0,R.useRef)(null),[z,N]=(0,R.useState)(null),[_,Y]=E({root:null,rootMargin:"0px",threshold:0});return G(_,()=>{_.current&&N(_.current.getBoundingClientRect())}),(0,R.useEffect)(()=>{D.current&&N(D.current.getBoundingClientRect())},[D]),(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)("div",{style:{height:z?.height},ref:_,children:Y&&(0,v.jsx)(J,{ref:D,...S})}),!Y&&(0,v.jsx)(J,{...S,sticky:!0,width:z?.width})]})};he.displayName="HeaderLayout";const xe=(0,Z.ZP)(C.x)`
18
+ For more details see https://formatjs.io/docs/getting-started/message-distribution`)}function q(u,g){var l=(0,S.ax)(g),s=(0,v.pi)((0,v.pi)({},S.Z0),u),d=s.locale,p=s.defaultLocale,w=s.onError;return d?!Intl.NumberFormat.supportedLocalesOf(d).length&&w?w(new C.gb('Missing locale data for locale: "'.concat(d,'" in Intl.NumberFormat. Using default locale: "').concat(p,'" as fallback. See https://formatjs.io/docs/react-intl#runtime-requirements for more details'))):!Intl.DateTimeFormat.supportedLocalesOf(d).length&&w&&w(new C.gb('Missing locale data for locale: "'.concat(d,'" in Intl.DateTimeFormat. Using default locale: "').concat(p,'" as fallback. See https://formatjs.io/docs/react-intl#runtime-requirements for more details'))):(w&&w(new C.OV('"locale" was not configured, using "'.concat(p,'" as fallback. See https://formatjs.io/docs/react-intl/api#intlshape for more details'))),s.locale=s.defaultLocale||"en"),re(s),(0,v.pi)((0,v.pi)({},s),{formatters:l,formatNumber:N.bind(null,s,l.getNumberFormat),formatNumberToParts:_.bind(null,s,l.getNumberFormat),formatRelativeTime:ce.bind(null,s,l.getRelativeTimeFormat),formatDate:Ne.bind(null,s,l.getDateTimeFormat),formatDateToParts:Be.bind(null,s,l.getDateTimeFormat),formatTime:Ye.bind(null,s,l.getDateTimeFormat),formatDateTimeRange:Me.bind(null,s,l.getDateTimeFormat),formatTimeToParts:He.bind(null,s,l.getDateTimeFormat),formatPlural:ze.bind(null,s,l.getPluralRules),formatMessage:J.bind(null,s,l),$t:J.bind(null,s,l),formatList:Ze.bind(null,s,l.getListFormat),formatListToParts:de.bind(null,s,l.getListFormat),formatDisplayName:Re.bind(null,s,l.getDisplayNames)})}var se=y(33961);function H(u){return{locale:u.locale,timeZone:u.timeZone,fallbackOnEmptyString:u.fallbackOnEmptyString,formats:u.formats,textComponent:u.textComponent,messages:u.messages,defaultLocale:u.defaultLocale,defaultFormats:u.defaultFormats,onError:u.onError,onWarn:u.onWarn,wrapRichTextChunksInFragment:u.wrapRichTextChunksInFragment,defaultRichTextElements:u.defaultRichTextElements}}function V(u){return u&&Object.keys(u).reduce(function(g,l){var s=u[l];return g[l]=(0,se.Gt)(s)?(0,E.dt)(s):s,g},{})}var Fe=function(u,g,l,s){for(var d=[],p=4;p<arguments.length;p++)d[p-4]=arguments[p];var w=V(s),L=J.apply(void 0,(0,v.ev)([u,g,l,w],d,!1));return Array.isArray(L)?R.Children.toArray(L):L},je=function(u,g){var l=u.defaultRichTextElements,s=(0,v._T)(u,["defaultRichTextElements"]),d=V(l),p=q((0,v.pi)((0,v.pi)((0,v.pi)({},E.Z0),s),{defaultRichTextElements:d}),g),w={locale:p.locale,timeZone:p.timeZone,fallbackOnEmptyString:p.fallbackOnEmptyString,formats:p.formats,defaultLocale:p.defaultLocale,defaultFormats:p.defaultFormats,messages:p.messages,onError:p.onError,defaultRichTextElements:d};return(0,v.pi)((0,v.pi)({},p),{formatMessage:Fe.bind(null,w,p.formatters),$t:Fe.bind(null,w,p.formatters)})},We=function(u){(0,v.ZT)(g,u);function g(){var l=u!==null&&u.apply(this,arguments)||this;return l.cache=(0,S.Sn)(),l.state={cache:l.cache,intl:je(H(l.props),l.cache),prevConfig:H(l.props)},l}return g.getDerivedStateFromProps=function(l,s){var d=s.prevConfig,p=s.cache,w=H(l);return(0,E.wU)(d,w)?null:{intl:je(w,p),prevConfig:w}},g.prototype.render=function(){return(0,E.lq)(this.state.intl),R.createElement(Z.zt,{value:this.state.intl},this.props.children)},g.displayName="IntlProvider",g.defaultProps=E.Z0,g}(R.PureComponent),Ve=We},17e3:function(B,X,y){"use strict";y.d(X,{D:function(){return Z}});var v=y(74512),R=y(72850);const Z=({children:E})=>(0,v.jsx)(R.x,{paddingLeft:10,paddingRight:10,children:E})},82055:function(B,X,y){"use strict";y.d(X,{T:function(){return he}});var v=y(74512),R=y(32735),Z=y(8471);const E=S=>{const D=(0,R.useRef)(null),[z,N]=(0,R.useState)(!0),_=([Y])=>{N(Y.isIntersecting)};return(0,R.useEffect)(()=>{const Y=D.current,le=new IntersectionObserver(_,S);return Y&&le.observe(D.current),()=>{Y&&le.disconnect()}},[D,S]),[D,z]};var k=y(2285);const G=(S,D)=>{const z=(0,k.W)(D);(0,R.useLayoutEffect)(()=>{const N=new ResizeObserver(z);return Array.isArray(S)?S.forEach(_=>{_.current&&N.observe(_.current)}):S.current&&N.observe(S.current),()=>{N.disconnect()}},[S,z])};var C=y(72850),oe=y(87933),$=y(49372);const he=S=>{const D=(0,R.useRef)(null),[z,N]=(0,R.useState)(null),[_,Y]=E({root:null,rootMargin:"0px",threshold:0});return G(_,()=>{_.current&&N(_.current.getBoundingClientRect())}),(0,R.useEffect)(()=>{D.current&&N(D.current.getBoundingClientRect())},[D]),(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)("div",{style:{height:z?.height},ref:_,children:Y&&(0,v.jsx)(J,{ref:D,...S})}),!Y&&(0,v.jsx)(J,{...S,sticky:!0,width:z?.width})]})};he.displayName="HeaderLayout";const xe=(0,Z.ZP)(C.x)`
19
19
  width: ${({width:S})=>S?`${S/16}rem`:void 0};
20
20
  z-index: ${({theme:S})=>S.zIndices[1]};
21
21
  `,J=R.forwardRef(({navigationAction:S,primaryAction:D,secondaryAction:z,subtitle:N,title:_,sticky:Y,width:le,...fe},ce)=>{const pe=typeof N=="string";return Y?(0,v.jsx)(xe,{paddingLeft:6,paddingRight:6,paddingTop:3,paddingBottom:3,position:"fixed",top:0,right:0,background:"neutral0",shadow:"tableShadow",width:le,"data-strapi-header-sticky":!0,children:(0,v.jsxs)(oe.k,{justifyContent:"space-between",children:[(0,v.jsxs)(oe.k,{children:[S&&(0,v.jsx)(C.x,{paddingRight:3,children:S}),(0,v.jsxs)(C.x,{children:[(0,v.jsx)($.Z,{variant:"beta",as:"h1",...fe,children:_}),pe?(0,v.jsx)($.Z,{variant:"pi",textColor:"neutral600",children:N}):N]}),z?(0,v.jsx)(C.x,{paddingLeft:4,children:z}):null]}),(0,v.jsx)(oe.k,{children:D?(0,v.jsx)(C.x,{paddingLeft:2,children:D}):void 0})]})}):(0,v.jsxs)(C.x,{ref:ce,paddingLeft:10,paddingRight:10,paddingBottom:8,paddingTop:S?6:8,background:"neutral100","data-strapi-header":!0,children:[S?(0,v.jsx)(C.x,{paddingBottom:2,children:S}):null,(0,v.jsxs)(oe.k,{justifyContent:"space-between",children:[(0,v.jsxs)(oe.k,{minWidth:0,children:[(0,v.jsx)($.Z,{as:"h1",variant:"alpha",...fe,children:_}),z?(0,v.jsx)(C.x,{paddingLeft:4,children:z}):null]}),D]}),pe?(0,v.jsx)($.Z,{variant:"epsilon",textColor:"neutral600",as:"p",children:N}):N]})})},27649:function(B,X,y){"use strict";y.d(X,{o:function(){return k}});var v=y(74512),R=y(8471),Z=y(72850);const E=(0,R.ZP)(Z.x)`