@strapi/admin 4.1.9 → 4.1.10-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.
Files changed (47) hide show
  1. package/admin/src/components/ConfigurationsProvider/index.js +51 -0
  2. package/admin/src/components/ConfigurationsProvider/reducer.js +28 -0
  3. package/admin/src/components/LeftMenu/index.js +4 -2
  4. package/admin/src/components/Providers/index.js +8 -4
  5. package/admin/src/components/UnauthenticatedLogo/index.js +4 -2
  6. package/admin/src/pages/App/index.js +7 -2
  7. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/Form/index.js +85 -0
  8. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/Form/init.js +13 -0
  9. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/Form/reducer.js +43 -0
  10. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoInput/index.js +116 -0
  11. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoInput/reducer.js +28 -0
  12. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoInput/stepper.js +25 -0
  13. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoModalStepper/AddLogoDialog.js +67 -0
  14. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoModalStepper/FromComputerForm.js +176 -0
  15. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoModalStepper/FromUrlForm.js +82 -0
  16. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoModalStepper/ImageCardAsset.js +51 -0
  17. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoModalStepper/PendingLogoDialog.js +97 -0
  18. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoModalStepper/index.js +85 -0
  19. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/LogoModalStepper/reducer.js +28 -0
  20. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/index.js +149 -87
  21. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/utils/api.js +16 -0
  22. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/utils/constants.js +3 -0
  23. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/utils/getFormData.js +17 -0
  24. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/utils/parseFileMetadatas.js +76 -0
  25. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/utils/prefixAllUrls.js +17 -0
  26. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/utils/urlToFile.js +21 -0
  27. package/admin/src/translations/en.json +25 -0
  28. package/build/7197.959bbe97.chunk.js +113 -0
  29. package/build/{9298.5b5c6ea1.chunk.js → 9298.f97fcef0.chunk.js} +2 -2
  30. package/build/Admin-authenticatedApp.d8767873.chunk.js +80 -0
  31. package/build/Admin_settingsPage.0d94a598.chunk.js +180 -0
  32. package/build/en-json.ca572384.chunk.js +1 -0
  33. package/build/index.html +1 -1
  34. package/build/{main.25315363.js → main.06f66609.js} +1 -1
  35. package/build/{runtime~main.1ecd9f7d.js → runtime~main.dc1c7ef6.js} +1 -1
  36. package/package.json +5 -5
  37. package/server/config/admin-actions.js +14 -0
  38. package/server/controllers/admin.js +33 -1
  39. package/server/routes/admin.js +28 -0
  40. package/server/services/index.js +1 -0
  41. package/server/services/project-settings.js +173 -0
  42. package/server/utils/index.d.ts +2 -0
  43. package/server/validation/project-settings.js +39 -0
  44. package/build/6706.b0b5124d.chunk.js +0 -113
  45. package/build/Admin-authenticatedApp.72cf6aa3.chunk.js +0 -80
  46. package/build/Admin_settingsPage.f83a7c21.chunk.js +0 -172
  47. package/build/en-json.3e1a222e.chunk.js +0 -1
@@ -1,26 +1,76 @@
1
- import React from 'react';
1
+ import React, { useRef } from 'react';
2
+ import { useQuery, useMutation, useQueryClient } from 'react-query';
2
3
  import { useIntl } from 'react-intl';
3
- import { useAppInfos, SettingsPageTitle, useFocusWhenNavigate } from '@strapi/helper-plugin';
4
+ import {
5
+ useAppInfos,
6
+ SettingsPageTitle,
7
+ useFocusWhenNavigate,
8
+ CheckPermissions,
9
+ useNotification,
10
+ useTracking,
11
+ } from '@strapi/helper-plugin';
4
12
  import { HeaderLayout, Layout, ContentLayout } from '@strapi/design-system/Layout';
5
13
  import { Main } from '@strapi/design-system/Main';
6
14
  import { Box } from '@strapi/design-system/Box';
7
15
  import { Grid, GridItem } from '@strapi/design-system/Grid';
8
16
  import { Typography } from '@strapi/design-system/Typography';
9
-
10
17
  import { Stack } from '@strapi/design-system/Stack';
11
18
  import { Link } from '@strapi/design-system/Link';
19
+ import { Button } from '@strapi/design-system/Button';
12
20
  import ExternalLink from '@strapi/icons/ExternalLink';
21
+ import Check from '@strapi/icons/Check';
22
+ import { useConfigurations } from '../../../../hooks';
23
+ import Form from './components/Form';
24
+ import { fetchProjectSettings, postProjectSettings } from './utils/api';
25
+ import getFormData from './utils/getFormData';
26
+
27
+ const permissions = [{ action: 'admin::project-settings.update', subject: null }];
13
28
 
14
29
  const ApplicationInfosPage = () => {
30
+ const inputsRef = useRef();
31
+ const toggleNotification = useNotification();
32
+ const { trackUsage } = useTracking();
15
33
  const { formatMessage } = useIntl();
34
+ const queryClient = useQueryClient();
16
35
  useFocusWhenNavigate();
17
36
  const appInfos = useAppInfos();
18
37
  const { shouldUpdateStrapi, latestStrapiReleaseTag, strapiVersion } = appInfos;
38
+ const { updateProjectSettings } = useConfigurations();
39
+
40
+ const { data } = useQuery('project-settings', fetchProjectSettings);
19
41
 
20
42
  const currentPlan = appInfos.communityEdition
21
43
  ? 'app.components.UpgradePlanModal.text-ce'
22
44
  : 'app.components.UpgradePlanModal.text-ee';
23
45
 
46
+ const submitMutation = useMutation(body => postProjectSettings(body), {
47
+ onSuccess: async ({ menuLogo }) => {
48
+ await queryClient.invalidateQueries('project-settings', { refetchActive: true });
49
+ updateProjectSettings({ menuLogo: menuLogo?.url });
50
+ },
51
+ });
52
+
53
+ const handleSubmit = () => {
54
+ const inputValues = inputsRef.current.getValues();
55
+ const formData = getFormData(inputValues);
56
+
57
+ submitMutation.mutate(formData, {
58
+ onSuccess: () => {
59
+ const { menuLogo } = inputValues;
60
+
61
+ if (menuLogo.rawFile) {
62
+ trackUsage('didChangeLogo');
63
+ }
64
+ },
65
+ onError: () => {
66
+ toggleNotification({
67
+ type: 'warning',
68
+ message: { id: 'notification.error', defaultMessage: 'An error occurred' },
69
+ });
70
+ },
71
+ });
72
+ };
73
+
24
74
  return (
25
75
  <Layout>
26
76
  <SettingsPageTitle name="Application" />
@@ -31,101 +81,113 @@ const ApplicationInfosPage = () => {
31
81
  id: 'Settings.application.description',
32
82
  defaultMessage: 'Administration panel’s global information',
33
83
  })}
84
+ primaryAction={
85
+ <Button onClick={handleSubmit} startIcon={<Check />}>
86
+ {formatMessage({ id: 'global.save', defaultMessage: 'Save' })}
87
+ </Button>
88
+ }
34
89
  />
35
90
  <ContentLayout>
36
- <Box
37
- hasRadius
38
- background="neutral0"
39
- shadow="tableShadow"
40
- paddingTop={7}
41
- paddingBottom={7}
42
- paddingRight={6}
43
- paddingLeft={6}
44
- >
45
- <Stack spacing={5}>
46
- <Typography variant="delta" as="h3">
47
- {formatMessage({
48
- id: 'global.details',
49
- defaultMessage: 'Details',
50
- })}
51
- </Typography>
52
-
53
- <Grid paddingTop={1}>
54
- <GridItem col={6} s={12}>
55
- <Typography variant="sigma" textColor="neutral600">
56
- {formatMessage({
57
- id: 'Settings.application.strapiVersion',
58
- defaultMessage: 'strapi version',
59
- })}
60
- </Typography>
61
- <Typography as="p">v{strapiVersion}</Typography>
62
- <Link
63
- href={
64
- appInfos.communityEdition
65
- ? 'https://discord.strapi.io'
66
- : 'https://support.strapi.io/support/home'
67
- }
68
- endIcon={<ExternalLink />}
69
- >
70
- {formatMessage({
71
- id: 'Settings.application.get-help',
72
- defaultMessage: 'Get help',
73
- })}
74
- </Link>
75
- </GridItem>
76
- <GridItem col={6} s={12}>
77
- <Typography variant="sigma" textColor="neutral600">
78
- {formatMessage({
79
- id: 'Settings.application.edition-title',
80
- defaultMessage: 'current plan',
81
- })}
82
- </Typography>
83
- <Typography as="p">
84
- {formatMessage({
85
- id: currentPlan,
86
- defaultMessage: `${
87
- appInfos.communityEdition ? 'Community Edition' : 'Enterprise Edition'
88
- }`,
89
- })}
90
- </Typography>
91
- </GridItem>
92
- </Grid>
91
+ <Stack spacing={6}>
92
+ <Box
93
+ hasRadius
94
+ background="neutral0"
95
+ shadow="tableShadow"
96
+ paddingTop={6}
97
+ paddingBottom={6}
98
+ paddingRight={7}
99
+ paddingLeft={7}
100
+ >
101
+ <Stack spacing={5}>
102
+ <Typography variant="delta" as="h3">
103
+ {formatMessage({
104
+ id: 'global.details',
105
+ defaultMessage: 'Details',
106
+ })}
107
+ </Typography>
93
108
 
94
- <Grid paddingTop={1}>
95
- <GridItem col={6} s={12}>
96
- {shouldUpdateStrapi && (
109
+ <Grid paddingTop={1}>
110
+ <GridItem col={6} s={12}>
111
+ <Typography variant="sigma" textColor="neutral600">
112
+ {formatMessage({
113
+ id: 'Settings.application.strapiVersion',
114
+ defaultMessage: 'strapi version',
115
+ })}
116
+ </Typography>
117
+ <Typography as="p">v{strapiVersion}</Typography>
97
118
  <Link
98
- href={`https://github.com/strapi/strapi/releases/tag/${latestStrapiReleaseTag}`}
119
+ href={
120
+ appInfos.communityEdition
121
+ ? 'https://discord.strapi.io'
122
+ : 'https://support.strapi.io/support/home'
123
+ }
99
124
  endIcon={<ExternalLink />}
100
125
  >
101
126
  {formatMessage({
102
- id: 'Settings.application.link-upgrade',
103
- defaultMessage: 'Upgrade your admin panel',
127
+ id: 'Settings.application.get-help',
128
+ defaultMessage: 'Get help',
129
+ })}
130
+ </Link>
131
+ </GridItem>
132
+ <GridItem col={6} s={12}>
133
+ <Typography variant="sigma" textColor="neutral600">
134
+ {formatMessage({
135
+ id: 'Settings.application.edition-title',
136
+ defaultMessage: 'current plan',
137
+ })}
138
+ </Typography>
139
+ <Typography as="p">
140
+ {formatMessage({
141
+ id: currentPlan,
142
+ defaultMessage: `${
143
+ appInfos.communityEdition ? 'Community Edition' : 'Enterprise Edition'
144
+ }`,
145
+ })}
146
+ </Typography>
147
+ </GridItem>
148
+ </Grid>
149
+
150
+ <Grid paddingTop={1}>
151
+ <GridItem col={6} s={12}>
152
+ {shouldUpdateStrapi && (
153
+ <Link
154
+ href={`https://github.com/strapi/strapi/releases/tag/${latestStrapiReleaseTag}`}
155
+ endIcon={<ExternalLink />}
156
+ >
157
+ {formatMessage({
158
+ id: 'Settings.application.link-upgrade',
159
+ defaultMessage: 'Upgrade your admin panel',
160
+ })}
161
+ </Link>
162
+ )}
163
+ </GridItem>
164
+ <GridItem col={6} s={12}>
165
+ <Link href="https://strapi.io/pricing-self-hosted" endIcon={<ExternalLink />}>
166
+ {formatMessage({
167
+ id: 'Settings.application.link-pricing',
168
+ defaultMessage: 'See all pricing plans',
104
169
  })}
105
170
  </Link>
106
- )}
107
- </GridItem>
108
- <GridItem col={6} s={12}>
109
- <Link href="https://strapi.io/pricing-self-hosted" endIcon={<ExternalLink />}>
171
+ </GridItem>
172
+ </Grid>
173
+
174
+ <Box paddingTop={1}>
175
+ <Typography variant="sigma" textColor="neutral600">
110
176
  {formatMessage({
111
- id: 'Settings.application.link-pricing',
112
- defaultMessage: 'See all pricing plans',
177
+ id: 'Settings.application.node-version',
178
+ defaultMessage: 'node version',
113
179
  })}
114
- </Link>
115
- </GridItem>
116
- </Grid>
117
-
118
- <Box paddingTop={1}>
119
- <Typography variant="sigma" textColor="neutral600">
120
- {formatMessage({
121
- id: 'Settings.application.node-version',
122
- defaultMessage: 'node version',
123
- })}
124
- </Typography>
125
- <Typography as="p">{appInfos.nodeVersion}</Typography>
126
- </Box>
127
- </Stack>
128
- </Box>
180
+ </Typography>
181
+ <Typography as="p">{appInfos.nodeVersion}</Typography>
182
+ </Box>
183
+ </Stack>
184
+ </Box>
185
+ {data && (
186
+ <CheckPermissions permissions={permissions}>
187
+ <Form ref={inputsRef} projectSettingsStored={data} />
188
+ </CheckPermissions>
189
+ )}
190
+ </Stack>
129
191
  </ContentLayout>
130
192
  </Main>
131
193
  </Layout>
@@ -0,0 +1,16 @@
1
+ import { axiosInstance } from '../../../../../core/utils';
2
+ import prefixAllUrls from './prefixAllUrls';
3
+
4
+ const fetchProjectSettings = async () => {
5
+ const { data } = await axiosInstance.get('/admin/project-settings');
6
+
7
+ return prefixAllUrls(data);
8
+ };
9
+
10
+ const postProjectSettings = async body => {
11
+ const { data } = await axiosInstance.post('/admin/project-settings', body);
12
+
13
+ return prefixAllUrls(data);
14
+ };
15
+
16
+ export { fetchProjectSettings, postProjectSettings };
@@ -0,0 +1,3 @@
1
+ export const DIMENSION = 750;
2
+ export const SIZE = 100;
3
+ export const ACCEPTED_FORMAT = ['image/jpeg', 'image/png', 'image/svg+xml'];
@@ -0,0 +1,17 @@
1
+ const getFormData = data => {
2
+ const formData = new FormData();
3
+
4
+ Object.entries(data).forEach(([key, value]) => {
5
+ if (value && value.rawFile instanceof File) {
6
+ formData.append(key, value.rawFile);
7
+ }
8
+
9
+ if (value && value.isReset) {
10
+ formData.append(key, null);
11
+ }
12
+ });
13
+
14
+ return formData;
15
+ };
16
+
17
+ export default getFormData;
@@ -0,0 +1,76 @@
1
+ import { DIMENSION, SIZE, ACCEPTED_FORMAT } from './constants';
2
+
3
+ const FILE_FORMAT_ERROR_MESSAGE = {
4
+ id: 'Settings.application.customization.modal.upload.error-format',
5
+ defaultMessage: 'Wrong format uploaded (accepted formats only: jpeg, jpg, png, svg).',
6
+ };
7
+
8
+ const FILE_SIZING_ERROR_MESSAGE = {
9
+ id: 'Settings.application.customization.modal.upload.error-size',
10
+ defaultMessage:
11
+ 'The file uploaded is too large (max dimension: {dimension}x{dimension}, max file size: {size}KB)',
12
+ };
13
+
14
+ const getFileDimensions = file => {
15
+ return new Promise(resolve => {
16
+ const reader = new FileReader();
17
+ reader.onload = () => {
18
+ const img = new Image();
19
+ img.onload = function() {
20
+ resolve({ width: img.width, height: img.height });
21
+ };
22
+ img.src = reader.result;
23
+ };
24
+ reader.readAsDataURL(file);
25
+ });
26
+ };
27
+
28
+ const rawFileToAsset = (rawFile, fileDimensions) => {
29
+ return {
30
+ ext: rawFile.name.split('.').pop(),
31
+ size: rawFile.size / 1000,
32
+ name: rawFile.name,
33
+ url: URL.createObjectURL(rawFile),
34
+ rawFile,
35
+ width: fileDimensions.width,
36
+ height: fileDimensions.height,
37
+ };
38
+ };
39
+
40
+ export const parseFileMetadatas = async file => {
41
+ let error;
42
+
43
+ const isFormatAuthorized = ACCEPTED_FORMAT.includes(file.type);
44
+
45
+ if (!isFormatAuthorized) {
46
+ error = new Error('File format');
47
+ error.displayMessage = FILE_FORMAT_ERROR_MESSAGE;
48
+
49
+ throw error;
50
+ }
51
+
52
+ const fileDimensions = await getFileDimensions(file);
53
+
54
+ const areDimensionsAuthorized =
55
+ fileDimensions.width < DIMENSION && fileDimensions.height < DIMENSION;
56
+
57
+ if (!areDimensionsAuthorized) {
58
+ error = new Error('File sizing');
59
+ error.displayMessage = FILE_SIZING_ERROR_MESSAGE;
60
+
61
+ throw error;
62
+ }
63
+
64
+ const asset = rawFileToAsset(file, fileDimensions);
65
+
66
+ const isSizeAuthorized = asset.size < SIZE;
67
+
68
+ if (!isSizeAuthorized) {
69
+ error = new Error('File sizing');
70
+ error.displayMessage = FILE_SIZING_ERROR_MESSAGE;
71
+
72
+ throw error;
73
+ }
74
+
75
+ return asset;
76
+ };
@@ -0,0 +1,17 @@
1
+ import transform from 'lodash/transform';
2
+ import { prefixFileUrlWithBackendUrl } from '@strapi/helper-plugin';
3
+
4
+ const prefixAllUrls = data =>
5
+ transform(
6
+ data,
7
+ (result, value, key) => {
8
+ if (value && value.url) {
9
+ result[key] = { ...value, url: prefixFileUrlWithBackendUrl(value.url) };
10
+ } else {
11
+ result[key] = value;
12
+ }
13
+ },
14
+ {}
15
+ );
16
+
17
+ export default prefixAllUrls;
@@ -0,0 +1,21 @@
1
+ import axios from 'axios';
2
+
3
+ const urlToFile = async url => {
4
+ try {
5
+ const res = await axios.get(url, { responseType: 'blob', timeout: 8000 });
6
+ const loadedFile = new File([res.data], res.config.url, {
7
+ type: res.headers['content-type'],
8
+ });
9
+
10
+ return loadedFile;
11
+ } catch (err) {
12
+ err.displayMessage = {
13
+ id: 'Settings.application.customization.modal.upload.error-network',
14
+ defaultMessage: 'Network error',
15
+ };
16
+
17
+ throw err;
18
+ }
19
+ };
20
+
21
+ export default urlToFile;
@@ -99,6 +99,31 @@
99
99
  "Settings.application.strapi-version": "strapi version",
100
100
  "Settings.application.strapiVersion": "strapi version",
101
101
  "Settings.application.title": "Overview",
102
+ "Settings.application.customization": "Customization",
103
+ "Settings.application.customization.carousel.title": "Logo",
104
+ "Settings.application.customization.carousel.change-action": "Change logo",
105
+ "Settings.application.customization.carousel.reset-action": "Reset logo",
106
+ "Settings.application.customization.carousel-slide.label": "Logo slide",
107
+ "Settings.application.customization.carousel-hint": "Change the admin panel logo (Max dimension: {dimension}x{dimension}, Max file size: {size}KB)",
108
+ "Settings.application.customization.modal.cancel": "Cancel",
109
+ "Settings.application.customization.modal.upload": "Upload logo",
110
+ "Settings.application.customization.modal.tab.label": "How do you want to upload your assets?",
111
+ "Settings.application.customization.modal.upload.from-computer": "From computer",
112
+ "Settings.application.customization.modal.upload.file-validation": "Max dimension: {dimension}x{dimension}, Max size: {size}KB",
113
+ "Settings.application.customization.modal.upload.error-format": "Wrong format uploaded (accepted formats only: jpeg, jpg, png, svg).",
114
+ "Settings.application.customization.modal.upload.error-size": "The file uploaded is too large (max dimension: {dimension}x{dimension}, max file size: {size}KB)",
115
+ "Settings.application.customization.modal.upload.error-network": "Network error",
116
+ "Settings.application.customization.modal.upload.cta.browse": "Browse files",
117
+ "Settings.application.customization.modal.upload.drag-drop": "Drag and Drop here or",
118
+ "Settings.application.customization.modal.upload.from-url": "From url",
119
+ "Settings.application.customization.modal.upload.from-url.input-label": "URL",
120
+ "Settings.application.customization.modal.upload.next": "Next",
121
+ "Settings.application.customization.modal.pending": "Pending logo",
122
+ "Settings.application.customization.modal.pending.choose-another": "Choose another logo",
123
+ "Settings.application.customization.modal.pending.title": "Logo ready to upload",
124
+ "Settings.application.customization.modal.pending.subtitle": "Manage the chosen logo before uploading it",
125
+ "Settings.application.customization.modal.pending.upload": "Upload logo",
126
+ "Settings.application.customization.modal.pending.card-badge": "image",
102
127
  "Settings.error": "Error",
103
128
  "Settings.global": "Global Settings",
104
129
  "Settings.permissions": "Administration panel",