@strapi/admin 4.2.0-alpha.5 → 4.2.0-alpha.6

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 (41) hide show
  1. package/admin/src/StrapiApp.js +40 -42
  2. package/admin/src/components/GuidedTour/Modal/components/Modal.js +1 -1
  3. package/admin/src/components/Providers/index.js +65 -32
  4. package/admin/src/components/Theme/index.js +11 -12
  5. package/admin/src/components/ThemeToggleProvider/index.js +66 -0
  6. package/admin/src/content-manager/components/SelectWrapper/utils/getSelectStyles.js +3 -1
  7. package/admin/src/content-manager/components/Wysiwyg/EditorStylesContainer.js +4 -2
  8. package/admin/src/contexts/ThemeToggle/index.js +5 -0
  9. package/admin/src/contexts/index.js +1 -0
  10. package/admin/src/hooks/index.js +1 -0
  11. package/admin/src/hooks/useThemeToggle/index.js +10 -0
  12. package/admin/src/pages/Admin/Onboarding/index.js +1 -1
  13. package/admin/src/pages/HomePage/SocialLinks.js +0 -3
  14. package/admin/src/pages/ProfilePage/index.js +74 -10
  15. package/admin/src/pages/ProfilePage/utils/api.js +4 -2
  16. package/admin/src/translations/en.json +5 -2
  17. package/build/4362.ce36d91a.chunk.js +1 -0
  18. package/build/{6250.836851ca.chunk.js → 6404.3c2d0a81.chunk.js} +1 -1
  19. package/build/{Admin-authenticatedApp.a24ebaa0.chunk.js → Admin-authenticatedApp.13f39114.chunk.js} +1 -1
  20. package/build/{Admin_homePage.86604515.chunk.js → Admin_homePage.fd1fc572.chunk.js} +1 -1
  21. package/build/Admin_profilePage.d7192d06.chunk.js +1 -0
  22. package/build/{Admin_settingsPage.55ec1f30.chunk.js → Admin_settingsPage.a8c7ded5.chunk.js} +1 -1
  23. package/build/content-manager.f1c46a88.chunk.js +1 -0
  24. package/build/{content-type-builder.de5d18ad.chunk.js → content-type-builder.cda4ba3c.chunk.js} +1 -1
  25. package/build/{email-settings-page.27ee4a98.chunk.js → email-settings-page.40ee2bda.chunk.js} +1 -1
  26. package/build/en-json.98c7c4be.chunk.js +1 -0
  27. package/build/index.html +1 -1
  28. package/build/{main.4ea77c5f.js → main.f03a903a.js} +2 -2
  29. package/build/{main.4ea77c5f.js.LICENSE.txt → main.f03a903a.js.LICENSE.txt} +0 -0
  30. package/build/{runtime~main.83b9ee0b.js → runtime~main.251c8ddd.js} +1 -1
  31. package/build/users-email-settings-page.5abb9575.chunk.js +1 -0
  32. package/package.json +5 -5
  33. package/admin/src/themes/colors.js +0 -51
  34. package/admin/src/themes/fontWeights.js +0 -8
  35. package/admin/src/themes/index.js +0 -13
  36. package/admin/src/themes/sizes.js +0 -31
  37. package/build/4362.dbe98749.chunk.js +0 -1
  38. package/build/Admin_profilePage.c497b39d.chunk.js +0 -1
  39. package/build/content-manager.31be1448.chunk.js +0 -1
  40. package/build/en-json.bce44d39.chunk.js +0 -1
  41. package/build/users-email-settings-page.862eb51e.chunk.js +0 -1
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { BrowserRouter } from 'react-router-dom';
3
- import { lightTheme } from '@strapi/design-system/themes';
3
+ import { lightTheme, darkTheme } from '@strapi/design-system/themes';
4
4
  import merge from 'lodash/merge';
5
5
  import pick from 'lodash/pick';
6
6
  import isFunction from 'lodash/isFunction';
@@ -13,7 +13,6 @@ import App from './pages/App';
13
13
  import AuthLogo from './assets/images/logo_strapi_auth_v4.png';
14
14
  import MenuLogo from './assets/images/logo_strapi_menu.png';
15
15
  import Providers from './components/Providers';
16
- import Theme from './components/Theme';
17
16
  import languageNativeNames from './translations/languageNativeNames';
18
17
  import {
19
18
  INJECT_COLUMN_IN_TABLE,
@@ -34,7 +33,7 @@ class StrapiApp {
34
33
  locales: ['en'],
35
34
  menuLogo: MenuLogo,
36
35
  notifications: { releases: true },
37
- theme: lightTheme,
36
+ themes: { light: lightTheme, dark: darkTheme },
38
37
  translations: {},
39
38
  tutorials: true,
40
39
  };
@@ -226,7 +225,7 @@ class StrapiApp {
226
225
  }
227
226
 
228
227
  if (this.customConfigurations?.theme) {
229
- this.configurations.theme = merge(this.configurations.theme, this.customConfigurations.theme);
228
+ merge(this.configurations.themes.light, this.customConfigurations.theme);
230
229
  }
231
230
 
232
231
  if (this.customConfigurations?.notifications?.releases !== undefined) {
@@ -427,44 +426,43 @@ class StrapiApp {
427
426
  } = this.library;
428
427
 
429
428
  return (
430
- <Theme theme={this.configurations.theme}>
431
- <Providers
432
- authLogo={this.configurations.authLogo}
433
- components={components}
434
- fields={fields}
435
- localeNames={localeNames}
436
- getAdminInjectedComponents={this.getAdminInjectedComponents}
437
- getPlugin={this.getPlugin}
438
- messages={this.configurations.translations}
439
- menu={this.menu}
440
- menuLogo={this.configurations.menuLogo}
441
- plugins={this.plugins}
442
- runHookParallel={this.runHookParallel}
443
- runHookWaterfall={(name, initialValue, async = false) => {
444
- return this.runHookWaterfall(name, initialValue, async, store);
445
- }}
446
- runHookSeries={this.runHookSeries}
447
- settings={this.settings}
448
- showTutorials={this.configurations.tutorials}
449
- showReleaseNotification={this.configurations.notifications.releases}
450
- store={store}
451
- >
452
- <>
453
- <Helmet
454
- link={[
455
- {
456
- rel: 'icon',
457
- type: 'image/png',
458
- href: this.configurations.head.favicon,
459
- },
460
- ]}
461
- />
462
- <BrowserRouter basename={basename}>
463
- <App store={store} />
464
- </BrowserRouter>
465
- </>
466
- </Providers>
467
- </Theme>
429
+ <Providers
430
+ authLogo={this.configurations.authLogo}
431
+ components={components}
432
+ fields={fields}
433
+ localeNames={localeNames}
434
+ getAdminInjectedComponents={this.getAdminInjectedComponents}
435
+ getPlugin={this.getPlugin}
436
+ messages={this.configurations.translations}
437
+ menu={this.menu}
438
+ menuLogo={this.configurations.menuLogo}
439
+ plugins={this.plugins}
440
+ runHookParallel={this.runHookParallel}
441
+ runHookWaterfall={(name, initialValue, async = false) => {
442
+ return this.runHookWaterfall(name, initialValue, async, store);
443
+ }}
444
+ runHookSeries={this.runHookSeries}
445
+ themes={this.configurations.themes}
446
+ settings={this.settings}
447
+ showTutorials={this.configurations.tutorials}
448
+ showReleaseNotification={this.configurations.notifications.releases}
449
+ store={store}
450
+ >
451
+ <>
452
+ <Helmet
453
+ link={[
454
+ {
455
+ rel: 'icon',
456
+ type: 'image/png',
457
+ href: this.configurations.head.favicon,
458
+ },
459
+ ]}
460
+ />
461
+ <BrowserRouter basename={basename}>
462
+ <App store={store} />
463
+ </BrowserRouter>
464
+ </>
465
+ </Providers>
468
466
  );
469
467
  }
470
468
  }
@@ -17,7 +17,7 @@ const ModalWrapper = styled(Flex)`
17
17
  z-index: 4;
18
18
  inset: 0;
19
19
  /* this is theme.colors.neutral800 with opacity */
20
- background: ${({ theme }) => `${theme.colors.neutral800}33`};
20
+ background: ${({ theme }) => `${theme.colors.neutral800}1F`};
21
21
  `;
22
22
 
23
23
  const Modal = ({ onClose, onSkip, children, hideSkip }) => {
@@ -9,6 +9,8 @@ import GuidedTour from '../GuidedTour';
9
9
  import AutoReloadOverlayBlockerProvider from '../AutoReloadOverlayBlockerProvider';
10
10
  import Notifications from '../Notifications';
11
11
  import OverlayBlocker from '../OverlayBlocker';
12
+ import ThemeToggleProvider from '../ThemeToggleProvider';
13
+ import Theme from '../Theme';
12
14
 
13
15
  const queryClient = new QueryClient({
14
16
  defaultOptions: {
@@ -36,41 +38,45 @@ const Providers = ({
36
38
  settings,
37
39
  showReleaseNotification,
38
40
  showTutorials,
39
-
40
41
  store,
42
+ themes,
41
43
  }) => {
42
44
  return (
43
- <QueryClientProvider client={queryClient}>
44
- <Provider store={store}>
45
- <AdminContext.Provider value={{ getAdminInjectedComponents }}>
46
- <ConfigurationsContext.Provider
47
- value={{ authLogo, menuLogo, showReleaseNotification, showTutorials }}
48
- >
49
- <StrapiAppProvider
50
- getPlugin={getPlugin}
51
- menu={menu}
52
- plugins={plugins}
53
- runHookParallel={runHookParallel}
54
- runHookWaterfall={runHookWaterfall}
55
- runHookSeries={runHookSeries}
56
- settings={settings}
57
- >
58
- <LibraryProvider components={components} fields={fields}>
59
- <LanguageProvider messages={messages} localeNames={localeNames}>
60
- <AutoReloadOverlayBlockerProvider>
61
- <OverlayBlocker>
62
- <GuidedTour>
63
- <Notifications>{children}</Notifications>
64
- </GuidedTour>
65
- </OverlayBlocker>
66
- </AutoReloadOverlayBlockerProvider>
67
- </LanguageProvider>
68
- </LibraryProvider>
69
- </StrapiAppProvider>
70
- </ConfigurationsContext.Provider>
71
- </AdminContext.Provider>
72
- </Provider>
73
- </QueryClientProvider>
45
+ <ThemeToggleProvider themes={themes}>
46
+ <Theme>
47
+ <QueryClientProvider client={queryClient}>
48
+ <Provider store={store}>
49
+ <AdminContext.Provider value={{ getAdminInjectedComponents }}>
50
+ <ConfigurationsContext.Provider
51
+ value={{ authLogo, menuLogo, showReleaseNotification, showTutorials }}
52
+ >
53
+ <StrapiAppProvider
54
+ getPlugin={getPlugin}
55
+ menu={menu}
56
+ plugins={plugins}
57
+ runHookParallel={runHookParallel}
58
+ runHookWaterfall={runHookWaterfall}
59
+ runHookSeries={runHookSeries}
60
+ settings={settings}
61
+ >
62
+ <LibraryProvider components={components} fields={fields}>
63
+ <LanguageProvider messages={messages} localeNames={localeNames}>
64
+ <AutoReloadOverlayBlockerProvider>
65
+ <OverlayBlocker>
66
+ <GuidedTour>
67
+ <Notifications>{children}</Notifications>
68
+ </GuidedTour>
69
+ </OverlayBlocker>
70
+ </AutoReloadOverlayBlockerProvider>
71
+ </LanguageProvider>
72
+ </LibraryProvider>
73
+ </StrapiAppProvider>
74
+ </ConfigurationsContext.Provider>
75
+ </AdminContext.Provider>
76
+ </Provider>
77
+ </QueryClientProvider>
78
+ </Theme>
79
+ </ThemeToggleProvider>
74
80
  );
75
81
  };
76
82
 
@@ -104,6 +110,33 @@ Providers.propTypes = {
104
110
  showReleaseNotification: PropTypes.bool.isRequired,
105
111
  showTutorials: PropTypes.bool.isRequired,
106
112
  store: PropTypes.object.isRequired,
113
+ themes: PropTypes.shape({
114
+ light: PropTypes.shape({
115
+ colors: PropTypes.object.isRequired,
116
+ shadows: PropTypes.object.isRequired,
117
+ sizes: PropTypes.object.isRequired,
118
+ zIndices: PropTypes.array.isRequired,
119
+ spaces: PropTypes.array.isRequired,
120
+ borderRadius: PropTypes.string.isRequired,
121
+ mediaQueries: PropTypes.object.isRequired,
122
+ fontSizes: PropTypes.array.isRequired,
123
+ lineHeights: PropTypes.array.isRequired,
124
+ fontWeights: PropTypes.object.isRequired,
125
+ }).isRequired,
126
+ dark: PropTypes.shape({
127
+ colors: PropTypes.object.isRequired,
128
+ shadows: PropTypes.object.isRequired,
129
+ sizes: PropTypes.object.isRequired,
130
+ zIndices: PropTypes.array.isRequired,
131
+ spaces: PropTypes.array.isRequired,
132
+ borderRadius: PropTypes.string.isRequired,
133
+ mediaQueries: PropTypes.object.isRequired,
134
+ fontSizes: PropTypes.array.isRequired,
135
+ lineHeights: PropTypes.array.isRequired,
136
+ fontWeights: PropTypes.object.isRequired,
137
+ }).isRequired,
138
+ custom: PropTypes.object,
139
+ }).isRequired,
107
140
  };
108
141
 
109
142
  export default Providers;
@@ -1,23 +1,22 @@
1
1
  import React from 'react';
2
2
  import { ThemeProvider } from '@strapi/design-system/ThemeProvider';
3
3
  import PropTypes from 'prop-types';
4
- import { lightTheme } from '@strapi/design-system/themes';
4
+ import { useThemeToggle } from '../../hooks';
5
5
  import GlobalStyle from '../GlobalStyle';
6
6
 
7
- const Theme = ({ children, theme }) => (
8
- <ThemeProvider theme={theme}>
9
- {children}
10
- <GlobalStyle />
11
- </ThemeProvider>
12
- );
7
+ const Theme = ({ children }) => {
8
+ const { currentTheme, themes } = useThemeToggle();
13
9
 
14
- Theme.propTypes = {
15
- children: PropTypes.element.isRequired,
16
- theme: PropTypes.object,
10
+ return (
11
+ <ThemeProvider theme={themes[currentTheme] || themes.light}>
12
+ {children}
13
+ <GlobalStyle />
14
+ </ThemeProvider>
15
+ );
17
16
  };
18
17
 
19
- Theme.defaultProps = {
20
- theme: lightTheme,
18
+ Theme.propTypes = {
19
+ children: PropTypes.element.isRequired,
21
20
  };
22
21
 
23
22
  export default Theme;
@@ -0,0 +1,66 @@
1
+ /**
2
+ *
3
+ * ThemeToggleProvider
4
+ *
5
+ */
6
+
7
+ import React, { useState } from 'react';
8
+ import PropTypes from 'prop-types';
9
+ import { ThemeToggleContext } from '../../contexts';
10
+
11
+ const THEME_KEY = 'STRAPI_THEME';
12
+
13
+ const getDefaultTheme = () => {
14
+ const browserTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
15
+ const persistedTheme = localStorage.getItem(THEME_KEY);
16
+
17
+ return persistedTheme || browserTheme;
18
+ };
19
+
20
+ const ThemeToggleProvider = ({ children, themes }) => {
21
+ const [currentTheme, setCurrentTheme] = useState(getDefaultTheme());
22
+
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>
32
+ );
33
+ };
34
+
35
+ ThemeToggleProvider.propTypes = {
36
+ children: PropTypes.node.isRequired,
37
+ themes: PropTypes.shape({
38
+ light: PropTypes.shape({
39
+ colors: PropTypes.object.isRequired,
40
+ shadows: PropTypes.object.isRequired,
41
+ sizes: PropTypes.object.isRequired,
42
+ zIndices: PropTypes.array.isRequired,
43
+ spaces: PropTypes.array.isRequired,
44
+ borderRadius: PropTypes.string.isRequired,
45
+ mediaQueries: PropTypes.object.isRequired,
46
+ fontSizes: PropTypes.array.isRequired,
47
+ lineHeights: PropTypes.array.isRequired,
48
+ fontWeights: PropTypes.object.isRequired,
49
+ }).isRequired,
50
+ dark: PropTypes.shape({
51
+ colors: PropTypes.object.isRequired,
52
+ shadows: PropTypes.object.isRequired,
53
+ sizes: PropTypes.object.isRequired,
54
+ zIndices: PropTypes.array.isRequired,
55
+ spaces: PropTypes.array.isRequired,
56
+ borderRadius: PropTypes.string.isRequired,
57
+ mediaQueries: PropTypes.object.isRequired,
58
+ fontSizes: PropTypes.array.isRequired,
59
+ lineHeights: PropTypes.array.isRequired,
60
+ fontWeights: PropTypes.object.isRequired,
61
+ }).isRequired,
62
+ custom: PropTypes.object,
63
+ }).isRequired,
64
+ };
65
+
66
+ export default ThemeToggleProvider;
@@ -49,6 +49,8 @@ const getSelectStyles = theme => {
49
49
  ...base,
50
50
  width: '100%',
51
51
  marginTop: theme.spaces[1],
52
+ backgroundColor: theme.colors.neutral0,
53
+ color: theme.colors.neutral800,
52
54
  borderRadius: '4px !important',
53
55
  borderTopLeftRadius: '4px !important',
54
56
  borderTopRightRadius: '4px !important',
@@ -76,7 +78,7 @@ const getSelectStyles = theme => {
76
78
  return { ...base, lineHeight: theme.spaces[5], backgroundColor, borderRadius: 4 };
77
79
  },
78
80
  placeholder: base => ({ ...base, marginLeft: 0 }),
79
- singleValue: base => ({ ...base, marginLeft: 0 }),
81
+ singleValue: base => ({ ...base, marginLeft: 0, color: theme.colors.neutral800 }),
80
82
  valueContainer: base => ({
81
83
  ...base,
82
84
  padding: 0,
@@ -29,7 +29,8 @@ export const EditorStylesContainer = styled.div`
29
29
 
30
30
  .CodeMirror-scrollbar-filler,
31
31
  .CodeMirror-gutter-filler {
32
- background-color: white; /* The little square between H and V scrollbars */
32
+ /* The little square between H and V scrollbars */
33
+ background-color: ${({ theme }) => `${theme.colors.neutral0}`};
33
34
  }
34
35
 
35
36
  /* GUTTER */
@@ -158,7 +159,7 @@ export const EditorStylesContainer = styled.div`
158
159
  .CodeMirror {
159
160
  position: relative;
160
161
  overflow: hidden;
161
- background: white;
162
+ background: ${({ theme }) => `${theme.colors.neutral0}`};
162
163
  }
163
164
 
164
165
  .CodeMirror-scroll {
@@ -292,6 +293,7 @@ export const EditorStylesContainer = styled.div`
292
293
  .CodeMirror-cursor {
293
294
  position: absolute;
294
295
  pointer-events: none;
296
+ border-color: ${({ theme }) => `${theme.colors.neutral800}`};
295
297
  }
296
298
  .CodeMirror-measure pre {
297
299
  position: static;
@@ -0,0 +1,5 @@
1
+ import { createContext } from 'react';
2
+
3
+ const ThemeToggleContext = createContext({});
4
+
5
+ export default ThemeToggleContext;
@@ -1,3 +1,4 @@
1
1
  export { default as AdminContext } from './Admin';
2
2
  export { default as ConfigurationsContext } from './Configurations';
3
3
  export { default as PermissionsDataManagerContext } from './PermisssionsDataManagerContext';
4
+ export { default as ThemeToggleContext } from './ThemeToggle';
@@ -9,3 +9,4 @@ export { default as useSettingsMenu } from './useSettingsMenu';
9
9
  export { default as useSettingsForm } from './useSettingsForm';
10
10
  export { default as usePermissionsDataManager } from './usePermissionsDataManager';
11
11
  export { default as useReleaseNotification } from './useReleaseNotification';
12
+ export { default as useThemeToggle } from './useThemeToggle';
@@ -0,0 +1,10 @@
1
+ import { useContext } from 'react';
2
+ import { ThemeToggleContext } from '../../contexts';
3
+
4
+ const useThemeToggle = () => {
5
+ const context = useContext(ThemeToggleContext);
6
+
7
+ return context;
8
+ };
9
+
10
+ export default useThemeToggle;
@@ -21,7 +21,7 @@ const Button = styled.button`
21
21
  box-shadow: ${({ theme }) => theme.shadows.tableShadow};
22
22
  border-radius: 50%;
23
23
  svg {
24
- color: ${({ theme }) => theme.colors.neutral0};
24
+ color: ${({ theme }) => theme.colors.buttonNeutral0};
25
25
  }
26
26
  `;
27
27
 
@@ -30,9 +30,6 @@ const StyledReddit = styled(Reddit)`
30
30
  > path:first-child {
31
31
  fill: #ff4500;
32
32
  }
33
- > path:last-child {
34
- fill: ${({ theme }) => theme.colors.neutral0};
35
- }
36
33
  `;
37
34
  const StyledStrapi = styled(Strapi)`
38
35
  > path:first-child {
@@ -9,12 +9,13 @@ import {
9
9
  useNotification,
10
10
  useOverlayBlocker,
11
11
  auth,
12
+ useTracking,
12
13
  } from '@strapi/helper-plugin';
13
14
  import { useIntl } from 'react-intl';
14
15
  import { Formik } from 'formik';
16
+ import upperFirst from 'lodash/upperFirst';
15
17
  import { useQuery, useMutation, useQueryClient } from 'react-query';
16
18
  import pick from 'lodash/pick';
17
- import omit from 'lodash/omit';
18
19
  import { Helmet } from 'react-helmet';
19
20
  import { Main } from '@strapi/design-system/Main';
20
21
  import { Typography } from '@strapi/design-system/Typography';
@@ -31,10 +32,15 @@ import Eye from '@strapi/icons/Eye';
31
32
  import EyeStriked from '@strapi/icons/EyeStriked';
32
33
  import Check from '@strapi/icons/Check';
33
34
  import useLocalesProvider from '../../components/LocalesProvider/useLocalesProvider';
35
+ import { useThemeToggle } from '../../hooks';
34
36
  import { fetchUser, putUser } from './utils/api';
35
37
  import schema from './utils/schema';
36
38
  import { getFullName } from '../../utils';
37
39
 
40
+ const DocumentationLink = styled.a`
41
+ color: ${({ theme }) => theme.colors.primary600};
42
+ `;
43
+
38
44
  const PasswordInput = styled(TextInput)`
39
45
  ::-ms-reveal {
40
46
  display: none;
@@ -59,9 +65,11 @@ const ProfilePage = () => {
59
65
  const { setUserDisplayName } = useAppInfos();
60
66
  const queryClient = useQueryClient();
61
67
  const { formatMessage } = useIntl();
68
+ const { trackUsage } = useTracking();
62
69
  const toggleNotification = useNotification();
63
70
  const { lockApp, unlockApp } = useOverlayBlocker();
64
71
  const { notifyStatus } = useNotifyAT();
72
+ const { currentTheme, themes: allApplicationThemes, onChangeTheme } = useThemeToggle();
65
73
  useFocusWhenNavigate();
66
74
 
67
75
  const { status, data } = useQuery('user', () => fetchUser(), {
@@ -83,14 +91,18 @@ const ProfilePage = () => {
83
91
 
84
92
  const isLoading = status !== 'success';
85
93
 
86
- const submitMutation = useMutation(body => putUser(omit(body, 'confirmPassword')), {
94
+ const submitMutation = useMutation(body => putUser(body), {
87
95
  onSuccess: async data => {
88
96
  await queryClient.invalidateQueries('user');
89
97
 
90
- auth.setUserInfo(data);
98
+ auth.setUserInfo(
99
+ pick(data, ['email', 'firstname', 'lastname', 'username', 'preferedLanguage'])
100
+ );
91
101
  const userDisplayName = data.username || getFullName(data.firstname, data.lastname);
92
102
  setUserDisplayName(userDisplayName);
93
103
  changeLocale(data.preferedLanguage);
104
+ onChangeTheme(data.currentTheme);
105
+ trackUsage('didChangeMode', { newMode: data.currentTheme });
94
106
 
95
107
  toggleNotification({
96
108
  type: 'success',
@@ -128,9 +140,16 @@ const ProfilePage = () => {
128
140
  );
129
141
  };
130
142
 
131
- const fieldsToPick = ['email', 'firstname', 'lastname', 'username', 'preferedLanguage'];
143
+ const fieldsToPick = [
144
+ 'currentTheme',
145
+ 'email',
146
+ 'firstname',
147
+ 'lastname',
148
+ 'username',
149
+ 'preferedLanguage',
150
+ ];
132
151
 
133
- const initialData = pick(data, fieldsToPick);
152
+ const initialData = pick({ ...data, currentTheme }, fieldsToPick);
134
153
 
135
154
  if (isLoading) {
136
155
  return (
@@ -154,6 +173,10 @@ const ProfilePage = () => {
154
173
  );
155
174
  }
156
175
 
176
+ const themesToDisplay = Object.keys(allApplicationThemes).filter(
177
+ themeName => allApplicationThemes[themeName]
178
+ );
179
+
157
180
  return (
158
181
  <Main aria-busy={isSubmittingForm}>
159
182
  <Helmet
@@ -424,20 +447,20 @@ const ProfilePage = () => {
424
447
  id:
425
448
  'Settings.profile.form.section.experience.interfaceLanguageHelp',
426
449
  defaultMessage:
427
- 'Selection will change the interface language only for you. Please refer to this {documentation} to make other languages available for your team.',
450
+ 'Preference changes will apply only to you. More information is available {here}.',
428
451
  },
429
452
  {
430
- documentation: (
431
- <a
453
+ here: (
454
+ <DocumentationLink
432
455
  target="_blank"
433
456
  rel="noopener noreferrer"
434
457
  href="https://docs.strapi.io/developer-docs/latest/development/admin-customization.html#locales"
435
458
  >
436
459
  {formatMessage({
437
460
  id: 'Settings.profile.form.section.experience.documentation',
438
- defaultMessage: 'documentation',
461
+ defaultMessage: 'here',
439
462
  })}
440
- </a>
463
+ </DocumentationLink>
441
464
  ),
442
465
  }
443
466
  )}
@@ -487,6 +510,47 @@ const ProfilePage = () => {
487
510
  })}
488
511
  </Select>
489
512
  </GridItem>
513
+ <GridItem s={12} col={6}>
514
+ <Select
515
+ label={formatMessage({
516
+ id: 'Settings.profile.form.section.experience.mode.label',
517
+ defaultMessage: 'Interface mode',
518
+ })}
519
+ placeholder={formatMessage({
520
+ id: 'components.Select.placeholder',
521
+ defaultMessage: 'Select',
522
+ })}
523
+ hint={formatMessage({
524
+ id: 'Settings.profile.form.section.experience.mode.hint',
525
+ defaultMessage: 'Displays your interface in the chosen mode.',
526
+ })}
527
+ value={values.currentTheme}
528
+ onChange={e => {
529
+ handleChange({
530
+ target: { name: 'currentTheme', value: e },
531
+ });
532
+ }}
533
+ >
534
+ {themesToDisplay.map(theme => {
535
+ const label = formatMessage(
536
+ {
537
+ id:
538
+ 'Settings.profile.form.section.experience.mode.option-label',
539
+ defaultMessage: '{name} mode',
540
+ },
541
+ {
542
+ name: upperFirst(theme),
543
+ }
544
+ );
545
+
546
+ return (
547
+ <Option value={theme} key={theme}>
548
+ {label}
549
+ </Option>
550
+ );
551
+ })}
552
+ </Select>
553
+ </GridItem>
490
554
  </Grid>
491
555
  </Stack>
492
556
  </Box>
@@ -1,3 +1,4 @@
1
+ import omit from 'lodash/omit';
1
2
  import { axiosInstance } from '../../../core/utils';
2
3
 
3
4
  const fetchUser = async () => {
@@ -7,9 +8,10 @@ const fetchUser = async () => {
7
8
  };
8
9
 
9
10
  const putUser = async body => {
10
- const { data } = await axiosInstance.put('/admin/users/me', body);
11
+ const dataToSend = omit(body, ['confirmPassword', 'currentTheme']);
12
+ const { data } = await axiosInstance.put('/admin/users/me', dataToSend);
11
13
 
12
- return data.data;
14
+ return { ...data.data, currentTheme: body.currentTheme };
13
15
  };
14
16
 
15
17
  export { fetchUser, putUser };
@@ -133,10 +133,13 @@
133
133
  "Settings.permissions.users.tabs.label": "Tabs Permissions",
134
134
  "Settings.profile.form.notify.data.loaded": "Your profile data has been loaded",
135
135
  "Settings.profile.form.section.experience.clear.select": "Clear the interface language selected",
136
- "Settings.profile.form.section.experience.documentation": "documentation",
136
+ "Settings.profile.form.section.experience.here": "here",
137
137
  "Settings.profile.form.section.experience.interfaceLanguage": "Interface language",
138
138
  "Settings.profile.form.section.experience.interfaceLanguage.hint": "This will only display your own interface in the chosen language.",
139
- "Settings.profile.form.section.experience.interfaceLanguageHelp": "Selection will change the interface language only for you. Please refer to this {documentation} to make other languages available for your team.",
139
+ "Settings.profile.form.section.experience.interfaceLanguageHelp": "Preference changes will apply only to you. More information is available {here}.",
140
+ "Settings.profile.form.section.experience.mode.label": "Interface mode",
141
+ "Settings.profile.form.section.experience.mode.hint": "Displays your interface in the chosen mode.",
142
+ "Settings.profile.form.section.experience.mode.option-label": "{name} mode",
140
143
  "Settings.profile.form.section.experience.title": "Experience",
141
144
  "Settings.profile.form.section.helmet.title": "User profile",
142
145
  "Settings.profile.form.section.password.title": "Change password",