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 ; + })} + +
+
+