@strapi/admin 4.4.0-beta.4 → 4.4.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/admin/src/StrapiApp.js +2 -0
  2. package/admin/src/components/AuthenticatedApp/index.js +2 -2
  3. package/admin/src/components/LanguageProvider/index.js +1 -0
  4. package/admin/src/components/ThemeToggleProvider/index.js +21 -10
  5. package/admin/src/content-manager/components/InputJSON/index.js +2 -0
  6. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormApiTokenContainer/index.js +254 -0
  7. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormBody/index.js +77 -0
  8. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormHead/index.js +85 -0
  9. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +19 -271
  10. package/admin/src/translations/zh-Hans.json +0 -1
  11. package/build/2077.c935ee42.chunk.js +205 -0
  12. package/build/4318.7d167b58.chunk.js +30 -0
  13. package/build/{4715.3f6cac0a.chunk.js → 4715.58cd558f.chunk.js} +32 -31
  14. package/build/4982.05eda880.chunk.js +324 -0
  15. package/build/{7866.c793a31d.chunk.js → 7866.1201afbd.chunk.js} +2 -2
  16. package/build/{8773.eccaa5f3.chunk.js → 8773.c06c24c0.chunk.js} +2 -2
  17. package/build/{Admin-authenticatedApp.6ad28580.chunk.js → Admin-authenticatedApp.9dec5230.chunk.js} +6 -6
  18. package/build/{Admin_settingsPage.fc9c607a.chunk.js → Admin_settingsPage.98e2a62b.chunk.js} +1 -1
  19. package/build/{admin-app.7b7f9463.chunk.js → admin-app.a61d5c2e.chunk.js} +17 -17
  20. package/build/api-tokens-create-page.93dd0689.chunk.js +1 -0
  21. package/build/api-tokens-edit-page.b0adac81.chunk.js +1 -0
  22. package/build/{content-manager.5ac9916a.chunk.js → content-manager.feb0d540.chunk.js} +1 -1
  23. package/build/{content-type-builder.d4610e20.chunk.js → content-type-builder.a684b2e8.chunk.js} +11 -11
  24. package/build/index.html +1 -1
  25. package/build/{main.c04d580d.js → main.e4065f58.js} +1304 -1288
  26. package/build/{runtime~main.3bd4f055.js → runtime~main.4204f341.js} +1 -1
  27. package/build/{zh-Hans-json.77a42bc5.chunk.js → zh-Hans-json.9c99f8d4.chunk.js} +1 -1
  28. package/package.json +9 -9
  29. package/build/2077.61cebc93.chunk.js +0 -195
  30. package/build/4235.c44d8565.chunk.js +0 -30
  31. package/build/4982.c6f88c5d.chunk.js +0 -314
  32. package/build/api-tokens-create-page.29cc87b6.chunk.js +0 -1
  33. package/build/api-tokens-edit-page.c294a88f.chunk.js +0 -1
@@ -22,6 +22,7 @@ import {
22
22
  } from './exposedHooks';
23
23
  import injectionZones from './injectionZones';
24
24
  import favicon from './favicon.ico';
25
+ import localStorageKey from './components/LanguageProvider/utils/localStorageKey';
25
26
 
26
27
  class StrapiApp {
27
28
  constructor({ adminConfig, appPlugins, library, middlewares, reducers }) {
@@ -449,6 +450,7 @@ class StrapiApp {
449
450
  href: this.configurations.head.favicon,
450
451
  },
451
452
  ]}
453
+ htmlAttributes={{ lang: localStorage.getItem(localStorageKey) || 'en' }}
452
454
  />
453
455
  <BrowserRouter basename={basename}>
454
456
  <App store={store} />
@@ -65,11 +65,11 @@ const AuthenticatedApp = () => {
65
65
  if (userRoles) {
66
66
  const isUserSuperAdmin = userRoles.find(({ code }) => code === 'strapi-super-admin');
67
67
 
68
- if (isUserSuperAdmin) {
68
+ if (isUserSuperAdmin && appInfos?.autoReload) {
69
69
  setGuidedTourVisibilityRef.current(true);
70
70
  }
71
71
  }
72
- }, [userRoles]);
72
+ }, [userRoles, appInfos]);
73
73
 
74
74
  // We don't need to wait for the release query to be fetched before rendering the plugins
75
75
  // however, we need the appInfos and the permissions
@@ -21,6 +21,7 @@ const LanguageProvider = ({ children, localeNames, messages }) => {
21
21
  useEffect(() => {
22
22
  // Set user language in local storage.
23
23
  window.localStorage.setItem(localStorageKey, locale);
24
+ document.documentElement.setAttribute('lang', locale);
24
25
  }, [locale]);
25
26
 
26
27
  const changeLocale = (locale) => {
@@ -4,7 +4,7 @@
4
4
  *
5
5
  */
6
6
 
7
- import React, { useState } from 'react';
7
+ import React, { useState, useMemo, useCallback } from 'react';
8
8
  import PropTypes from 'prop-types';
9
9
  import { ThemeToggleContext } from '../../contexts';
10
10
 
@@ -14,22 +14,33 @@ const getDefaultTheme = () => {
14
14
  const browserTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
15
15
  const persistedTheme = localStorage.getItem(THEME_KEY);
16
16
 
17
+ if (!persistedTheme) {
18
+ localStorage.setItem(THEME_KEY, browserTheme);
19
+ }
20
+
17
21
  return persistedTheme || browserTheme;
18
22
  };
19
23
 
20
24
  const ThemeToggleProvider = ({ children, themes }) => {
21
25
  const [currentTheme, setCurrentTheme] = useState(getDefaultTheme());
22
26
 
23
- const handleChangeTheme = (nextTheme) => {
24
- setCurrentTheme(nextTheme);
25
- localStorage.setItem(THEME_KEY, nextTheme);
26
- };
27
-
28
- return (
29
- <ThemeToggleContext.Provider value={{ currentTheme, onChangeTheme: handleChangeTheme, themes }}>
30
- {children}
31
- </ThemeToggleContext.Provider>
27
+ const handleChangeTheme = useCallback(
28
+ (nextTheme) => {
29
+ setCurrentTheme(nextTheme);
30
+ localStorage.setItem(THEME_KEY, nextTheme);
31
+ },
32
+ [setCurrentTheme]
32
33
  );
34
+
35
+ const themeValues = useMemo(() => {
36
+ return {
37
+ currentTheme,
38
+ onChangeTheme: handleChangeTheme,
39
+ themes,
40
+ };
41
+ }, [currentTheme, handleChangeTheme, themes]);
42
+
43
+ return <ThemeToggleContext.Provider value={themeValues}>{children}</ThemeToggleContext.Provider>;
33
44
  };
34
45
 
35
46
  ThemeToggleProvider.propTypes = {
@@ -79,6 +79,8 @@ class InputJSON extends React.Component {
79
79
  try {
80
80
  if (value === null) return this.codeMirror.setValue('');
81
81
 
82
+ if (value === 0) return this.codeMirror.setValue('0');
83
+
82
84
  return this.codeMirror.setValue(value);
83
85
  } catch (err) {
84
86
  return this.setState({ error: true });
@@ -0,0 +1,254 @@
1
+ import React from 'react';
2
+ import { useIntl } from 'react-intl';
3
+ import { usePersistentState } from '@strapi/helper-plugin';
4
+ import PropTypes from 'prop-types';
5
+ import { Box } from '@strapi/design-system/Box';
6
+ import { Grid, GridItem } from '@strapi/design-system/Grid';
7
+ import { Select, Option } from '@strapi/design-system/Select';
8
+ import { Stack } from '@strapi/design-system/Stack';
9
+ import { Textarea } from '@strapi/design-system/Textarea';
10
+ import { TextInput } from '@strapi/design-system/TextInput';
11
+ import { Typography } from '@strapi/design-system/Typography';
12
+ import { getDateOfExpiration } from '../../utils';
13
+
14
+ const FormApiTokenContainer = ({
15
+ errors,
16
+ onChange,
17
+ canEditInputs,
18
+ isCreating,
19
+ values,
20
+ apiToken,
21
+ onDispatch,
22
+ setHasChangedPermissions,
23
+ }) => {
24
+ const { formatMessage } = useIntl();
25
+ const [lang] = usePersistentState('strapi-admin-language', 'en');
26
+
27
+ const handleChangeSelectApiTokenType = ({ target: { value } }) => {
28
+ setHasChangedPermissions(false);
29
+
30
+ if (value === 'full-access') {
31
+ onDispatch({
32
+ type: 'SELECT_ALL_ACTIONS',
33
+ });
34
+ }
35
+ if (value === 'read-only') {
36
+ onDispatch({
37
+ type: 'ON_CHANGE_READ_ONLY',
38
+ });
39
+ }
40
+ };
41
+
42
+ return (
43
+ <Box
44
+ background="neutral0"
45
+ hasRadius
46
+ shadow="filterShadow"
47
+ paddingTop={6}
48
+ paddingBottom={6}
49
+ paddingLeft={7}
50
+ paddingRight={7}
51
+ >
52
+ <Stack spacing={4}>
53
+ <Typography variant="delta" as="h2">
54
+ {formatMessage({
55
+ id: 'global.details',
56
+ defaultMessage: 'Details',
57
+ })}
58
+ </Typography>
59
+ <Grid gap={5}>
60
+ <GridItem key="name" col={6} xs={12}>
61
+ <TextInput
62
+ name="name"
63
+ error={
64
+ errors.name
65
+ ? formatMessage(
66
+ errors.name?.id
67
+ ? errors.name
68
+ : { id: errors.name, defaultMessage: errors.name }
69
+ )
70
+ : null
71
+ }
72
+ label={formatMessage({
73
+ id: 'Settings.apiTokens.form.name',
74
+ defaultMessage: 'Name',
75
+ })}
76
+ onChange={onChange}
77
+ value={values.name}
78
+ disabled={!canEditInputs}
79
+ required
80
+ />
81
+ </GridItem>
82
+ <GridItem key="description" col={6} xs={12}>
83
+ <Textarea
84
+ label={formatMessage({
85
+ id: 'Settings.apiTokens.form.description',
86
+ defaultMessage: 'Description',
87
+ })}
88
+ name="description"
89
+ error={
90
+ errors.description
91
+ ? formatMessage(
92
+ errors.description?.id
93
+ ? errors.description
94
+ : {
95
+ id: errors.description,
96
+ defaultMessage: errors.description,
97
+ }
98
+ )
99
+ : null
100
+ }
101
+ onChange={onChange}
102
+ disabled={!canEditInputs}
103
+ >
104
+ {values.description}
105
+ </Textarea>
106
+ </GridItem>
107
+ <GridItem key="lifespan" col={6} xs={12}>
108
+ <Select
109
+ name="lifespan"
110
+ label={formatMessage({
111
+ id: 'Settings.apiTokens.form.duration',
112
+ defaultMessage: 'Token duration',
113
+ })}
114
+ value={values.lifespan}
115
+ error={
116
+ errors.lifespan
117
+ ? formatMessage(
118
+ errors.lifespan?.id
119
+ ? errors.lifespan
120
+ : { id: errors.lifespan, defaultMessage: errors.lifespan }
121
+ )
122
+ : null
123
+ }
124
+ onChange={(value) => {
125
+ onChange({ target: { name: 'lifespan', value } });
126
+ }}
127
+ required
128
+ disabled={!isCreating}
129
+ placeholder="Select"
130
+ >
131
+ <Option value="604800000">
132
+ {formatMessage({
133
+ id: 'Settings.apiTokens.duration.7-days',
134
+ defaultMessage: '7 days',
135
+ })}
136
+ </Option>
137
+ <Option value="2592000000">
138
+ {formatMessage({
139
+ id: 'Settings.apiTokens.duration.30-days',
140
+ defaultMessage: '30 days',
141
+ })}
142
+ </Option>
143
+ <Option value="7776000000">
144
+ {formatMessage({
145
+ id: 'Settings.apiTokens.duration.90-days',
146
+ defaultMessage: '90 days',
147
+ })}
148
+ </Option>
149
+ <Option value={null}>
150
+ {formatMessage({
151
+ id: 'Settings.apiTokens.duration.unlimited',
152
+ defaultMessage: 'Unlimited',
153
+ })}
154
+ </Option>
155
+ </Select>
156
+ <Typography variant="pi" textColor="neutral600">
157
+ {!isCreating &&
158
+ `${formatMessage({
159
+ id: 'Settings.apiTokens.duration.expiration-date',
160
+ defaultMessage: 'Expiration date',
161
+ })}: ${getDateOfExpiration(
162
+ apiToken?.createdAt,
163
+ parseInt(values.lifespan, 10),
164
+ lang
165
+ )}`}
166
+ </Typography>
167
+ </GridItem>
168
+
169
+ <GridItem key="type" col={6} xs={12}>
170
+ <Select
171
+ name="type"
172
+ label={formatMessage({
173
+ id: 'Settings.apiTokens.form.type',
174
+ defaultMessage: 'Token type',
175
+ })}
176
+ value={values?.type}
177
+ error={
178
+ errors.type
179
+ ? formatMessage(
180
+ errors.type?.id
181
+ ? errors.type
182
+ : { id: errors.type, defaultMessage: errors.type }
183
+ )
184
+ : null
185
+ }
186
+ onChange={(value) => {
187
+ handleChangeSelectApiTokenType({ target: { value } });
188
+ onChange({ target: { name: 'type', value } });
189
+ }}
190
+ placeholder="Select"
191
+ required
192
+ disabled={!canEditInputs}
193
+ >
194
+ <Option value="read-only">
195
+ {formatMessage({
196
+ id: 'Settings.apiTokens.types.read-only',
197
+ defaultMessage: 'Read-only',
198
+ })}
199
+ </Option>
200
+ <Option value="full-access">
201
+ {formatMessage({
202
+ id: 'Settings.apiTokens.types.full-access',
203
+ defaultMessage: 'Full access',
204
+ })}
205
+ </Option>
206
+ <Option value="custom">
207
+ {formatMessage({
208
+ id: 'Settings.apiTokens.types.custom',
209
+ defaultMessage: 'Custom',
210
+ })}
211
+ </Option>
212
+ </Select>
213
+ </GridItem>
214
+ </Grid>
215
+ </Stack>
216
+ </Box>
217
+ );
218
+ };
219
+
220
+ FormApiTokenContainer.propTypes = {
221
+ errors: PropTypes.shape({
222
+ name: PropTypes.string,
223
+ description: PropTypes.string,
224
+ lifespan: PropTypes.string,
225
+ type: PropTypes.string,
226
+ }),
227
+ onChange: PropTypes.func.isRequired,
228
+ canEditInputs: PropTypes.bool.isRequired,
229
+ values: PropTypes.shape({
230
+ name: PropTypes.string,
231
+ description: PropTypes.string,
232
+ lifespan: PropTypes.string,
233
+ type: PropTypes.string,
234
+ }).isRequired,
235
+ isCreating: PropTypes.bool.isRequired,
236
+ apiToken: PropTypes.shape({
237
+ id: PropTypes.string,
238
+ type: PropTypes.string,
239
+ lifespan: PropTypes.number,
240
+ name: PropTypes.string,
241
+ accessKey: PropTypes.string,
242
+ permissions: PropTypes.array,
243
+ description: PropTypes.string,
244
+ createdAt: PropTypes.string,
245
+ }).isRequired,
246
+ onDispatch: PropTypes.func.isRequired,
247
+ setHasChangedPermissions: PropTypes.func.isRequired,
248
+ };
249
+
250
+ FormApiTokenContainer.defaultProps = {
251
+ errors: {},
252
+ };
253
+
254
+ export default FormApiTokenContainer;
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { ContentLayout } from '@strapi/design-system/Layout';
4
+ import { Stack } from '@strapi/design-system/Stack';
5
+ import HeaderContentBox from '../ContentBox';
6
+ import FormApiTokenContainer from '../FormApiTokenContainer';
7
+ import Permissions from '../Permissions';
8
+
9
+ const FormBody = ({
10
+ apiToken,
11
+ errors,
12
+ onChange,
13
+ canEditInputs,
14
+ isCreating,
15
+ values,
16
+ onDispatch,
17
+ setHasChangedPermissions,
18
+ }) => {
19
+ return (
20
+ <ContentLayout>
21
+ <Stack spacing={6}>
22
+ {Boolean(apiToken?.name) && <HeaderContentBox apiToken={apiToken.accessKey} />}
23
+ <FormApiTokenContainer
24
+ errors={errors}
25
+ onChange={onChange}
26
+ canEditInputs={canEditInputs}
27
+ isCreating={isCreating}
28
+ values={values}
29
+ apiToken={apiToken}
30
+ onDispatch={onDispatch}
31
+ setHasChangedPermissions={setHasChangedPermissions}
32
+ />
33
+ <Permissions
34
+ disabled={
35
+ !canEditInputs || values?.type === 'read-only' || values?.type === 'full-access'
36
+ }
37
+ />
38
+ </Stack>
39
+ </ContentLayout>
40
+ );
41
+ };
42
+
43
+ FormBody.propTypes = {
44
+ errors: PropTypes.shape({
45
+ name: PropTypes.string,
46
+ description: PropTypes.string,
47
+ lifespan: PropTypes.string,
48
+ type: PropTypes.string,
49
+ }),
50
+ apiToken: PropTypes.shape({
51
+ id: PropTypes.string,
52
+ type: PropTypes.string,
53
+ lifespan: PropTypes.number,
54
+ name: PropTypes.string,
55
+ accessKey: PropTypes.string,
56
+ permissions: PropTypes.array,
57
+ description: PropTypes.string,
58
+ createdAt: PropTypes.string,
59
+ }).isRequired,
60
+ onChange: PropTypes.func.isRequired,
61
+ canEditInputs: PropTypes.bool.isRequired,
62
+ isCreating: PropTypes.bool.isRequired,
63
+ values: PropTypes.shape({
64
+ name: PropTypes.string,
65
+ description: PropTypes.string,
66
+ lifespan: PropTypes.string,
67
+ type: PropTypes.string,
68
+ }).isRequired,
69
+ onDispatch: PropTypes.func.isRequired,
70
+ setHasChangedPermissions: PropTypes.func.isRequired,
71
+ };
72
+
73
+ FormBody.defaultProps = {
74
+ errors: {},
75
+ };
76
+
77
+ export default FormBody;
@@ -0,0 +1,85 @@
1
+ import React from 'react';
2
+ import { useIntl } from 'react-intl';
3
+ import { Link } from '@strapi/helper-plugin';
4
+ import PropTypes from 'prop-types';
5
+ import ArrowLeft from '@strapi/icons/ArrowLeft';
6
+ import Check from '@strapi/icons/Check';
7
+ import { Button } from '@strapi/design-system/Button';
8
+ import { HeaderLayout } from '@strapi/design-system/Layout';
9
+ import { Stack } from '@strapi/design-system/Stack';
10
+ import Regenerate from '../Regenerate';
11
+
12
+ const FormHead = ({ apiToken, setApiToken, canEditInputs, canRegenerate, isSubmitting }) => {
13
+ const { formatMessage } = useIntl();
14
+ const handleRegenerate = (newKey) => {
15
+ setApiToken({
16
+ ...apiToken,
17
+ accessKey: newKey,
18
+ });
19
+ };
20
+
21
+ return (
22
+ <HeaderLayout
23
+ title={
24
+ apiToken?.name ||
25
+ formatMessage({
26
+ id: 'Settings.apiTokens.createPage.title',
27
+ defaultMessage: 'Create API Token',
28
+ })
29
+ }
30
+ primaryAction={
31
+ canEditInputs ? (
32
+ <Stack horizontal spacing={2}>
33
+ {canRegenerate && apiToken?.id && (
34
+ <Regenerate onRegenerate={handleRegenerate} idToRegenerate={apiToken?.id} />
35
+ )}
36
+ <Button
37
+ disabled={isSubmitting}
38
+ loading={isSubmitting}
39
+ startIcon={<Check />}
40
+ type="submit"
41
+ size="S"
42
+ >
43
+ {formatMessage({
44
+ id: 'global.save',
45
+ defaultMessage: 'Save',
46
+ })}
47
+ </Button>
48
+ </Stack>
49
+ ) : (
50
+ canRegenerate &&
51
+ apiToken?.id && (
52
+ <Regenerate onRegenerate={handleRegenerate} idToRegenerate={apiToken?.id} />
53
+ )
54
+ )
55
+ }
56
+ navigationAction={
57
+ <Link startIcon={<ArrowLeft />} to="/settings/api-tokens">
58
+ {formatMessage({
59
+ id: 'global.back',
60
+ defaultMessage: 'Back',
61
+ })}
62
+ </Link>
63
+ }
64
+ />
65
+ );
66
+ };
67
+
68
+ FormHead.propTypes = {
69
+ apiToken: PropTypes.shape({
70
+ id: PropTypes.string,
71
+ type: PropTypes.string,
72
+ lifespan: PropTypes.number,
73
+ name: PropTypes.string,
74
+ accessKey: PropTypes.string,
75
+ permissions: PropTypes.array,
76
+ description: PropTypes.string,
77
+ createdAt: PropTypes.string,
78
+ }).isRequired,
79
+ canEditInputs: PropTypes.bool.isRequired,
80
+ canRegenerate: PropTypes.bool.isRequired,
81
+ setApiToken: PropTypes.func.isRequired,
82
+ isSubmitting: PropTypes.bool.isRequired,
83
+ };
84
+
85
+ export default FormHead;