@strapi/admin 4.5.3 → 4.5.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 (92) hide show
  1. package/admin/src/components/AuthenticatedApp/index.js +13 -2
  2. package/admin/src/content-manager/components/DynamicZone/components/DynamicComponent.js +1 -1
  3. package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +39 -3
  4. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +5 -1
  5. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/recursivelyFindPathsBasedOnCondition.js +8 -1
  6. package/admin/src/content-manager/components/NonRepeatableComponent/index.js +4 -0
  7. package/admin/src/content-manager/components/RelationInput/RelationInput.js +4 -9
  8. package/admin/src/content-manager/components/RelationInputDataManager/RelationInputDataManager.js +1 -1
  9. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/index.js +4 -0
  10. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +1 -1
  11. package/admin/src/content-manager/components/Wysiwyg/Editor.js +1 -1
  12. package/admin/src/content-manager/hooks/useLazyComponents/index.js +40 -15
  13. package/admin/src/content-manager/sharedReducers/crudReducer/actions.js +5 -0
  14. package/admin/src/content-manager/sharedReducers/crudReducer/constants.js +2 -0
  15. package/admin/src/content-manager/sharedReducers/crudReducer/reducer.js +7 -0
  16. package/admin/src/core/utils/axiosInstance.js +4 -2
  17. package/admin/src/hooks/index.js +1 -0
  18. package/admin/src/hooks/useFetchClient/index.js +23 -0
  19. package/admin/src/pages/App/index.js +20 -13
  20. package/admin/src/pages/HomePage/SocialLinks.js +4 -4
  21. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +2 -3
  22. package/admin/src/pages/SettingsPage/pages/Users/ListPage/ModalForm/index.js +23 -18
  23. package/admin/src/translations/tr.json +485 -5
  24. package/admin/src/utils/fetchClient.js +45 -0
  25. package/admin/src/utils/getFetchClient.js +10 -0
  26. package/admin/src/utils/index.js +1 -0
  27. package/admin/src/utils/uniqueAdminHash.js +22 -0
  28. package/build/{1233.80b05d66.chunk.js → 1233.802422fa.chunk.js} +61 -61
  29. package/build/4306.df40a798.chunk.js +98 -0
  30. package/build/4318.9283c350.chunk.js +30 -0
  31. package/build/5057.195a59ff.chunk.js +65 -0
  32. package/build/{4986.3820d11d.chunk.js → 8176.e929d326.chunk.js} +3 -3
  33. package/build/{8633.8da5488a.chunk.js → 8633.43ec9042.chunk.js} +1 -1
  34. package/build/8881.c693411a.chunk.js +245 -0
  35. package/build/9161.4a0ab137.chunk.js +2119 -0
  36. package/build/9279.6290c87a.chunk.js +117 -0
  37. package/build/Admin-authenticatedApp.0da578b8.chunk.js +80 -0
  38. package/build/{Admin_homePage.54e33c2d.chunk.js → Admin_homePage.8945f71a.chunk.js} +5 -5
  39. package/build/{Admin_marketplace.8219fda6.chunk.js → Admin_marketplace.ed754a4a.chunk.js} +1 -1
  40. package/build/{Admin_profilePage.e9fcce92.chunk.js → Admin_profilePage.60ab80bb.chunk.js} +1 -1
  41. package/build/{Admin_settingsPage.a1a5218b.chunk.js → Admin_settingsPage.6ef8acc9.chunk.js} +6 -6
  42. package/build/admin-app.a3277e72.chunk.js +112 -0
  43. package/build/admin-edit-roles-page.f407538c.chunk.js +1 -0
  44. package/build/admin-edit-users.5547b126.chunk.js +10 -0
  45. package/build/{admin-users.a0748674.chunk.js → admin-users.e64fb0f1.chunk.js} +1 -1
  46. package/build/content-manager.f9630c3b.chunk.js +1197 -0
  47. package/build/content-type-builder-translation-tr-json.949e22eb.chunk.js +1 -0
  48. package/build/content-type-builder.b132b5f4.chunk.js +145 -0
  49. package/build/email-settings-page.c6e62f6b.chunk.js +15 -0
  50. package/build/email-translation-tr-json.8aa034bb.chunk.js +1 -0
  51. package/build/i18n-translation-tr-json.34ca9d61.chunk.js +1 -0
  52. package/build/index.html +1 -1
  53. package/build/{main.f31112a5.js → main.71f24343.js} +371 -371
  54. package/build/runtime~main.1115f82b.js +2 -0
  55. package/build/sso-settings-page.feed2f45.chunk.js +1 -0
  56. package/build/tr-json.eac8bd79.chunk.js +1 -0
  57. package/build/upload-translation-tr-json.b173223a.chunk.js +1 -0
  58. package/build/upload.74540aab.chunk.js +64 -0
  59. package/build/users-advanced-settings-page.0c0b8230.chunk.js +13 -0
  60. package/build/users-email-settings-page.3126ff8c.chunk.js +28 -0
  61. package/build/users-permissions-translation-tr-json.9bebc250.chunk.js +1 -0
  62. package/build/users-providers-settings-page.b7b602e2.chunk.js +33 -0
  63. package/build/users-roles-settings-page.ce5b582d.chunk.js +30 -0
  64. package/build/{webhook-edit-page.14ad1e6e.chunk.js → webhook-edit-page.1215a6b7.chunk.js} +1 -1
  65. package/package.json +11 -11
  66. package/server/controllers/admin.js +2 -0
  67. package/server/routes/admin.js +1 -1
  68. package/server/services/metrics.js +5 -2
  69. package/utils/get-plugins-path.js +17 -3
  70. package/build/1920.74a262e7.chunk.js +0 -245
  71. package/build/2438.61291207.chunk.js +0 -2183
  72. package/build/2517.9b4940f3.chunk.js +0 -117
  73. package/build/4306.f03c2b46.chunk.js +0 -98
  74. package/build/4318.7931eee7.chunk.js +0 -30
  75. package/build/Admin-authenticatedApp.1e1d3bdd.chunk.js +0 -80
  76. package/build/admin-app.9cb0abc7.chunk.js +0 -112
  77. package/build/admin-edit-roles-page.23f15909.chunk.js +0 -1
  78. package/build/admin-edit-users.283b49ed.chunk.js +0 -10
  79. package/build/content-manager.01e04e11.chunk.js +0 -1200
  80. package/build/content-type-builder-translation-tr-json.2e52bc60.chunk.js +0 -1
  81. package/build/content-type-builder.848a9610.chunk.js +0 -145
  82. package/build/email-settings-page.d44a57cb.chunk.js +0 -15
  83. package/build/email-translation-tr-json.87f2feb3.chunk.js +0 -1
  84. package/build/runtime~main.f91e75cd.js +0 -2
  85. package/build/sso-settings-page.9f091262.chunk.js +0 -1
  86. package/build/tr-json.9c44ea0c.chunk.js +0 -1
  87. package/build/upload.a73936d9.chunk.js +0 -64
  88. package/build/users-advanced-settings-page.dc23bc56.chunk.js +0 -13
  89. package/build/users-email-settings-page.6541d372.chunk.js +0 -28
  90. package/build/users-permissions-translation-tr-json.cdc49a3c.chunk.js +0 -1
  91. package/build/users-providers-settings-page.e11a2f64.chunk.js +0 -33
  92. package/build/users-roles-settings-page.445e5e16.chunk.js +0 -30
@@ -20,7 +20,7 @@ import {
20
20
  fetchUserRoles,
21
21
  } from './utils/api';
22
22
  import checkLatestStrapiVersion from './utils/checkLatestStrapiVersion';
23
- import { getFullName } from '../../utils';
23
+ import { getFullName, hashAdminUserEmail } from '../../utils';
24
24
 
25
25
  const strapiVersion = packageJSON.version;
26
26
 
@@ -31,6 +31,7 @@ const AuthenticatedApp = () => {
31
31
  const userInfo = auth.getUserInfo();
32
32
  const userName = get(userInfo, 'username') || getFullName(userInfo.firstname, userInfo.lastname);
33
33
  const [userDisplayName, setUserDisplayName] = useState(userName);
34
+ const [userId, setUserId] = useState(null);
34
35
  const { showReleaseNotification } = useConfigurations();
35
36
  const [
36
37
  { data: appInfos, status },
@@ -71,6 +72,15 @@ const AuthenticatedApp = () => {
71
72
  }
72
73
  }, [userRoles, appInfos]);
73
74
 
75
+ useEffect(() => {
76
+ const getUserId = async () => {
77
+ const userId = await hashAdminUserEmail(userInfo);
78
+ setUserId(userId);
79
+ };
80
+
81
+ getUserId();
82
+ }, [userInfo]);
83
+
74
84
  // We don't need to wait for the release query to be fetched before rendering the plugins
75
85
  // however, we need the appInfos and the permissions
76
86
  const shouldShowNotDependentQueriesLoader =
@@ -81,12 +91,13 @@ const AuthenticatedApp = () => {
81
91
  const appInfosValue = useMemo(() => {
82
92
  return {
83
93
  ...appInfos,
94
+ userId,
84
95
  latestStrapiReleaseTag: tag_name,
85
96
  setUserDisplayName,
86
97
  shouldUpdateStrapi,
87
98
  userDisplayName,
88
99
  };
89
- }, [appInfos, tag_name, shouldUpdateStrapi, userDisplayName]);
100
+ }, [appInfos, tag_name, shouldUpdateStrapi, userDisplayName, userId]);
90
101
 
91
102
  if (shouldShowLoader) {
92
103
  return <LoadingIndicatorPage />;
@@ -111,7 +111,7 @@ const DynamicZoneComponent = ({
111
111
  <StyledBox hasRadius>
112
112
  <Accordion expanded={isOpen} onToggle={handleToggle} size="S" error={errorMessage}>
113
113
  <AccordionToggle
114
- startIcon={<FontAwesomeIcon icon={icon} />}
114
+ startIcon={icon && <FontAwesomeIcon icon={icon} />}
115
115
  action={
116
116
  <Stack horizontal spacing={0} expanded={isOpen}>
117
117
  {showDownIcon && (
@@ -8,7 +8,9 @@ import set from 'lodash/set';
8
8
  import PropTypes from 'prop-types';
9
9
  import { useIntl } from 'react-intl';
10
10
  import { Prompt, Redirect } from 'react-router-dom';
11
- import { Main } from '@strapi/design-system/Main';
11
+ import { useDispatch, useSelector } from 'react-redux';
12
+
13
+ import { Main } from '@strapi/design-system';
12
14
  import {
13
15
  LoadingIndicatorPage,
14
16
  ContentManagerEditViewDataManagerContext,
@@ -20,8 +22,13 @@ import {
20
22
  } from '@strapi/helper-plugin';
21
23
 
22
24
  import { getTrad, removeKeyInObject } from '../../utils';
25
+
26
+ import selectCrudReducer from '../../sharedReducers/crudReducer/selectors';
27
+
23
28
  import reducer, { initialState } from './reducer';
24
29
  import { cleanData, createYupSchema, recursivelyFindPathsBasedOnCondition } from './utils';
30
+ import { clearSetModifiedDataOnly } from '../../sharedReducers/crudReducer/actions';
31
+ import { usePrev } from '../../hooks';
25
32
 
26
33
  const EditViewDataManagerProvider = ({
27
34
  allLayoutData,
@@ -61,6 +68,9 @@ const EditViewDataManagerProvider = ({
61
68
  publishConfirmation,
62
69
  } = reducerState;
63
70
 
71
+ const { setModifiedDataOnly } = useSelector(selectCrudReducer);
72
+ const reduxDispatch = useDispatch();
73
+
64
74
  const toggleNotification = useNotification();
65
75
  const { lockApp, unlockApp } = useOverlayBlocker();
66
76
 
@@ -144,8 +154,18 @@ const EditViewDataManagerProvider = ({
144
154
 
145
155
  const { components } = allLayoutData;
146
156
 
157
+ const previousInitialValues = usePrev(initialValues);
158
+
147
159
  useEffect(() => {
148
- if (initialValues && currentContentTypeLayout?.attributes) {
160
+ /**
161
+ * Only fire this effect if the initialValues are different
162
+ * otherwise it's a fruitless effort no matter what happens.
163
+ */
164
+ if (
165
+ initialValues &&
166
+ currentContentTypeLayout?.attributes &&
167
+ !isEqual(previousInitialValues, initialValues)
168
+ ) {
149
169
  /**
150
170
  * This will return an array of paths:
151
171
  * ['many_to_one', 'one_to_many', 'one_to_one']
@@ -179,9 +199,25 @@ const EditViewDataManagerProvider = ({
179
199
  componentPaths,
180
200
  repeatableComponentPaths,
181
201
  dynamicZonePaths,
202
+ setModifiedDataOnly,
182
203
  });
204
+
205
+ /**
206
+ * TODO: This should be moved to a side-effect e.g. thunks
207
+ * something to consider for V5
208
+ */
209
+ if (setModifiedDataOnly) {
210
+ reduxDispatch(clearSetModifiedDataOnly());
211
+ }
183
212
  }
184
- }, [initialValues, currentContentTypeLayout, components]);
213
+ }, [
214
+ initialValues,
215
+ currentContentTypeLayout,
216
+ components,
217
+ setModifiedDataOnly,
218
+ reduxDispatch,
219
+ previousInitialValues,
220
+ ]);
185
221
 
186
222
  const dispatchAddComponent = useCallback(
187
223
  (type) =>
@@ -192,6 +192,7 @@ const reducer = (state, action) =>
192
192
  componentPaths = [],
193
193
  repeatableComponentPaths = [],
194
194
  dynamicZonePaths = [],
195
+ setModifiedDataOnly,
195
196
  } = action;
196
197
 
197
198
  /**
@@ -243,7 +244,10 @@ const reducer = (state, action) =>
243
244
  return acc;
244
245
  }, data);
245
246
 
246
- draftState.initialData = mergeDataWithPreparedRelations;
247
+ if (!setModifiedDataOnly) {
248
+ draftState.initialData = mergeDataWithPreparedRelations;
249
+ }
250
+
247
251
  draftState.modifiedData = mergeDataWithPreparedRelations;
248
252
 
249
253
  draftState.formErrors = {};
@@ -55,8 +55,15 @@ const recursivelyFindPathsBasedOnConditionSetup = (components, predicate = () =>
55
55
  *
56
56
  * NOTE: we don't need to know the path to the `array` because it's about data shape not about the actual data
57
57
  */
58
- }).map((path) => path.split(`${componentName}.`)[1]);
58
+ }).map((path) => {
59
+ return path.split(`${componentName}.`)[1];
60
+ });
59
61
  })
62
+ /**
63
+ * We filter because this will give you `dynamiczone.undefined` because the dynamic_zone component
64
+ * is not required to be returned in this circumstance.
65
+ */
66
+ .filter((path) => Boolean(path))
60
67
  .map((path) => `${key}.${path}`);
61
68
 
62
69
  acc = [...acc, attributesInDynamicComponents];
@@ -9,6 +9,7 @@ import { Stack } from '@strapi/design-system/Stack';
9
9
  import { useContentTypeLayout } from '../../hooks';
10
10
  import FieldComponent from '../FieldComponent';
11
11
  import Inputs from '../Inputs';
12
+ import useLazyComponents from '../../hooks/useLazyComponents';
12
13
 
13
14
  const NonRepeatableComponent = ({ componentUid, isFromDynamicZone, isNested, name }) => {
14
15
  const { getComponentLayout } = useContentTypeLayout();
@@ -18,6 +19,8 @@ const NonRepeatableComponent = ({ componentUid, isFromDynamicZone, isNested, nam
18
19
  );
19
20
  const fields = componentLayoutData.layouts.edit;
20
21
 
22
+ const { lazyComponentStore } = useLazyComponents();
23
+
21
24
  return (
22
25
  <Box
23
26
  background={isFromDynamicZone ? '' : 'neutral100'}
@@ -67,6 +70,7 @@ const NonRepeatableComponent = ({ componentUid, isFromDynamicZone, isNested, nam
67
70
  metadatas={metadatas}
68
71
  queryInfos={queryInfos}
69
72
  size={size}
73
+ customFieldInputs={lazyComponentStore}
70
74
  />
71
75
  </GridItem>
72
76
  );
@@ -24,18 +24,13 @@ import { RELATION_ITEM_HEIGHT } from './constants';
24
24
  import { usePrev } from '../../hooks';
25
25
 
26
26
  const LinkEllipsis = styled(Link)`
27
- white-space: nowrap;
28
- overflow: hidden;
29
- text-overflow: ellipsis;
30
- display: inherit;
31
- `;
27
+ display: block;
32
28
 
33
- const BoxEllipsis = styled(Box)`
34
29
  > span {
35
30
  white-space: nowrap;
36
31
  overflow: hidden;
37
32
  text-overflow: ellipsis;
38
- display: inherit;
33
+ display: block;
39
34
  }
40
35
  `;
41
36
 
@@ -322,7 +317,7 @@ const RelationInput = ({
322
317
  }
323
318
  style={style}
324
319
  >
325
- <BoxEllipsis minWidth={0} paddingTop={1} paddingBottom={1} paddingRight={4}>
320
+ <Box minWidth={0} paddingTop={1} paddingBottom={1} paddingRight={4}>
326
321
  <Tooltip description={mainField ?? `${id}`}>
327
322
  {href ? (
328
323
  <LinkEllipsis to={href} disabled={disabled}>
@@ -334,7 +329,7 @@ const RelationInput = ({
334
329
  </Typography>
335
330
  )}
336
331
  </Tooltip>
337
- </BoxEllipsis>
332
+ </Box>
338
333
 
339
334
  {publicationState && (
340
335
  <Status variant={statusColor} showBullet={false} size="S">
@@ -40,7 +40,7 @@ export const RelationInputDataManager = ({
40
40
  const { connectRelation, disconnectRelation, loadRelation, modifiedData, slug, initialData } =
41
41
  useCMEditViewDataManager();
42
42
 
43
- const relationsFromModifiedData = get(modifiedData, name) ?? [];
43
+ const relationsFromModifiedData = get(modifiedData, name, []);
44
44
 
45
45
  const currentLastPage = Math.ceil(get(initialData, name, []).length / RELATIONS_TO_DISPLAY);
46
46
 
@@ -21,6 +21,7 @@ import Preview from './Preview';
21
21
  import DraggingSibling from './DraggingSibling';
22
22
  import { CustomIconButton } from './IconButtonCustoms';
23
23
  import { connect, select } from './utils';
24
+ import useLazyComponents from '../../../hooks/useLazyComponents';
24
25
 
25
26
  const DragButton = styled.span`
26
27
  display: flex;
@@ -177,6 +178,8 @@ const DraggedItem = ({
177
178
  const accordionTitle = toString(displayedValue);
178
179
  const accordionHasError = hasErrors ? 'error' : undefined;
179
180
 
181
+ const { lazyComponentStore } = useLazyComponents();
182
+
180
183
  return (
181
184
  <Box ref={refs ? refs.dropRef : null}>
182
185
  {isDragging && <Preview />}
@@ -273,6 +276,7 @@ const DraggedItem = ({
273
276
  // onBlur={hasErrors ? checkFormErrors : null}
274
277
  queryInfos={queryInfos}
275
278
  size={size}
279
+ customFieldInputs={lazyComponentStore}
276
280
  />
277
281
  </GridItem>
278
282
  );
@@ -103,7 +103,7 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
103
103
  setIsCreatingEntry(true);
104
104
 
105
105
  try {
106
- const { data } = await axiosInstance(getRequestUrl(`${slug}${searchToSend}`), {
106
+ const { data } = await axiosInstance.get(getRequestUrl(`${slug}${searchToSend}`), {
107
107
  cancelToken: source.token,
108
108
  });
109
109
 
@@ -43,7 +43,7 @@ const Editor = ({
43
43
  }, [editorRef, textareaRef, name, placeholder]);
44
44
 
45
45
  useEffect(() => {
46
- if (value && !editorRef.current.state.focused) {
46
+ if (value && !editorRef.current.hasFocus()) {
47
47
  editorRef.current.setValue(value);
48
48
  }
49
49
  }, [editorRef, value]);
@@ -1,44 +1,69 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useCallback, useEffect, useState } from 'react';
2
2
  import { useCustomFields } from '@strapi/helper-plugin';
3
3
 
4
+ const componentStore = new Map();
5
+
4
6
  /**
5
7
  * @description
6
8
  * A hook to lazy load custom field components
7
9
  * @param {Array.<string>} componentUids - The uids to look up components
8
10
  * @returns object
9
11
  */
10
- const useLazyComponents = (componentUids) => {
11
- const [lazyComponentStore, setLazyComponentStore] = useState({});
12
- const [loading, setLoading] = useState(true);
12
+ const useLazyComponents = (componentUids = []) => {
13
+ const [lazyComponentStore, setLazyComponentStore] = useState(Object.fromEntries(componentStore));
14
+ const [loading, setLoading] = useState(() => {
15
+ if (componentStore.size === 0 && componentUids.length > 0) {
16
+ return true;
17
+ }
18
+
19
+ return false;
20
+ });
13
21
  const customFieldsRegistry = useCustomFields();
14
22
 
15
23
  useEffect(() => {
24
+ const setStore = (store) => {
25
+ setLazyComponentStore(store);
26
+ setLoading(false);
27
+ };
28
+
16
29
  const lazyLoadComponents = async (uids, components) => {
17
30
  const modules = await Promise.all(components);
18
31
 
19
32
  uids.forEach((uid, index) => {
20
- if (!Object.keys(lazyComponentStore).includes(uid)) {
21
- setLazyComponentStore({ ...lazyComponentStore, [uid]: modules[index].default });
22
- }
33
+ componentStore.set(uid, modules[index].default);
23
34
  });
35
+
36
+ setStore(Object.fromEntries(componentStore));
24
37
  };
25
38
 
26
- if (componentUids.length) {
27
- const componentPromises = componentUids.map((uid) => {
39
+ if (componentUids.length && loading) {
40
+ /**
41
+ * These uids are not in the component store therefore we need to get the components
42
+ */
43
+ const newUids = componentUids.filter((uid) => !componentStore.get(uid));
44
+
45
+ const componentPromises = newUids.map((uid) => {
28
46
  const customField = customFieldsRegistry.get(uid);
29
47
 
30
48
  return customField.components.Input();
31
49
  });
32
50
 
33
- lazyLoadComponents(componentUids, componentPromises);
51
+ if (componentPromises.length > 0) {
52
+ lazyLoadComponents(newUids, componentPromises);
53
+ }
34
54
  }
55
+ }, [componentUids, customFieldsRegistry, loading]);
35
56
 
36
- if (componentUids.length === Object.keys(lazyComponentStore).length) {
37
- setLoading(false);
38
- }
39
- }, [componentUids, customFieldsRegistry, loading, lazyComponentStore]);
57
+ /**
58
+ * Wrap this in a callback so it can be used in
59
+ * effects to cleanup the cached store if required
60
+ */
61
+ const cleanup = useCallback(() => {
62
+ componentStore.clear();
63
+ setLazyComponentStore({});
64
+ }, []);
40
65
 
41
- return { isLazyLoading: loading, lazyComponentStore };
66
+ return { isLazyLoading: loading, lazyComponentStore, cleanup };
42
67
  };
43
68
 
44
69
  export default useLazyComponents;
@@ -6,6 +6,7 @@ import {
6
6
  SET_DATA_STRUCTURES,
7
7
  SET_STATUS,
8
8
  SUBMIT_SUCCEEDED,
9
+ CLEAR_SET_MODIFIED_DATA_ONLY,
9
10
  } from './constants';
10
11
 
11
12
  export const getData = () => {
@@ -42,3 +43,7 @@ export const submitSucceeded = (data) => ({
42
43
  type: SUBMIT_SUCCEEDED,
43
44
  data,
44
45
  });
46
+
47
+ export const clearSetModifiedDataOnly = () => ({
48
+ type: CLEAR_SET_MODIFIED_DATA_ONLY,
49
+ });
@@ -5,3 +5,5 @@ export const RESET_PROPS = 'ContentManager/CrudReducer/RESET_PROPS';
5
5
  export const SET_DATA_STRUCTURES = 'ContentManager/CrudReducer/SET_DATA_STRUCTURES';
6
6
  export const SET_STATUS = 'ContentManager/CrudReducer/SET_STATUS';
7
7
  export const SUBMIT_SUCCEEDED = 'ContentManager/CrudReducer/SUBMIT_SUCCEEDED';
8
+ export const CLEAR_SET_MODIFIED_DATA_ONLY =
9
+ 'ContentManager/CrudReducer/CLEAR_SET_MODIFIED_DATA_ONLY';
@@ -8,6 +8,7 @@ import produce from 'immer';
8
8
  // to do any of this.
9
9
 
10
10
  import {
11
+ CLEAR_SET_MODIFIED_DATA_ONLY,
11
12
  GET_DATA,
12
13
  GET_DATA_SUCCEEDED,
13
14
  INIT_FORM,
@@ -23,6 +24,7 @@ const crudInitialState = {
23
24
  isLoading: true,
24
25
  data: null,
25
26
  status: 'resolved',
27
+ setModifiedDataOnly: false,
26
28
  };
27
29
 
28
30
  const crudReducer = (state = crudInitialState, action) =>
@@ -36,6 +38,7 @@ const crudReducer = (state = crudInitialState, action) =>
36
38
  case GET_DATA_SUCCEEDED: {
37
39
  draftState.isLoading = false;
38
40
  draftState.data = action.data;
41
+ draftState.setModifiedDataOnly = action.setModifiedDataOnly ?? false;
39
42
  break;
40
43
  }
41
44
  case INIT_FORM: {
@@ -66,6 +69,10 @@ const crudReducer = (state = crudInitialState, action) =>
66
69
  draftState.data = action.data;
67
70
  break;
68
71
  }
72
+ case CLEAR_SET_MODIFIED_DATA_ONLY: {
73
+ draftState.setModifiedDataOnly = false;
74
+ break;
75
+ }
69
76
  default:
70
77
  return draftState;
71
78
  }
@@ -1,5 +1,5 @@
1
1
  import axios from 'axios';
2
- import { auth } from '@strapi/helper-plugin';
2
+ import { auth, wrapAxiosInstance } from '@strapi/helper-plugin';
3
3
 
4
4
  const instance = axios.create({
5
5
  baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
@@ -33,4 +33,6 @@ instance.interceptors.response.use(
33
33
  }
34
34
  );
35
35
 
36
- export default instance;
36
+ const wrapper = wrapAxiosInstance(instance);
37
+
38
+ export default wrapper;
@@ -10,3 +10,4 @@ export { default as usePermissionsDataManager } from './usePermissionsDataManage
10
10
  export { default as useReleaseNotification } from './useReleaseNotification';
11
11
  export { default as useThemeToggle } from './useThemeToggle';
12
12
  export { default as useRegenerate } from './useRegenerate';
13
+ export { default as useFetchClient } from './useFetchClient';
@@ -0,0 +1,23 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { getFetchClient } from '../../utils/getFetchClient';
3
+
4
+ const useFetchClient = () => {
5
+ const controller = useRef(null);
6
+
7
+ if (controller.current === null) {
8
+ controller.current = new AbortController();
9
+ }
10
+ useEffect(() => {
11
+ return () => {
12
+ controller.current.abort();
13
+ };
14
+ }, []);
15
+
16
+ const defaultOptions = {
17
+ signal: controller.current.signal,
18
+ };
19
+
20
+ return getFetchClient(defaultOptions);
21
+ };
22
+
23
+ export default useFetchClient;
@@ -25,7 +25,7 @@ import NotFoundPage from '../NotFoundPage';
25
25
  import UseCasePage from '../UseCasePage';
26
26
  import { getUID } from './utils';
27
27
  import routes from './utils/routes';
28
- import { useConfigurations } from '../../hooks';
28
+ import { useConfigurations, useFetchClient } from '../../hooks';
29
29
 
30
30
  const AuthenticatedApp = lazy(() =>
31
31
  import(/* webpackChunkName: "Admin-authenticatedApp" */ '../../components/AuthenticatedApp')
@@ -35,8 +35,12 @@ function App() {
35
35
  const toggleNotification = useNotification();
36
36
  const { updateProjectSettings } = useConfigurations();
37
37
  const { formatMessage } = useIntl();
38
- const [{ isLoading, hasAdmin, uuid }, setState] = useState({ isLoading: true, hasAdmin: false });
38
+ const [{ isLoading, hasAdmin, uuid, deviceId }, setState] = useState({
39
+ isLoading: true,
40
+ hasAdmin: false,
41
+ });
39
42
  const appInfo = useAppInfos();
43
+ const { get } = useFetchClient();
40
44
 
41
45
  const authRoutes = useMemo(() => {
42
46
  return makeUniqueRoutes(
@@ -80,27 +84,29 @@ function App() {
80
84
  } = await axios.get(`${strapi.backendURL}/admin/init`);
81
85
 
82
86
  updateProjectSettings({ menuLogo: prefixFileUrlWithBackendUrl(menuLogo) });
87
+ const deviceId = await getUID();
83
88
 
84
89
  if (uuid) {
85
90
  const {
86
91
  data: { data: properties },
87
- } = await axios.get(`${strapi.backendURL}/admin/telemetry-properties`);
92
+ } = await get(`/admin/telemetry-properties`, {
93
+ // NOTE: needed because the interceptors of the fetchClient redirect to /login when receive a 401 and it would end up in an infinite loop when the user doesn't have a session.
94
+ validateStatus: (status) => status < 500,
95
+ });
88
96
 
89
97
  setTelemetryProperties(properties);
90
98
 
91
99
  try {
92
- const deviceId = await getUID();
93
-
94
- await fetch('https://analytics.strapi.io/track', {
100
+ await fetch('https://analytics.strapi.io/api/v2/track', {
95
101
  method: 'POST',
96
102
  body: JSON.stringify({
103
+ // This event is anonymous
97
104
  event: 'didInitializeAdministration',
98
- uuid,
105
+ userId: '',
99
106
  deviceId,
100
- properties: {
101
- ...properties,
102
- environment: appInfo.currentEnvironment,
103
- },
107
+ eventPropeties: {},
108
+ userProperties: { environment: appInfo.currentEnvironment },
109
+ groupProperties: { ...properties, projectId: uuid },
104
110
  }),
105
111
  headers: {
106
112
  'Content-Type': 'application/json',
@@ -111,7 +117,7 @@ function App() {
111
117
  }
112
118
  }
113
119
 
114
- setState({ isLoading: false, hasAdmin, uuid });
120
+ setState({ isLoading: false, hasAdmin, uuid, deviceId });
115
121
  } catch (err) {
116
122
  toggleNotification({
117
123
  type: 'warning',
@@ -130,8 +136,9 @@ function App() {
130
136
  () => ({
131
137
  uuid,
132
138
  telemetryProperties,
139
+ deviceId,
133
140
  }),
134
- [uuid, telemetryProperties]
141
+ [uuid, telemetryProperties, deviceId]
135
142
  );
136
143
 
137
144
  if (isLoading) {
@@ -33,13 +33,13 @@ const StyledReddit = styled(Reddit)`
33
33
  `;
34
34
  const StyledStrapi = styled(Strapi)`
35
35
  > path:first-child {
36
- fill: #8e75ff;
36
+ fill: #4945ff;
37
37
  }
38
38
  > path:nth-child(2) {
39
- fill: #8e75ff;
39
+ fill: #fff;
40
40
  }
41
- > path:nth-child(3) {
42
- fill: #8e75ff;
41
+ > path:nth-child(4) {
42
+ fill: #9593ff;
43
43
  }
44
44
  `;
45
45
 
@@ -12,7 +12,6 @@ import {
12
12
  } from '@strapi/helper-plugin';
13
13
  import { Main } from '@strapi/design-system/Main';
14
14
  import { Formik } from 'formik';
15
- import { get } from 'lodash';
16
15
  import { useRouteMatch, useHistory } from 'react-router-dom';
17
16
  import { useQuery } from 'react-query';
18
17
  import { formatAPIErrors } from '../../../../../utils';
@@ -205,12 +204,12 @@ const ApiTokenCreateView = () => {
205
204
  if (err?.response?.data?.error?.message === MSG_ERROR_NAME_TAKEN) {
206
205
  toggleNotification({
207
206
  type: 'warning',
208
- message: get(err, 'response.data.message', 'notification.error.tokennamenotunique'),
207
+ message: err.response.data.message || 'notification.error.tokennamenotunique',
209
208
  });
210
209
  } else {
211
210
  toggleNotification({
212
211
  type: 'warning',
213
- message: get(err, 'response.data.message', 'notification.error'),
212
+ message: err?.response?.data?.message || 'notification.error',
214
213
  });
215
214
  }
216
215
  unlockApp();
@@ -34,27 +34,32 @@ const ModalForm = ({ queryName, onToggle }) => {
34
34
  const { formatMessage } = useIntl();
35
35
  const toggleNotification = useNotification();
36
36
  const { lockApp, unlockApp } = useOverlayBlocker();
37
- const postMutation = useMutation((body) => axiosInstance.post('/admin/users', body), {
38
- async onSuccess({ data }) {
39
- setRegistrationToken(data.data.registrationToken);
40
- await queryClient.invalidateQueries(queryName);
41
- goNext();
42
- setIsSubmitting(false);
37
+ const postMutation = useMutation(
38
+ (body) => {
39
+ return axiosInstance.post('/admin/users', body);
43
40
  },
44
- onError(err) {
45
- setIsSubmitting(false);
41
+ {
42
+ async onSuccess({ data }) {
43
+ setRegistrationToken(data.data.registrationToken);
44
+ await queryClient.invalidateQueries(queryName);
45
+ goNext();
46
+ setIsSubmitting(false);
47
+ },
48
+ onError(err) {
49
+ setIsSubmitting(false);
46
50
 
47
- toggleNotification({
48
- type: 'warning',
49
- message: { id: 'notification.error', defaultMessage: 'An error occured' },
50
- });
51
+ toggleNotification({
52
+ type: 'warning',
53
+ message: { id: 'notification.error', defaultMessage: 'An error occured' },
54
+ });
51
55
 
52
- throw err;
53
- },
54
- onSettled() {
55
- unlockApp();
56
- },
57
- });
56
+ throw err;
57
+ },
58
+ onSettled() {
59
+ unlockApp();
60
+ },
61
+ }
62
+ );
58
63
 
59
64
  const headerTitle = formatMessage({
60
65
  id: 'Settings.permissions.users.create',