diff --git a/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/A.js b/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/A.js
index 6aac231064..8023416e86 100644
--- a/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/A.js
+++ b/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/A.js
@@ -1,6 +1,7 @@
import styled from 'styled-components';
const A = styled.a`
+ display: flex;
position: relative;
padding-top: 0.7rem;
padding-bottom: 0.2rem;
@@ -10,7 +11,6 @@ const A = styled.a`
cursor: pointer;
color: ${props => props.theme.main.colors.leftMenu['link-color']};
text-decoration: none;
- display: block;
-webkit-font-smoothing: antialiased;
&:hover {
@@ -31,6 +31,8 @@ const A = styled.a`
}
&.linkActive {
+ padding-right: 2.3rem;
+
color: white !important;
border-left: 0.3rem solid ${props => props.theme.main.colors.strapi.blue};
}
diff --git a/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/LeftMenuLinkContent.js b/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/LeftMenuLinkContent.js
index 645de067cd..c83f116adb 100644
--- a/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/LeftMenuLinkContent.js
+++ b/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/LeftMenuLinkContent.js
@@ -10,10 +10,10 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import styled from 'styled-components';
import { Link, withRouter } from 'react-router-dom';
-
import en from '../../../translations/en.json';
import LeftMenuIcon from './LeftMenuIcon';
import A from './A';
+import NotificationCount from './NotificationCount';
const LinkLabel = styled.span`
display: inline-block;
@@ -23,7 +23,7 @@ const LinkLabel = styled.span`
`;
// TODO: refacto this file
-const LeftMenuLinkContent = ({ destination, iconName, label, location }) => {
+const LeftMenuLinkContent = ({ destination, iconName, label, location, notificationsCount }) => {
const isLinkActive = startsWith(
location.pathname.replace('/admin', '').concat('/'),
destination.concat('/')
@@ -67,6 +67,7 @@ const LeftMenuLinkContent = ({ destination, iconName, label, location }) => {
>
{content}
+ {notificationsCount > 0 && }
);
};
@@ -78,6 +79,7 @@ LeftMenuLinkContent.propTypes = {
location: PropTypes.shape({
pathname: PropTypes.string,
}).isRequired,
+ notificationsCount: PropTypes.number.isRequired,
};
export default withRouter(LeftMenuLinkContent);
diff --git a/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/NotificationCount.js b/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/NotificationCount.js
new file mode 100644
index 0000000000..d935960f84
--- /dev/null
+++ b/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/NotificationCount.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import styled from 'styled-components';
+import PropTypes from 'prop-types';
+import { Text } from '@buffetjs/core';
+
+const NotificationWrapper = styled.div`
+ height: 14px;
+ margin-top: 4px;
+ padding: 0px 4px;
+ background-color: #383d49;
+ border-radius: 2px;
+ font-size: 11px;
+`;
+
+const NotificationCount = ({ count }) => (
+
+
+ {count}
+
+
+);
+
+NotificationCount.defaultProps = {
+ count: 0,
+};
+
+NotificationCount.propTypes = {
+ count: PropTypes.number,
+};
+
+export default NotificationCount;
diff --git a/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/index.js b/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/index.js
index 4b4b8a2bfe..667854ad02 100644
--- a/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/index.js
+++ b/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLink/index.js
@@ -9,13 +9,14 @@ import PropTypes from 'prop-types';
import LeftMenuLinkContent from './LeftMenuLinkContent';
-const LeftMenuLink = ({ destination, iconName, label, location }) => {
+const LeftMenuLink = ({ destination, iconName, label, location, notificationsCount }) => {
return (
);
};
@@ -27,6 +28,7 @@ LeftMenuLink.propTypes = {
location: PropTypes.shape({
pathname: PropTypes.string,
}).isRequired,
+ notificationsCount: PropTypes.number.isRequired,
};
LeftMenuLink.defaultProps = {
diff --git a/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLinkSection/LeftMenuListLink.js b/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLinkSection/LeftMenuListLink.js
index b067383c8e..9af1980d60 100644
--- a/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLinkSection/LeftMenuListLink.js
+++ b/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLinkSection/LeftMenuListLink.js
@@ -3,7 +3,6 @@ import styled from 'styled-components';
const LeftMenuListLink = styled.div`
max-height: 180px;
margin-bottom: 19px;
- margin-right: 28px;
overflow: auto;
`;
diff --git a/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLinkSection/index.js b/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLinkSection/index.js
index 3e9bda3102..9815283de0 100644
--- a/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLinkSection/index.js
+++ b/packages/strapi-admin/admin/src/components/LeftMenu/LeftMenuLinkSection/index.js
@@ -46,6 +46,7 @@ const LeftMenuLinksSection = ({
iconName={link.icon}
label={link.label}
destination={link.destination}
+ notificationsCount={link.notificationsCount || 0}
/>
))
) : (
diff --git a/packages/strapi-admin/admin/src/containers/Admin/actions.js b/packages/strapi-admin/admin/src/containers/Admin/actions.js
index cf9b93e291..19c0ed633f 100644
--- a/packages/strapi-admin/admin/src/containers/Admin/actions.js
+++ b/packages/strapi-admin/admin/src/containers/Admin/actions.js
@@ -5,12 +5,20 @@
*/
import {
+ GET_STRAPI_LATEST_RELEASE_SUCCEEDED,
GET_USER_PERMISSIONS,
GET_USER_PERMISSIONS_ERROR,
GET_USER_PERMISSIONS_SUCCEEDED,
SET_APP_ERROR,
} from './constants';
+export function getStrapiLatestReleaseSucceeded(latestStrapiReleaseTag) {
+ return {
+ type: GET_STRAPI_LATEST_RELEASE_SUCCEEDED,
+ latestStrapiReleaseTag,
+ };
+}
+
export function getUserPermissions() {
return {
type: GET_USER_PERMISSIONS,
diff --git a/packages/strapi-admin/admin/src/containers/Admin/constants.js b/packages/strapi-admin/admin/src/containers/Admin/constants.js
index 6d5dcb786b..dca1515264 100644
--- a/packages/strapi-admin/admin/src/containers/Admin/constants.js
+++ b/packages/strapi-admin/admin/src/containers/Admin/constants.js
@@ -5,6 +5,8 @@
*/
export const SET_APP_ERROR = 'StrapiAdmin/Admin/SET_APP_ERROR';
+export const GET_STRAPI_LATEST_RELEASE_SUCCEEDED =
+ 'StrapiAdmin/Admin/GET_STRAPI_LATEST_RELEASE_SUCCEEDED';
export const GET_USER_PERMISSIONS = 'StrapiAdmin/Admin/GET_USER_PERMISSIONS';
export const GET_USER_PERMISSIONS_ERROR = 'StrapiAdmin/Admin/GET_USER_PERMISSIONS_ERROR';
export const GET_USER_PERMISSIONS_SUCCEEDED = 'StrapiAdmin/Admin/GET_USER_PERMISSIONS_SUCCEEDED';
diff --git a/packages/strapi-admin/admin/src/containers/Admin/index.js b/packages/strapi-admin/admin/src/containers/Admin/index.js
index 7563aba852..2aeafe8acb 100644
--- a/packages/strapi-admin/admin/src/containers/Admin/index.js
+++ b/packages/strapi-admin/admin/src/containers/Admin/index.js
@@ -23,7 +23,7 @@ import {
CheckPagePermissions,
request,
} from 'strapi-helper-plugin';
-import { SETTINGS_BASE_URL, SHOW_TUTORIALS } from '../../config';
+import { SETTINGS_BASE_URL, SHOW_TUTORIALS, STRAPI_UPDATE_NOTIF } from '../../config';
import adminPermissions from '../../permissions';
import Header from '../../components/Header/index';
@@ -42,10 +42,12 @@ import Logout from './Logout';
import {
disableGlobalOverlayBlocker,
enableGlobalOverlayBlocker,
+ getInfosDataSucceeded,
updatePlugin,
} from '../App/actions';
import makeSelecApp from '../App/selectors';
import {
+ getStrapiLatestReleaseSucceeded,
getUserPermissions,
getUserPermissionsError,
getUserPermissionsSucceeded,
@@ -67,7 +69,7 @@ export class Admin extends React.Component {
componentDidMount() {
this.emitEvent('didAccessAuthenticatedAdministration');
- this.fetchUserPermissions(true);
+ this.initApp();
}
shouldComponentUpdate(prevProps) {
@@ -108,6 +110,59 @@ export class Admin extends React.Component {
}
};
+ fetchAppInfo = async () => {
+ try {
+ const { data } = await request('/admin/information', { method: 'GET' });
+
+ this.props.getInfosDataSucceeded(data);
+ } catch (err) {
+ console.error(err);
+ strapi.notification.error('notification.error');
+ }
+ };
+
+ fetchStrapiLatestRelease = async () => {
+ const {
+ global: { strapiVersion },
+ getStrapiLatestReleaseSucceeded,
+ } = this.props;
+
+ if (!STRAPI_UPDATE_NOTIF) {
+ return;
+ }
+
+ try {
+ const {
+ data: { tag_name },
+ } = await axios.get('https://api.github.com/repos/strapi/strapi/releases/latest');
+
+ getStrapiLatestReleaseSucceeded(tag_name);
+
+ const showUpdateNotif = !JSON.parse(localStorage.getItem('STRAPI_UPDATE_NOTIF'));
+
+ if (!showUpdateNotif) {
+ return;
+ }
+
+ if (`v${strapiVersion}` !== tag_name) {
+ strapi.notification.toggle({
+ type: 'info',
+ message: { id: 'notification.version.update.message' },
+ link: {
+ url: `https://github.com/strapi/strapi/releases/tag/${tag_name}`,
+ label: {
+ id: 'notification.version.update.link',
+ },
+ },
+ blockTransition: true,
+ onClose: () => localStorage.setItem('STRAPI_UPDATE_NOTIF', true),
+ });
+ }
+ } catch (err) {
+ // Silent
+ }
+ };
+
fetchUserPermissions = async (resetState = false) => {
const { getUserPermissions, getUserPermissionsError, getUserPermissionsSucceeded } = this.props;
@@ -134,6 +189,12 @@ export class Admin extends React.Component {
return !Object.keys(plugins).every(plugin => plugins[plugin].isReady === true);
};
+ initApp = async () => {
+ await this.fetchAppInfo();
+ await this.fetchStrapiLatestRelease();
+ await this.fetchUserPermissions(true);
+ };
+
/**
* Display the app loader until the app is ready
* @returns {Boolean}
@@ -170,7 +231,7 @@ export class Admin extends React.Component {
render() {
const {
- admin: { isLoading, userPermissions },
+ admin: { isLoading, latestStrapiReleaseTag, userPermissions },
global: {
autoReload,
blockApp,
@@ -211,14 +272,21 @@ export class Admin extends React.Component {
enableGlobalOverlayBlocker={enableGlobalOverlayBlocker}
fetchUserPermissions={this.fetchUserPermissions}
formatMessage={formatMessage}
+ latestStrapiReleaseTag={latestStrapiReleaseTag}
menu={this.menuRef.current}
plugins={plugins}
settingsBaseURL={SETTINGS_BASE_URL || '/settings'}
+ strapiVersion={strapiVersion}
updatePlugin={updatePlugin}
>
-
+
{/* Injection zone not ready yet */}
@@ -275,10 +343,13 @@ Admin.propTypes = {
admin: PropTypes.shape({
appError: PropTypes.bool,
isLoading: PropTypes.bool,
+ latestStrapiReleaseTag: PropTypes.string.isRequired,
userPermissions: PropTypes.array,
}).isRequired,
disableGlobalOverlayBlocker: PropTypes.func.isRequired,
enableGlobalOverlayBlocker: PropTypes.func.isRequired,
+ getInfosDataSucceeded: PropTypes.func.isRequired,
+ getStrapiLatestReleaseSucceeded: PropTypes.func.isRequired,
getUserPermissions: PropTypes.func.isRequired,
getUserPermissionsError: PropTypes.func.isRequired,
getUserPermissionsSucceeded: PropTypes.func.isRequired,
@@ -311,6 +382,8 @@ export function mapDispatchToProps(dispatch) {
{
disableGlobalOverlayBlocker,
enableGlobalOverlayBlocker,
+ getInfosDataSucceeded,
+ getStrapiLatestReleaseSucceeded,
getUserPermissions,
getUserPermissionsError,
getUserPermissionsSucceeded,
diff --git a/packages/strapi-admin/admin/src/containers/Admin/reducer.js b/packages/strapi-admin/admin/src/containers/Admin/reducer.js
index 8494d2054e..07f6584007 100644
--- a/packages/strapi-admin/admin/src/containers/Admin/reducer.js
+++ b/packages/strapi-admin/admin/src/containers/Admin/reducer.js
@@ -5,17 +5,21 @@
*/
import produce from 'immer';
+import packageJSON from '../../../../package.json';
import {
+ GET_STRAPI_LATEST_RELEASE_SUCCEEDED,
GET_USER_PERMISSIONS,
GET_USER_PERMISSIONS_ERROR,
GET_USER_PERMISSIONS_SUCCEEDED,
SET_APP_ERROR,
} from './constants';
+const packageVersion = packageJSON.version;
const initialState = {
appError: false,
isLoading: true,
+ latestStrapiReleaseTag: `v${packageVersion}`,
userPermissions: [],
};
@@ -23,6 +27,10 @@ const reducer = (state = initialState, action) =>
// eslint-disable-next-line consistent-return
produce(state, draftState => {
switch (action.type) {
+ case GET_STRAPI_LATEST_RELEASE_SUCCEEDED: {
+ draftState.latestStrapiReleaseTag = action.latestStrapiReleaseTag;
+ break;
+ }
case GET_USER_PERMISSIONS: {
draftState.isLoading = true;
break;
diff --git a/packages/strapi-admin/admin/src/containers/Admin/tests/index.test.js b/packages/strapi-admin/admin/src/containers/Admin/tests/index.test.js
index 0ff6657b4a..99495b1195 100644
--- a/packages/strapi-admin/admin/src/containers/Admin/tests/index.test.js
+++ b/packages/strapi-admin/admin/src/containers/Admin/tests/index.test.js
@@ -19,10 +19,13 @@ describe('', () => {
props = {
admin: {
appError: false,
+ latestStrapiReleaseTag: '3',
},
disableGlobalOverlayBlocker: jest.fn(),
emitEvent: jest.fn(),
enableGlobalOverlayBlocker: jest.fn(),
+ getInfosDataSucceeded: jest.fn(),
+ getStrapiLatestReleaseSucceeded: jest.fn(),
getUserPermissions: jest.fn(),
getUserPermissionsError: jest.fn(),
getUserPermissionsSucceeded: jest.fn(),
@@ -42,6 +45,7 @@ describe('', () => {
intl: {
formatMessage: jest.fn(),
},
+
location: {},
setAppError: jest.fn(),
showGlobalAppBlocker: jest.fn(),
diff --git a/packages/strapi-admin/admin/src/containers/Admin/tests/reducer.test.js b/packages/strapi-admin/admin/src/containers/Admin/tests/reducer.test.js
index cba1568f16..4f23414c09 100644
--- a/packages/strapi-admin/admin/src/containers/Admin/tests/reducer.test.js
+++ b/packages/strapi-admin/admin/src/containers/Admin/tests/reducer.test.js
@@ -1,4 +1,5 @@
import produce from 'immer';
+import packageJSON from '../../../../../package.json';
import {
setAppError,
getUserPermissions,
@@ -14,6 +15,7 @@ describe('adminReducer', () => {
state = {
appError: false,
isLoading: true,
+ latestStrapiReleaseTag: `v${packageJSON.version}`,
userPermissions: [],
};
});
diff --git a/packages/strapi-admin/admin/src/containers/App/actions.js b/packages/strapi-admin/admin/src/containers/App/actions.js
index 66999421da..38c0a2517f 100644
--- a/packages/strapi-admin/admin/src/containers/App/actions.js
+++ b/packages/strapi-admin/admin/src/containers/App/actions.js
@@ -8,12 +8,12 @@ import {
DISABLE_GLOBAL_OVERLAY_BLOCKER,
ENABLE_GLOBAL_OVERLAY_BLOCKER,
FREEZE_APP,
+ GET_INFOS_DATA_SUCCEEDED,
GET_DATA_SUCCEEDED,
LOAD_PLUGIN,
PLUGIN_DELETED,
PLUGIN_LOADED,
UNFREEZE_APP,
- UNSET_HAS_USERS_PLUGIN,
UPDATE_PLUGIN,
} from './constants';
@@ -36,6 +36,13 @@ export function freezeApp(data) {
};
}
+export function getInfosDataSucceeded(data) {
+ return {
+ type: GET_INFOS_DATA_SUCCEEDED,
+ data,
+ };
+}
+
export function getDataSucceeded(data) {
return {
type: GET_DATA_SUCCEEDED,
@@ -70,12 +77,6 @@ export function unfreezeApp() {
};
}
-export function unsetHasUserPlugin() {
- return {
- type: UNSET_HAS_USERS_PLUGIN,
- };
-}
-
export function updatePlugin(pluginId, updatedKey, updatedValue) {
return {
type: UPDATE_PLUGIN,
diff --git a/packages/strapi-admin/admin/src/containers/App/constants.js b/packages/strapi-admin/admin/src/containers/App/constants.js
index b1c21a74ab..feaab13852 100644
--- a/packages/strapi-admin/admin/src/containers/App/constants.js
+++ b/packages/strapi-admin/admin/src/containers/App/constants.js
@@ -9,9 +9,10 @@ export const LOAD_PLUGIN = 'app/App/LOAD_PLUGIN';
export const PLUGIN_LOADED = 'app/App/PLUGIN_LOADED';
export const PLUGIN_DELETED = 'app/App/PLUGIN_DELETED';
export const UNFREEZE_APP = 'app/App/UNFREEZE_APP';
-export const UNSET_HAS_USERS_PLUGIN = 'app/App/UNSET_HAS_USERS_PLUGIN';
+
export const UPDATE_PLUGIN = 'app/App/UPDATE_PLUGIN';
export const DISABLE_GLOBAL_OVERLAY_BLOCKER =
'app/App/OverlayBlocker/DISABLE_GLOBAL_OVERLAY_BLOCKER';
export const ENABLE_GLOBAL_OVERLAY_BLOCKER = 'app/App/OverlayBlocker/ENABLE_GLOBAL_OVERLAY_BLOCKER';
export const GET_DATA_SUCCEEDED = 'app/App/GET_DATA_SUCCEEDED';
+export const GET_INFOS_DATA_SUCCEEDED = 'admin/App/GET_INFOS_DATA_SUCCEEDED';
diff --git a/packages/strapi-admin/admin/src/containers/App/index.js b/packages/strapi-admin/admin/src/containers/App/index.js
index 31ad25d4c8..69af4cc365 100644
--- a/packages/strapi-admin/admin/src/containers/App/index.js
+++ b/packages/strapi-admin/admin/src/containers/App/index.js
@@ -35,25 +35,31 @@ function App(props) {
getDataRef.current = props.getDataSucceeded;
useEffect(() => {
- const getData = async () => {
- const currentToken = auth.getToken();
+ const currentToken = auth.getToken();
- if (currentToken) {
- try {
- const {
- data: { token },
- } = await request('/admin/renew-token', {
- method: 'POST',
- body: { token: currentToken },
- });
- auth.updateToken(token);
- } catch (err) {
- // Refresh app
- auth.clearAppStorage();
- window.location.reload();
- }
+ const renewToken = async () => {
+ try {
+ const {
+ data: { token },
+ } = await request('/admin/renew-token', {
+ method: 'POST',
+ body: { token: currentToken },
+ });
+ auth.updateToken(token);
+ } catch (err) {
+ // Refresh app
+ auth.clearAppStorage();
+ window.location.reload();
}
+ };
+ if (currentToken) {
+ renewToken();
+ }
+ }, []);
+
+ useEffect(() => {
+ const getData = async () => {
try {
const { data } = await request('/admin/init', { method: 'GET' });
@@ -87,7 +93,7 @@ function App(props) {
};
getData();
- }, [getDataRef]);
+ }, []);
if (isLoading) {
return ;
diff --git a/packages/strapi-admin/admin/src/containers/App/reducer.js b/packages/strapi-admin/admin/src/containers/App/reducer.js
index fb9b9075bf..669423ca97 100644
--- a/packages/strapi-admin/admin/src/containers/App/reducer.js
+++ b/packages/strapi-admin/admin/src/containers/App/reducer.js
@@ -6,16 +6,17 @@ import {
DISABLE_GLOBAL_OVERLAY_BLOCKER,
ENABLE_GLOBAL_OVERLAY_BLOCKER,
FREEZE_APP,
+ GET_INFOS_DATA_SUCCEEDED,
GET_DATA_SUCCEEDED,
PLUGIN_DELETED,
PLUGIN_LOADED,
UNFREEZE_APP,
- UNSET_HAS_USERS_PLUGIN,
UPDATE_PLUGIN,
} from './constants';
const packageVersion = packageJSON.version;
const initialState = fromJS({
+ appInfos: {},
autoReload: false,
blockApp: false,
currentEnvironment: 'development',
@@ -43,24 +44,27 @@ function appReducer(state = initialState, action) {
return null;
});
- case GET_DATA_SUCCEEDED: {
- const {
- data: { hasAdmin, uuid, currentEnvironment, autoReload, strapiVersion },
- } = action;
-
- if (strapiVersion !== state.get('strapiVersion')) {
+ case GET_INFOS_DATA_SUCCEEDED: {
+ if (action.data.strapiVersion !== state.get('strapiVersion')) {
console.error(
- `It seems that the built version ${packageVersion} is different than your project's one (${strapiVersion})`
+ `It seems that the built version ${packageVersion} is different than your project's one (${action.data.strapiVersion})`
);
console.error('Please delete your `.cache` and `build` folders and restart your app');
}
+ return (
+ state
+ .update('appInfos', () => action.data)
+ // Keep this for plugins legacy
+ .update('autoReload', () => action.data.autoReload)
+ .update('currentEnvironment', () => action.data.currentEnvironment)
+ );
+ }
+ case GET_DATA_SUCCEEDED: {
return state
.update('isLoading', () => false)
- .update('hasAdminUser', () => hasAdmin)
- .update('uuid', () => uuid)
- .update('autoReload', () => autoReload)
- .update('currentEnvironment', () => currentEnvironment);
+ .update('hasAdminUser', () => action.data.hasAdmin)
+ .update('uuid', () => action.data.uuid);
}
case PLUGIN_LOADED:
return state.setIn(['plugins', action.plugin.id], fromJS(action.plugin));
@@ -73,8 +77,7 @@ function appReducer(state = initialState, action) {
return state.deleteIn(['plugins', action.plugin]);
case UNFREEZE_APP:
return state.set('blockApp', false).set('overlayBlockerData', null);
- case UNSET_HAS_USERS_PLUGIN:
- return state.set('hasUserPlugin', false);
+
default:
return state;
}
diff --git a/packages/strapi-admin/admin/src/containers/App/selectors.js b/packages/strapi-admin/admin/src/containers/App/selectors.js
index f69fdf9af1..7926431e02 100644
--- a/packages/strapi-admin/admin/src/containers/App/selectors.js
+++ b/packages/strapi-admin/admin/src/containers/App/selectors.js
@@ -9,47 +9,22 @@ const selectApp = () => state => state.get('app');
* Select the language locale
*/
-const selectPlugins = () =>
- createSelector(
- selectApp(),
- appState => appState.get('plugins')
- );
+const selectPlugins = () => createSelector(selectApp(), appState => appState.get('plugins'));
-const makeSelectApp = () =>
- createSelector(
- selectApp(),
- appState => appState.toJS()
- );
+const makeSelectApp = () => createSelector(selectApp(), appState => appState.toJS());
const selectHasUserPlugin = () =>
- createSelector(
- selectApp(),
- appState => appState.get('hasUserPlugin')
- );
+ createSelector(selectApp(), appState => appState.get('hasUserPlugin'));
const makeSelectShowGlobalAppBlocker = () =>
- createSelector(
- selectApp(),
- appState => appState.get('showGlobalAppBlocker')
- );
+ createSelector(selectApp(), appState => appState.get('showGlobalAppBlocker'));
-const makeSelectBlockApp = () =>
- createSelector(
- selectApp(),
- appState => appState.get('blockApp')
- );
+const makeSelectBlockApp = () => createSelector(selectApp(), appState => appState.get('blockApp'));
const makeSelectOverlayBlockerProps = () =>
- createSelector(
- selectApp(),
- appState => appState.get('overlayBlockerData')
- );
+ createSelector(selectApp(), appState => appState.get('overlayBlockerData'));
-const makeSelectUuid = () =>
- createSelector(
- selectApp(),
- appState => appState.get('uuid')
- );
+const makeSelectUuid = () => createSelector(selectApp(), appState => appState.get('uuid'));
export default makeSelectApp;
export {
diff --git a/packages/strapi-admin/admin/src/containers/App/tests/actions.test.js b/packages/strapi-admin/admin/src/containers/App/tests/actions.test.js
index 714a289984..3ae0e5b572 100644
--- a/packages/strapi-admin/admin/src/containers/App/tests/actions.test.js
+++ b/packages/strapi-admin/admin/src/containers/App/tests/actions.test.js
@@ -1,19 +1,21 @@
import {
FREEZE_APP,
+ GET_DATA_SUCCEEDED,
+ GET_INFOS_DATA_SUCCEEDED,
LOAD_PLUGIN,
PLUGIN_DELETED,
PLUGIN_LOADED,
UNFREEZE_APP,
- UNSET_HAS_USERS_PLUGIN,
UPDATE_PLUGIN,
} from '../constants';
import {
freezeApp,
loadPlugin,
+ getInfosDataSucceeded,
+ getDataSucceeded,
pluginDeleted,
pluginLoaded,
unfreezeApp,
- unsetHasUserPlugin,
updatePlugin,
} from '../actions';
@@ -40,6 +42,30 @@ describe(' actions', () => {
});
});
+ describe('getDataSucceeded', () => {
+ it('shoudl return the correct type and the passed data', () => {
+ const data = { ok: true };
+ const expected = {
+ type: GET_DATA_SUCCEEDED,
+ data,
+ };
+
+ expect(getDataSucceeded(data)).toEqual(expected);
+ });
+ });
+
+ describe('getInfosDataSucceeded', () => {
+ it('shoudl return the correct type and the passed data', () => {
+ const data = { ok: true };
+ const expected = {
+ type: GET_INFOS_DATA_SUCCEEDED,
+ data,
+ };
+
+ expect(getInfosDataSucceeded(data)).toEqual(expected);
+ });
+ });
+
describe('loadPlugin', () => {
it('should return the correct type and the passed data', () => {
const plugin = {
@@ -82,16 +108,6 @@ describe(' actions', () => {
});
});
- describe('unsetHasUserPlugin', () => {
- it('should return the correct type', () => {
- const expected = {
- type: UNSET_HAS_USERS_PLUGIN,
- };
-
- expect(unsetHasUserPlugin()).toEqual(expected);
- });
- });
-
describe('updatePlugin', () => {
it('should return the correct type and the passed data', () => {
const pluginId = 'content-manager';
@@ -104,9 +120,7 @@ describe(' actions', () => {
updatedValue,
};
- expect(updatePlugin(pluginId, updatedKey, updatedValue)).toEqual(
- expected
- );
+ expect(updatePlugin(pluginId, updatedKey, updatedValue)).toEqual(expected);
});
});
});
diff --git a/packages/strapi-admin/admin/src/containers/App/tests/reducer.test.js b/packages/strapi-admin/admin/src/containers/App/tests/reducer.test.js
index 8685633911..095e129c0e 100644
--- a/packages/strapi-admin/admin/src/containers/App/tests/reducer.test.js
+++ b/packages/strapi-admin/admin/src/containers/App/tests/reducer.test.js
@@ -4,10 +4,11 @@ import {
disableGlobalOverlayBlocker,
enableGlobalOverlayBlocker,
freezeApp,
+ getDataSucceeded,
+ getInfosDataSucceeded,
pluginDeleted,
pluginLoaded,
unfreezeApp,
- unsetHasUserPlugin,
updatePlugin,
} from '../actions';
import appReducer from '../reducer';
@@ -17,6 +18,7 @@ describe(' reducer', () => {
beforeEach(() => {
state = fromJS({
+ appInfos: {},
autoReload: false,
blockApp: false,
currentEnvironment: 'development',
@@ -96,9 +98,34 @@ describe(' reducer', () => {
expect(appReducer(state, unfreezeApp())).toEqual(expectedResult);
});
- it('should handle the unsetHasUserPlugin action correclty', () => {
- const expectedResult = state.set('hasUserPlugin', false);
+ describe('GET_INFOS_DATA_SUCCEEDED', () => {
+ it('should handle the set the data correctly', () => {
+ const data = {
+ autoReload: true,
+ communityEdition: false,
+ currentEnvironment: 'test',
+ nodeVersion: 'v12.14.1',
+ strapiVersion: '3.2.1',
+ };
+ const expected = state
+ .set('appInfos', data)
+ .set('autoReload', true)
+ .set('currentEnvironment', 'test');
- expect(appReducer(state, unsetHasUserPlugin())).toEqual(expectedResult);
+ expect(appReducer(state, getInfosDataSucceeded(data))).toEqual(expected);
+ });
+ });
+
+ describe('GET_DATA_SUCCEEDED', () => {
+ it('should handle the set the data correctly', () => {
+ const expected = state
+ .set('hasAdminUser', true)
+ .set('uuid', 'true')
+ .set('isLoading', false);
+
+ expect(appReducer(state, getDataSucceeded({ hasAdmin: true, uuid: 'true' }))).toEqual(
+ expected
+ );
+ });
});
});
diff --git a/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/Detail/index.js b/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/Detail/index.js
new file mode 100644
index 0000000000..073fb0be39
--- /dev/null
+++ b/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/Detail/index.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Text } from '@buffetjs/core';
+import InfoText from '../InfoText';
+import Link from '../Link';
+import Wrapper from '../Wrapper';
+
+const Detail = ({ content, link, title }) => {
+ return (
+
+
+ {title}
+
+
+ {link && }
+
+ );
+};
+
+Detail.defaultProps = {
+ link: null,
+};
+
+Detail.propTypes = {
+ content: PropTypes.string.isRequired,
+ link: PropTypes.object,
+ title: PropTypes.string.isRequired,
+};
+
+export default Detail;
diff --git a/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/InfoText/index.js b/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/InfoText/index.js
new file mode 100644
index 0000000000..be7e0855ce
--- /dev/null
+++ b/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/InfoText/index.js
@@ -0,0 +1,19 @@
+import React from 'react';
+import { Padded, Text } from '@buffetjs/core';
+import PropTypes from 'prop-types';
+
+const InfoText = ({ content }) => {
+ return (
+
+
+ {content}
+
+
+ );
+};
+
+InfoText.propTypes = {
+ content: PropTypes.string.isRequired,
+};
+
+export default InfoText;
diff --git a/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/Link/components.js b/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/Link/components.js
new file mode 100644
index 0000000000..5dbe575788
--- /dev/null
+++ b/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/Link/components.js
@@ -0,0 +1,22 @@
+import styled from 'styled-components';
+import { Text } from '@buffetjs/core';
+import { Arrow } from '@buffetjs/icons';
+
+const LinkText = styled(Text)`
+ color: ${({ theme }) => theme.main.colors.mediumBlue};
+ > a {
+ &:hover {
+ color: ${({ theme }) => theme.main.colors.mediumBlue};
+ text-decoration: none;
+ }
+ }
+`;
+
+export const LinkArrow = styled(Arrow)`
+ transform: rotate(45deg);
+ margin-top: 2px;
+ margin-left: 10px;
+ color: ${({ theme }) => theme.main.colors.blue};
+`;
+
+export default LinkText;
diff --git a/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/Link/index.js b/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/Link/index.js
new file mode 100644
index 0000000000..7656dac438
--- /dev/null
+++ b/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/Link/index.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import { Padded } from '@buffetjs/core';
+import PropTypes from 'prop-types';
+import BaselineAlignement from '../../../../components/BaselineAlignement';
+import LinkText, { LinkArrow } from './components';
+
+const Link = ({ href, label }) => {
+ return (
+
+
+
+
+ {label}
+
+
+
+
+ );
+};
+
+Link.propTypes = {
+ href: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired,
+};
+
+export default Link;
diff --git a/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/Wrapper/index.js b/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/Wrapper/index.js
new file mode 100644
index 0000000000..6fd0dd0de1
--- /dev/null
+++ b/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/Wrapper/index.js
@@ -0,0 +1,7 @@
+import styled from 'styled-components';
+
+const Wrapper = styled.div`
+ width: 50%;
+`;
+
+export default Wrapper;
diff --git a/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/index.js b/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/index.js
new file mode 100644
index 0000000000..43226824f0
--- /dev/null
+++ b/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/components/index.js
@@ -0,0 +1,4 @@
+export { default as Detail } from './Detail';
+export { default as InfoText } from './InfoText';
+export { default as Link } from './Link';
+export { default as Wrapper } from './Wrapper';
diff --git a/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/index.js b/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/index.js
new file mode 100644
index 0000000000..6ac58671dc
--- /dev/null
+++ b/packages/strapi-admin/admin/src/containers/ApplicationInfosPage/index.js
@@ -0,0 +1,86 @@
+import React, { memo, useMemo } from 'react';
+import { Header } from '@buffetjs/custom';
+import { Flex, Padded, Text } from '@buffetjs/core';
+import { useSelector } from 'react-redux';
+import { createSelector } from 'reselect';
+import { useIntl } from 'react-intl';
+import BaselineAlignement from '../../components/BaselineAlignement';
+import Bloc from '../../components/Bloc';
+import PageTitle from '../../components/SettingsPageTitle';
+import makeSelectApp from '../App/selectors';
+import makeSelectAdmin from '../Admin/selectors';
+import { Detail, InfoText } from './components';
+
+const makeSelectAppInfos = () => createSelector(makeSelectApp(), appState => appState.appInfos);
+const makeSelectLatestRelease = () =>
+ createSelector(makeSelectAdmin(), adminState => adminState.latestStrapiReleaseTag);
+
+const ApplicationInfosPage = () => {
+ const { formatMessage } = useIntl();
+ const selectAppInfos = useMemo(makeSelectAppInfos, []);
+ const selectLatestRealase = useMemo(makeSelectLatestRelease, []);
+ const appInfos = useSelector(state => selectAppInfos(state));
+ const latestStrapiReleaseTag = useSelector(state => selectLatestRealase(state));
+
+ const currentPlan = appInfos.communityEdition
+ ? 'app.components.UpgradePlanModal.text-ce'
+ : 'app.components.UpgradePlanModal.text-ee';
+
+ const headerProps = {
+ title: { label: formatMessage({ id: 'Settings.application.title' }) },
+ content: formatMessage({
+ id: 'Settings.application.description',
+ }),
+ };
+ const pricingLabel = formatMessage({ id: 'Settings.application.link-pricing' });
+ const upgradeLabel = formatMessage({ id: 'Settings.application.link-upgrade' });
+ const strapiVersion = formatMessage({ id: 'Settings.application.strapi-version' });
+ const nodeVersion = formatMessage({ id: 'Settings.application.node-version' });
+ const editionTitle = formatMessage({ id: 'Settings.application.edition-title' });
+
+ const shouldShowUpgradeLink = `v${appInfos.strapiVersion}` !== latestStrapiReleaseTag;
+
+ /* eslint-disable indent */
+ const upgradeLink = shouldShowUpgradeLink
+ ? {
+ label: upgradeLabel,
+ href: `https://github.com/strapi/strapi/releases/tag/${latestStrapiReleaseTag}`,
+ }
+ : null;
+ /* eslint-enable indent */
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {nodeVersion}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default memo(ApplicationInfosPage);
diff --git a/packages/strapi-admin/admin/src/containers/HomePage/index.js b/packages/strapi-admin/admin/src/containers/HomePage/index.js
index 013ecbfde3..dd3723a183 100644
--- a/packages/strapi-admin/admin/src/containers/HomePage/index.js
+++ b/packages/strapi-admin/admin/src/containers/HomePage/index.js
@@ -4,16 +4,12 @@
*
*/
/* eslint-disable */
-import React, { memo, useMemo, useEffect } from 'react';
+import React, { memo, useMemo } from 'react';
import { FormattedMessage } from 'react-intl';
-import { get, isNil, upperFirst } from 'lodash';
+import { get, upperFirst } from 'lodash';
import { auth, LoadingIndicatorPage } from 'strapi-helper-plugin';
-import axios from 'axios';
-import { useSelector } from 'react-redux';
-
import PageTitle from '../../components/PageTitle';
import { useModels } from '../../hooks';
-import { STRAPI_UPDATE_NOTIF } from '../../config';
import useFetch from './hooks';
import { ALink, Block, Container, LinkWrapper, P, Wave, Separator } from './components';
@@ -65,54 +61,7 @@ const SOCIAL_LINKS = [
},
];
-const HomePage = ({ global: { strapiVersion }, history: { push } }) => {
- const notifications = useSelector(state => state.get('newNotification').notifications);
-
- useEffect(() => {
- const getStrapiLatestRelease = async () => {
- try {
- const notificationAlreadyExist =
- notifications.findIndex(notification => notification.uid === 'STRAPI_UPDATE_NOTIF') != -1;
-
- const showUpdateNotif =
- STRAPI_UPDATE_NOTIF &&
- !JSON.parse(localStorage.getItem('STRAPI_UPDATE_NOTIF')) &&
- !notificationAlreadyExist;
-
- if (showUpdateNotif) {
- const res = await fetch('https://api.github.com/repos/strapi/strapi/releases/latest');
-
- const data = await res.json();
-
- if (strapiVersion !== data.name.split('v').join('')) {
- strapi.notification.toggle({
- type: 'info',
- message: { id: 'notification.version.update.message' },
- link: {
- url: `https://github.com/strapi/strapi/releases/tag/${data.name}`,
- label: {
- id: 'notification.version.update.link',
- },
- },
- blockTransition: true,
- // Used to check if the notification is already displayed
- // to avoid multiple notifications each time the user goes back to the home page.
- uid: 'STRAPI_UPDATE_NOTIF',
- onClose: () => localStorage.setItem('STRAPI_UPDATE_NOTIF', true),
- });
- }
- }
- } catch (e) {
- strapi.notification.toggle({
- type: 'warning',
- message: { id: 'notification.error' },
- });
- }
- };
-
- getStrapiLatestRelease();
- }, []);
-
+const HomePage = ({ history: { push } }) => {
const { error, isLoading, posts } = useFetch();
// Temporary until we develop the menu API
const { collectionTypes, singleTypes, isLoading: isLoadingForModels } = useModels();
diff --git a/packages/strapi-admin/admin/src/containers/LeftMenu/index.js b/packages/strapi-admin/admin/src/containers/LeftMenu/index.js
index 02a75815b0..fb06d22078 100644
--- a/packages/strapi-admin/admin/src/containers/LeftMenu/index.js
+++ b/packages/strapi-admin/admin/src/containers/LeftMenu/index.js
@@ -30,10 +30,19 @@ import reducer, { initialState } from './reducer';
import Loader from './Loader';
import Wrapper from './Wrapper';
-const LeftMenu = forwardRef(({ version, plugins }, ref) => {
+const LeftMenu = forwardRef(({ latestStrapiReleaseTag, version, plugins }, ref) => {
const location = useLocation();
const permissions = useContext(UserContext);
const { menu: settingsMenu } = useSettingsMenu(true);
+
+ // TODO: this needs to be added to the settings API in the v4
+ const settingsLinkNotificationCount = useMemo(() => {
+ if (`v${version}` !== latestStrapiReleaseTag) {
+ return 1;
+ }
+
+ return 0;
+ }, [latestStrapiReleaseTag, version]);
const [
{
collectionTypesSectionLinks,
@@ -43,7 +52,9 @@ const LeftMenu = forwardRef(({ version, plugins }, ref) => {
singleTypesSectionLinks,
},
dispatch,
- ] = useReducer(reducer, initialState, () => init(initialState, plugins, settingsMenu));
+ ] = useReducer(reducer, initialState, () =>
+ init(initialState, plugins, settingsMenu, settingsLinkNotificationCount)
+ );
const generalSectionLinksFiltered = useMemo(() => filterLinks(generalSectionLinks), [
generalSectionLinks,
]);
@@ -197,6 +208,7 @@ const LeftMenu = forwardRef(({ version, plugins }, ref) => {
});
LeftMenu.propTypes = {
+ latestStrapiReleaseTag: PropTypes.string.isRequired,
version: PropTypes.string.isRequired,
plugins: PropTypes.object.isRequired,
};
diff --git a/packages/strapi-admin/admin/src/containers/LeftMenu/init.js b/packages/strapi-admin/admin/src/containers/LeftMenu/init.js
index 0450d15f88..821d057ba6 100644
--- a/packages/strapi-admin/admin/src/containers/LeftMenu/init.js
+++ b/packages/strapi-admin/admin/src/containers/LeftMenu/init.js
@@ -3,7 +3,7 @@ import { SETTINGS_BASE_URL } from '../../config';
import { sortLinks } from '../../utils';
import { getSettingsMenuLinksPermissions } from './utils';
-const init = (initialState, plugins = {}, settingsMenu = []) => {
+const init = (initialState, plugins = {}, settingsMenu = [], settingsLinkNotificationCount = 0) => {
const settingsLinkPermissions = getSettingsMenuLinksPermissions(settingsMenu);
const pluginsLinks = Object.values(plugins).reduce((acc, current) => {
@@ -21,8 +21,10 @@ const init = (initialState, plugins = {}, settingsMenu = []) => {
if (!settingsLinkPermissions.filter(perm => perm === null).length && settingsLinkIndex !== -1) {
const permissionsPath = ['generalSectionLinks', settingsLinkIndex, 'permissions'];
+ const notificationPath = ['generalSectionLinks', settingsLinkIndex, 'notificationsCount'];
set(initialState, permissionsPath, settingsLinkPermissions);
+ set(initialState, notificationPath, settingsLinkNotificationCount);
}
if (sortedLinks.length) {
diff --git a/packages/strapi-admin/admin/src/containers/LeftMenu/reducer.js b/packages/strapi-admin/admin/src/containers/LeftMenu/reducer.js
index 90a6b16b0b..c8d8c42a1a 100644
--- a/packages/strapi-admin/admin/src/containers/LeftMenu/reducer.js
+++ b/packages/strapi-admin/admin/src/containers/LeftMenu/reducer.js
@@ -13,6 +13,7 @@ const initialState = {
destination: '/list-plugins',
isDisplayed: false,
permissions: adminPermissions.marketplace.main,
+ notificationsCount: 0,
},
{
icon: 'shopping-basket',
@@ -20,6 +21,7 @@ const initialState = {
destination: '/marketplace',
isDisplayed: false,
permissions: adminPermissions.marketplace.main,
+ notificationsCount: 0,
},
{
icon: 'cog',
@@ -29,6 +31,7 @@ const initialState = {
// Permissions of this link are retrieved in the init phase
// using the settings menu
permissions: [],
+ notificationsCount: 0,
},
],
singleTypesSectionLinks: [],
diff --git a/packages/strapi-admin/admin/src/containers/LeftMenu/tests/init.test.js b/packages/strapi-admin/admin/src/containers/LeftMenu/tests/init.test.js
index 5a2a659e32..d1c18c2164 100644
--- a/packages/strapi-admin/admin/src/containers/LeftMenu/tests/init.test.js
+++ b/packages/strapi-admin/admin/src/containers/LeftMenu/tests/init.test.js
@@ -112,6 +112,7 @@ describe('ADMIN | LeftMenu | init', () => {
title: 'Settings.webhooks.title',
to: '/settings/webhooks',
name: 'webhooks',
+
permissions: [
{ action: 'admin::webhook.create', subject: null },
{ action: 'admin::webhook.read', subject: null },
@@ -223,6 +224,7 @@ describe('ADMIN | LeftMenu | init', () => {
label: 'app.components.LeftMenuLinkContainer.settings',
isDisplayed: false,
destination: SETTINGS_BASE_URL,
+ notificationsCount: 0,
permissions: [
// webhooks
{ action: 'admin::webhook.create', subject: null },
diff --git a/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/Icon.js b/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/Icon.js
new file mode 100644
index 0000000000..eb4a20efdb
--- /dev/null
+++ b/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/Icon.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import styled from 'styled-components';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+
+const LeftMenuIcon = styled(({ ...props }) => )`
+ position: absolute;
+ top: calc(50% - 0.25rem);
+ left: 1.5rem;
+ font-size: 0.5rem;
+ color: ${props => props.theme.main.colors.leftMenu['link-color']};
+`;
+
+export default LeftMenuIcon;
diff --git a/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/Link.js b/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/Link.js
new file mode 100644
index 0000000000..3f3d3bb896
--- /dev/null
+++ b/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/Link.js
@@ -0,0 +1,25 @@
+import styled from 'styled-components';
+import { NavLink } from 'react-router-dom';
+
+const Link = styled(NavLink)`
+ display: flex;
+ justify-content: space-between;
+ position: relative;
+ padding-left: 30px;
+ height: 34px;
+ border-radius: 2px;
+ &.active {
+ background-color: #e9eaeb;
+ > p {
+ font-weight: 600;
+ }
+ > svg {
+ color: #2d3138;
+ }
+ }
+ &:hover {
+ text-decoration: none;
+ }
+`;
+
+export default Link;
diff --git a/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/Notif.js b/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/Notif.js
new file mode 100644
index 0000000000..327211aa39
--- /dev/null
+++ b/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/Notif.js
@@ -0,0 +1,17 @@
+import styled from 'styled-components';
+
+const Notif = styled.div`
+ margin: auto;
+ margin-right: 15px;
+
+ &:before {
+ content: '';
+ display: flex;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background-color: #007dff;
+ }
+`;
+
+export default Notif;
diff --git a/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/Wrapper.js b/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/Wrapper.js
new file mode 100644
index 0000000000..72e96acbbf
--- /dev/null
+++ b/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/Wrapper.js
@@ -0,0 +1,9 @@
+import styled from 'styled-components';
+
+const Wrapper = styled.div`
+ position: relative;
+ margin-bottom: -5px;
+ padding: 25px 20px 0 20px;
+`;
+
+export default Wrapper;
diff --git a/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/index.js b/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/index.js
new file mode 100644
index 0000000000..0ff5427975
--- /dev/null
+++ b/packages/strapi-admin/admin/src/containers/SettingsPage/components/ApplicationDetailLink/index.js
@@ -0,0 +1,27 @@
+import React, { memo } from 'react';
+import { Text } from '@buffetjs/core';
+import { FormattedMessage } from 'react-intl';
+import { useGlobalContext } from 'strapi-helper-plugin';
+import Icon from './Icon';
+import Link from './Link';
+import Notif from './Notif';
+import Wrapper from './Wrapper';
+
+const ApplicationDetailLink = () => {
+ const { latestStrapiReleaseTag, strapiVersion } = useGlobalContext();
+ const showNotif = `v${strapiVersion}` !== latestStrapiReleaseTag;
+
+ return (
+
+
+
+
+
+
+ {showNotif && }
+
+
+ );
+};
+
+export default memo(ApplicationDetailLink);
diff --git a/packages/strapi-admin/admin/src/containers/SettingsPage/components/MenuWrapper/index.js b/packages/strapi-admin/admin/src/containers/SettingsPage/components/MenuWrapper/index.js
new file mode 100644
index 0000000000..16af0a6939
--- /dev/null
+++ b/packages/strapi-admin/admin/src/containers/SettingsPage/components/MenuWrapper/index.js
@@ -0,0 +1,8 @@
+import styled from 'styled-components';
+
+// background-color: red;
+const MenuWrapper = styled.div`
+ background-color: ${props => props.theme.main.colors.mediumGrey};
+`;
+
+export default MenuWrapper;
diff --git a/packages/strapi-admin/admin/src/containers/SettingsPage/SettingDispatcher.js b/packages/strapi-admin/admin/src/containers/SettingsPage/components/SettingDispatcher/index.js
similarity index 90%
rename from packages/strapi-admin/admin/src/containers/SettingsPage/SettingDispatcher.js
rename to packages/strapi-admin/admin/src/containers/SettingsPage/components/SettingDispatcher/index.js
index 8d8e13692e..cbff697492 100644
--- a/packages/strapi-admin/admin/src/containers/SettingsPage/SettingDispatcher.js
+++ b/packages/strapi-admin/admin/src/containers/SettingsPage/components/SettingDispatcher/index.js
@@ -2,7 +2,7 @@ import React, { memo } from 'react';
import { useGlobalContext } from 'strapi-helper-plugin';
import { get } from 'lodash';
import { useParams } from 'react-router-dom';
-import PageTitle from '../../components/SettingsPageTitle';
+import PageTitle from '../../../../components/SettingsPageTitle';
const SettingDispatcher = () => {
const { plugins } = useGlobalContext();
diff --git a/packages/strapi-admin/admin/src/containers/SettingsPage/StyledLeftMenu.js b/packages/strapi-admin/admin/src/containers/SettingsPage/components/StyledLeftMenu/index.js
similarity index 100%
rename from packages/strapi-admin/admin/src/containers/SettingsPage/StyledLeftMenu.js
rename to packages/strapi-admin/admin/src/containers/SettingsPage/components/StyledLeftMenu/index.js
diff --git a/packages/strapi-admin/admin/src/containers/SettingsPage/Wrapper.js b/packages/strapi-admin/admin/src/containers/SettingsPage/components/Wrapper/index.js
similarity index 100%
rename from packages/strapi-admin/admin/src/containers/SettingsPage/Wrapper.js
rename to packages/strapi-admin/admin/src/containers/SettingsPage/components/Wrapper/index.js
diff --git a/packages/strapi-admin/admin/src/containers/SettingsPage/components/index.js b/packages/strapi-admin/admin/src/containers/SettingsPage/components/index.js
new file mode 100644
index 0000000000..5c8da5726e
--- /dev/null
+++ b/packages/strapi-admin/admin/src/containers/SettingsPage/components/index.js
@@ -0,0 +1,5 @@
+export { default as ApplicationDetailLink } from './ApplicationDetailLink';
+export { default as MenuWrapper } from './MenuWrapper';
+export { default as SettingDispatcher } from './SettingDispatcher';
+export { default as StyledLeftMenu } from './StyledLeftMenu';
+export { default as Wrapper } from './Wrapper';
diff --git a/packages/strapi-admin/admin/src/containers/SettingsPage/index.js b/packages/strapi-admin/admin/src/containers/SettingsPage/index.js
index c6eac62e42..d24ed458e5 100644
--- a/packages/strapi-admin/admin/src/containers/SettingsPage/index.js
+++ b/packages/strapi-admin/admin/src/containers/SettingsPage/index.js
@@ -24,23 +24,28 @@ import HeaderSearch from '../../components/HeaderSearch';
import PageTitle from '../../components/PageTitle';
import { useSettingsMenu } from '../../hooks';
import { retrieveGlobalLinks } from '../../utils';
+import ApplicationInfosPage from '../ApplicationInfosPage';
import SettingsSearchHeaderProvider from '../SettingsHeaderSearchContextProvider';
import UsersEditPage from '../Users/ProtectedEditPage';
import UsersListPage from '../Users/ProtectedListPage';
import RolesEditPage from '../Roles/ProtectedEditPage';
+import WebhooksCreateView from '../Webhooks/ProtectedCreateView';
+import WebhooksEditView from '../Webhooks/ProtectedEditView';
+import WebhooksListView from '../Webhooks/ProtectedListView';
+import {
+ ApplicationDetailLink,
+ MenuWrapper,
+ SettingDispatcher,
+ StyledLeftMenu,
+ Wrapper,
+} from './components';
+
import {
createRoute,
- findFirstAllowedEndpoint,
createPluginsLinksRoutes,
makeUniqueRoutes,
getSectionsToDisplay,
} from './utils';
-import WebhooksCreateView from '../Webhooks/ProtectedCreateView';
-import WebhooksEditView from '../Webhooks/ProtectedEditView';
-import WebhooksListView from '../Webhooks/ProtectedListView';
-import SettingDispatcher from './SettingDispatcher';
-import LeftMenu from './StyledLeftMenu';
-import Wrapper from './Wrapper';
function SettingsPage() {
const { settingId } = useParams();
@@ -50,14 +55,6 @@ function SettingsPage() {
const { isLoading, menu } = useSettingsMenu();
const { formatMessage } = useIntl();
const pluginsGlobalLinks = useMemo(() => retrieveGlobalLinks(plugins), [plugins]);
- const firstAvailableEndpoint = useMemo(() => {
- // Don't need to compute while permissions are being checked
- if (isLoading) {
- return '';
- }
-
- return findFirstAllowedEndpoint(menu);
- }, [menu, isLoading]);
// Create all the that needs to be created by the plugins
// For instance the upload plugin needs to create a
@@ -95,8 +92,8 @@ function SettingsPage() {
return ;
}
- if (!settingId && firstAvailableEndpoint) {
- return ;
+ if (!settingId) {
+ return ;
}
const settingTitle = formatMessage({ id: 'app.components.LeftMenuLinkContainer.settings' });
@@ -109,14 +106,23 @@ function SettingsPage() {
-
- {filteredMenu.map(item => {
- return ;
- })}
-
+
+
+
+ {filteredMenu.map(item => {
+ return ;
+ })}
+
+
+
+