mirror of
https://github.com/strapi/strapi.git
synced 2025-08-10 01:38:10 +00:00
Merge pull request #8336 from strapi/front/application-settings
Add application details page
This commit is contained in:
commit
e26b45e6be
@ -1,6 +1,7 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const A = styled.a`
|
const A = styled.a`
|
||||||
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-top: 0.7rem;
|
padding-top: 0.7rem;
|
||||||
padding-bottom: 0.2rem;
|
padding-bottom: 0.2rem;
|
||||||
@ -10,7 +11,6 @@ const A = styled.a`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: ${props => props.theme.main.colors.leftMenu['link-color']};
|
color: ${props => props.theme.main.colors.leftMenu['link-color']};
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: block;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -31,6 +31,8 @@ const A = styled.a`
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.linkActive {
|
&.linkActive {
|
||||||
|
padding-right: 2.3rem;
|
||||||
|
|
||||||
color: white !important;
|
color: white !important;
|
||||||
border-left: 0.3rem solid ${props => props.theme.main.colors.strapi.blue};
|
border-left: 0.3rem solid ${props => props.theme.main.colors.strapi.blue};
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,10 @@ import PropTypes from 'prop-types';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Link, withRouter } from 'react-router-dom';
|
import { Link, withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import en from '../../../translations/en.json';
|
import en from '../../../translations/en.json';
|
||||||
import LeftMenuIcon from './LeftMenuIcon';
|
import LeftMenuIcon from './LeftMenuIcon';
|
||||||
import A from './A';
|
import A from './A';
|
||||||
|
import NotificationCount from './NotificationCount';
|
||||||
|
|
||||||
const LinkLabel = styled.span`
|
const LinkLabel = styled.span`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -23,7 +23,7 @@ const LinkLabel = styled.span`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// TODO: refacto this file
|
// TODO: refacto this file
|
||||||
const LeftMenuLinkContent = ({ destination, iconName, label, location }) => {
|
const LeftMenuLinkContent = ({ destination, iconName, label, location, notificationsCount }) => {
|
||||||
const isLinkActive = startsWith(
|
const isLinkActive = startsWith(
|
||||||
location.pathname.replace('/admin', '').concat('/'),
|
location.pathname.replace('/admin', '').concat('/'),
|
||||||
destination.concat('/')
|
destination.concat('/')
|
||||||
@ -67,6 +67,7 @@ const LeftMenuLinkContent = ({ destination, iconName, label, location }) => {
|
|||||||
>
|
>
|
||||||
<LeftMenuIcon icon={iconName} />
|
<LeftMenuIcon icon={iconName} />
|
||||||
{content}
|
{content}
|
||||||
|
{notificationsCount > 0 && <NotificationCount count={notificationsCount} />}
|
||||||
</A>
|
</A>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -78,6 +79,7 @@ LeftMenuLinkContent.propTypes = {
|
|||||||
location: PropTypes.shape({
|
location: PropTypes.shape({
|
||||||
pathname: PropTypes.string,
|
pathname: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
notificationsCount: PropTypes.number.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withRouter(LeftMenuLinkContent);
|
export default withRouter(LeftMenuLinkContent);
|
||||||
|
@ -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 }) => (
|
||||||
|
<NotificationWrapper>
|
||||||
|
<Text fontWeight="bold" fontSize="xs" lineHeight="14px" color="#919bae">
|
||||||
|
{count}
|
||||||
|
</Text>
|
||||||
|
</NotificationWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
NotificationCount.defaultProps = {
|
||||||
|
count: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
NotificationCount.propTypes = {
|
||||||
|
count: PropTypes.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotificationCount;
|
@ -9,13 +9,14 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import LeftMenuLinkContent from './LeftMenuLinkContent';
|
import LeftMenuLinkContent from './LeftMenuLinkContent';
|
||||||
|
|
||||||
const LeftMenuLink = ({ destination, iconName, label, location }) => {
|
const LeftMenuLink = ({ destination, iconName, label, location, notificationsCount }) => {
|
||||||
return (
|
return (
|
||||||
<LeftMenuLinkContent
|
<LeftMenuLinkContent
|
||||||
destination={destination}
|
destination={destination}
|
||||||
iconName={iconName}
|
iconName={iconName}
|
||||||
label={label}
|
label={label}
|
||||||
location={location}
|
location={location}
|
||||||
|
notificationsCount={notificationsCount}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -27,6 +28,7 @@ LeftMenuLink.propTypes = {
|
|||||||
location: PropTypes.shape({
|
location: PropTypes.shape({
|
||||||
pathname: PropTypes.string,
|
pathname: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
notificationsCount: PropTypes.number.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
LeftMenuLink.defaultProps = {
|
LeftMenuLink.defaultProps = {
|
||||||
|
@ -3,7 +3,6 @@ import styled from 'styled-components';
|
|||||||
const LeftMenuListLink = styled.div`
|
const LeftMenuListLink = styled.div`
|
||||||
max-height: 180px;
|
max-height: 180px;
|
||||||
margin-bottom: 19px;
|
margin-bottom: 19px;
|
||||||
margin-right: 28px;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ const LeftMenuLinksSection = ({
|
|||||||
iconName={link.icon}
|
iconName={link.icon}
|
||||||
label={link.label}
|
label={link.label}
|
||||||
destination={link.destination}
|
destination={link.destination}
|
||||||
|
notificationsCount={link.notificationsCount || 0}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
@ -5,12 +5,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
GET_STRAPI_LATEST_RELEASE_SUCCEEDED,
|
||||||
GET_USER_PERMISSIONS,
|
GET_USER_PERMISSIONS,
|
||||||
GET_USER_PERMISSIONS_ERROR,
|
GET_USER_PERMISSIONS_ERROR,
|
||||||
GET_USER_PERMISSIONS_SUCCEEDED,
|
GET_USER_PERMISSIONS_SUCCEEDED,
|
||||||
SET_APP_ERROR,
|
SET_APP_ERROR,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
|
export function getStrapiLatestReleaseSucceeded(latestStrapiReleaseTag) {
|
||||||
|
return {
|
||||||
|
type: GET_STRAPI_LATEST_RELEASE_SUCCEEDED,
|
||||||
|
latestStrapiReleaseTag,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function getUserPermissions() {
|
export function getUserPermissions() {
|
||||||
return {
|
return {
|
||||||
type: GET_USER_PERMISSIONS,
|
type: GET_USER_PERMISSIONS,
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const SET_APP_ERROR = 'StrapiAdmin/Admin/SET_APP_ERROR';
|
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 = 'StrapiAdmin/Admin/GET_USER_PERMISSIONS';
|
||||||
export const GET_USER_PERMISSIONS_ERROR = 'StrapiAdmin/Admin/GET_USER_PERMISSIONS_ERROR';
|
export const GET_USER_PERMISSIONS_ERROR = 'StrapiAdmin/Admin/GET_USER_PERMISSIONS_ERROR';
|
||||||
export const GET_USER_PERMISSIONS_SUCCEEDED = 'StrapiAdmin/Admin/GET_USER_PERMISSIONS_SUCCEEDED';
|
export const GET_USER_PERMISSIONS_SUCCEEDED = 'StrapiAdmin/Admin/GET_USER_PERMISSIONS_SUCCEEDED';
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
CheckPagePermissions,
|
CheckPagePermissions,
|
||||||
request,
|
request,
|
||||||
} from 'strapi-helper-plugin';
|
} 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 adminPermissions from '../../permissions';
|
||||||
import Header from '../../components/Header/index';
|
import Header from '../../components/Header/index';
|
||||||
@ -42,10 +42,12 @@ import Logout from './Logout';
|
|||||||
import {
|
import {
|
||||||
disableGlobalOverlayBlocker,
|
disableGlobalOverlayBlocker,
|
||||||
enableGlobalOverlayBlocker,
|
enableGlobalOverlayBlocker,
|
||||||
|
getInfosDataSucceeded,
|
||||||
updatePlugin,
|
updatePlugin,
|
||||||
} from '../App/actions';
|
} from '../App/actions';
|
||||||
import makeSelecApp from '../App/selectors';
|
import makeSelecApp from '../App/selectors';
|
||||||
import {
|
import {
|
||||||
|
getStrapiLatestReleaseSucceeded,
|
||||||
getUserPermissions,
|
getUserPermissions,
|
||||||
getUserPermissionsError,
|
getUserPermissionsError,
|
||||||
getUserPermissionsSucceeded,
|
getUserPermissionsSucceeded,
|
||||||
@ -67,7 +69,7 @@ export class Admin extends React.Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.emitEvent('didAccessAuthenticatedAdministration');
|
this.emitEvent('didAccessAuthenticatedAdministration');
|
||||||
this.fetchUserPermissions(true);
|
this.initApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(prevProps) {
|
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) => {
|
fetchUserPermissions = async (resetState = false) => {
|
||||||
const { getUserPermissions, getUserPermissionsError, getUserPermissionsSucceeded } = this.props;
|
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);
|
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
|
* Display the app loader until the app is ready
|
||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
@ -170,7 +231,7 @@ export class Admin extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
admin: { isLoading, userPermissions },
|
admin: { isLoading, latestStrapiReleaseTag, userPermissions },
|
||||||
global: {
|
global: {
|
||||||
autoReload,
|
autoReload,
|
||||||
blockApp,
|
blockApp,
|
||||||
@ -211,14 +272,21 @@ export class Admin extends React.Component {
|
|||||||
enableGlobalOverlayBlocker={enableGlobalOverlayBlocker}
|
enableGlobalOverlayBlocker={enableGlobalOverlayBlocker}
|
||||||
fetchUserPermissions={this.fetchUserPermissions}
|
fetchUserPermissions={this.fetchUserPermissions}
|
||||||
formatMessage={formatMessage}
|
formatMessage={formatMessage}
|
||||||
|
latestStrapiReleaseTag={latestStrapiReleaseTag}
|
||||||
menu={this.menuRef.current}
|
menu={this.menuRef.current}
|
||||||
plugins={plugins}
|
plugins={plugins}
|
||||||
settingsBaseURL={SETTINGS_BASE_URL || '/settings'}
|
settingsBaseURL={SETTINGS_BASE_URL || '/settings'}
|
||||||
|
strapiVersion={strapiVersion}
|
||||||
updatePlugin={updatePlugin}
|
updatePlugin={updatePlugin}
|
||||||
>
|
>
|
||||||
<UserProvider value={userPermissions}>
|
<UserProvider value={userPermissions}>
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<LeftMenu version={strapiVersion} plugins={plugins} ref={this.menuRef} />
|
<LeftMenu
|
||||||
|
latestStrapiReleaseTag={latestStrapiReleaseTag}
|
||||||
|
version={strapiVersion}
|
||||||
|
plugins={plugins}
|
||||||
|
ref={this.menuRef}
|
||||||
|
/>
|
||||||
<NavTopRightWrapper>
|
<NavTopRightWrapper>
|
||||||
{/* Injection zone not ready yet */}
|
{/* Injection zone not ready yet */}
|
||||||
<Logout />
|
<Logout />
|
||||||
@ -275,10 +343,13 @@ Admin.propTypes = {
|
|||||||
admin: PropTypes.shape({
|
admin: PropTypes.shape({
|
||||||
appError: PropTypes.bool,
|
appError: PropTypes.bool,
|
||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
|
latestStrapiReleaseTag: PropTypes.string.isRequired,
|
||||||
userPermissions: PropTypes.array,
|
userPermissions: PropTypes.array,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
disableGlobalOverlayBlocker: PropTypes.func.isRequired,
|
disableGlobalOverlayBlocker: PropTypes.func.isRequired,
|
||||||
enableGlobalOverlayBlocker: PropTypes.func.isRequired,
|
enableGlobalOverlayBlocker: PropTypes.func.isRequired,
|
||||||
|
getInfosDataSucceeded: PropTypes.func.isRequired,
|
||||||
|
getStrapiLatestReleaseSucceeded: PropTypes.func.isRequired,
|
||||||
getUserPermissions: PropTypes.func.isRequired,
|
getUserPermissions: PropTypes.func.isRequired,
|
||||||
getUserPermissionsError: PropTypes.func.isRequired,
|
getUserPermissionsError: PropTypes.func.isRequired,
|
||||||
getUserPermissionsSucceeded: PropTypes.func.isRequired,
|
getUserPermissionsSucceeded: PropTypes.func.isRequired,
|
||||||
@ -311,6 +382,8 @@ export function mapDispatchToProps(dispatch) {
|
|||||||
{
|
{
|
||||||
disableGlobalOverlayBlocker,
|
disableGlobalOverlayBlocker,
|
||||||
enableGlobalOverlayBlocker,
|
enableGlobalOverlayBlocker,
|
||||||
|
getInfosDataSucceeded,
|
||||||
|
getStrapiLatestReleaseSucceeded,
|
||||||
getUserPermissions,
|
getUserPermissions,
|
||||||
getUserPermissionsError,
|
getUserPermissionsError,
|
||||||
getUserPermissionsSucceeded,
|
getUserPermissionsSucceeded,
|
||||||
|
@ -5,17 +5,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
|
import packageJSON from '../../../../package.json';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
GET_STRAPI_LATEST_RELEASE_SUCCEEDED,
|
||||||
GET_USER_PERMISSIONS,
|
GET_USER_PERMISSIONS,
|
||||||
GET_USER_PERMISSIONS_ERROR,
|
GET_USER_PERMISSIONS_ERROR,
|
||||||
GET_USER_PERMISSIONS_SUCCEEDED,
|
GET_USER_PERMISSIONS_SUCCEEDED,
|
||||||
SET_APP_ERROR,
|
SET_APP_ERROR,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
|
const packageVersion = packageJSON.version;
|
||||||
const initialState = {
|
const initialState = {
|
||||||
appError: false,
|
appError: false,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
latestStrapiReleaseTag: `v${packageVersion}`,
|
||||||
userPermissions: [],
|
userPermissions: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -23,6 +27,10 @@ const reducer = (state = initialState, action) =>
|
|||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
produce(state, draftState => {
|
produce(state, draftState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case GET_STRAPI_LATEST_RELEASE_SUCCEEDED: {
|
||||||
|
draftState.latestStrapiReleaseTag = action.latestStrapiReleaseTag;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case GET_USER_PERMISSIONS: {
|
case GET_USER_PERMISSIONS: {
|
||||||
draftState.isLoading = true;
|
draftState.isLoading = true;
|
||||||
break;
|
break;
|
||||||
|
@ -19,10 +19,13 @@ describe('<Admin />', () => {
|
|||||||
props = {
|
props = {
|
||||||
admin: {
|
admin: {
|
||||||
appError: false,
|
appError: false,
|
||||||
|
latestStrapiReleaseTag: '3',
|
||||||
},
|
},
|
||||||
disableGlobalOverlayBlocker: jest.fn(),
|
disableGlobalOverlayBlocker: jest.fn(),
|
||||||
emitEvent: jest.fn(),
|
emitEvent: jest.fn(),
|
||||||
enableGlobalOverlayBlocker: jest.fn(),
|
enableGlobalOverlayBlocker: jest.fn(),
|
||||||
|
getInfosDataSucceeded: jest.fn(),
|
||||||
|
getStrapiLatestReleaseSucceeded: jest.fn(),
|
||||||
getUserPermissions: jest.fn(),
|
getUserPermissions: jest.fn(),
|
||||||
getUserPermissionsError: jest.fn(),
|
getUserPermissionsError: jest.fn(),
|
||||||
getUserPermissionsSucceeded: jest.fn(),
|
getUserPermissionsSucceeded: jest.fn(),
|
||||||
@ -42,6 +45,7 @@ describe('<Admin />', () => {
|
|||||||
intl: {
|
intl: {
|
||||||
formatMessage: jest.fn(),
|
formatMessage: jest.fn(),
|
||||||
},
|
},
|
||||||
|
|
||||||
location: {},
|
location: {},
|
||||||
setAppError: jest.fn(),
|
setAppError: jest.fn(),
|
||||||
showGlobalAppBlocker: jest.fn(),
|
showGlobalAppBlocker: jest.fn(),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
|
import packageJSON from '../../../../../package.json';
|
||||||
import {
|
import {
|
||||||
setAppError,
|
setAppError,
|
||||||
getUserPermissions,
|
getUserPermissions,
|
||||||
@ -14,6 +15,7 @@ describe('adminReducer', () => {
|
|||||||
state = {
|
state = {
|
||||||
appError: false,
|
appError: false,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
latestStrapiReleaseTag: `v${packageJSON.version}`,
|
||||||
userPermissions: [],
|
userPermissions: [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -8,12 +8,12 @@ import {
|
|||||||
DISABLE_GLOBAL_OVERLAY_BLOCKER,
|
DISABLE_GLOBAL_OVERLAY_BLOCKER,
|
||||||
ENABLE_GLOBAL_OVERLAY_BLOCKER,
|
ENABLE_GLOBAL_OVERLAY_BLOCKER,
|
||||||
FREEZE_APP,
|
FREEZE_APP,
|
||||||
|
GET_INFOS_DATA_SUCCEEDED,
|
||||||
GET_DATA_SUCCEEDED,
|
GET_DATA_SUCCEEDED,
|
||||||
LOAD_PLUGIN,
|
LOAD_PLUGIN,
|
||||||
PLUGIN_DELETED,
|
PLUGIN_DELETED,
|
||||||
PLUGIN_LOADED,
|
PLUGIN_LOADED,
|
||||||
UNFREEZE_APP,
|
UNFREEZE_APP,
|
||||||
UNSET_HAS_USERS_PLUGIN,
|
|
||||||
UPDATE_PLUGIN,
|
UPDATE_PLUGIN,
|
||||||
} from './constants';
|
} 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) {
|
export function getDataSucceeded(data) {
|
||||||
return {
|
return {
|
||||||
type: GET_DATA_SUCCEEDED,
|
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) {
|
export function updatePlugin(pluginId, updatedKey, updatedValue) {
|
||||||
return {
|
return {
|
||||||
type: UPDATE_PLUGIN,
|
type: UPDATE_PLUGIN,
|
||||||
|
@ -9,9 +9,10 @@ export const LOAD_PLUGIN = 'app/App/LOAD_PLUGIN';
|
|||||||
export const PLUGIN_LOADED = 'app/App/PLUGIN_LOADED';
|
export const PLUGIN_LOADED = 'app/App/PLUGIN_LOADED';
|
||||||
export const PLUGIN_DELETED = 'app/App/PLUGIN_DELETED';
|
export const PLUGIN_DELETED = 'app/App/PLUGIN_DELETED';
|
||||||
export const UNFREEZE_APP = 'app/App/UNFREEZE_APP';
|
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 UPDATE_PLUGIN = 'app/App/UPDATE_PLUGIN';
|
||||||
export const DISABLE_GLOBAL_OVERLAY_BLOCKER =
|
export const DISABLE_GLOBAL_OVERLAY_BLOCKER =
|
||||||
'app/App/OverlayBlocker/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 ENABLE_GLOBAL_OVERLAY_BLOCKER = 'app/App/OverlayBlocker/ENABLE_GLOBAL_OVERLAY_BLOCKER';
|
||||||
export const GET_DATA_SUCCEEDED = 'app/App/GET_DATA_SUCCEEDED';
|
export const GET_DATA_SUCCEEDED = 'app/App/GET_DATA_SUCCEEDED';
|
||||||
|
export const GET_INFOS_DATA_SUCCEEDED = 'admin/App/GET_INFOS_DATA_SUCCEEDED';
|
||||||
|
@ -35,25 +35,31 @@ function App(props) {
|
|||||||
getDataRef.current = props.getDataSucceeded;
|
getDataRef.current = props.getDataSucceeded;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getData = async () => {
|
const currentToken = auth.getToken();
|
||||||
const currentToken = auth.getToken();
|
|
||||||
|
|
||||||
if (currentToken) {
|
const renewToken = async () => {
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data: { token },
|
data: { token },
|
||||||
} = await request('/admin/renew-token', {
|
} = await request('/admin/renew-token', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { token: currentToken },
|
body: { token: currentToken },
|
||||||
});
|
});
|
||||||
auth.updateToken(token);
|
auth.updateToken(token);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Refresh app
|
// Refresh app
|
||||||
auth.clearAppStorage();
|
auth.clearAppStorage();
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentToken) {
|
||||||
|
renewToken();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getData = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await request('/admin/init', { method: 'GET' });
|
const { data } = await request('/admin/init', { method: 'GET' });
|
||||||
|
|
||||||
@ -87,7 +93,7 @@ function App(props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
getData();
|
getData();
|
||||||
}, [getDataRef]);
|
}, []);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <LoadingIndicatorPage />;
|
return <LoadingIndicatorPage />;
|
||||||
|
@ -6,16 +6,17 @@ import {
|
|||||||
DISABLE_GLOBAL_OVERLAY_BLOCKER,
|
DISABLE_GLOBAL_OVERLAY_BLOCKER,
|
||||||
ENABLE_GLOBAL_OVERLAY_BLOCKER,
|
ENABLE_GLOBAL_OVERLAY_BLOCKER,
|
||||||
FREEZE_APP,
|
FREEZE_APP,
|
||||||
|
GET_INFOS_DATA_SUCCEEDED,
|
||||||
GET_DATA_SUCCEEDED,
|
GET_DATA_SUCCEEDED,
|
||||||
PLUGIN_DELETED,
|
PLUGIN_DELETED,
|
||||||
PLUGIN_LOADED,
|
PLUGIN_LOADED,
|
||||||
UNFREEZE_APP,
|
UNFREEZE_APP,
|
||||||
UNSET_HAS_USERS_PLUGIN,
|
|
||||||
UPDATE_PLUGIN,
|
UPDATE_PLUGIN,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
const packageVersion = packageJSON.version;
|
const packageVersion = packageJSON.version;
|
||||||
const initialState = fromJS({
|
const initialState = fromJS({
|
||||||
|
appInfos: {},
|
||||||
autoReload: false,
|
autoReload: false,
|
||||||
blockApp: false,
|
blockApp: false,
|
||||||
currentEnvironment: 'development',
|
currentEnvironment: 'development',
|
||||||
@ -43,24 +44,27 @@ function appReducer(state = initialState, action) {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
case GET_DATA_SUCCEEDED: {
|
case GET_INFOS_DATA_SUCCEEDED: {
|
||||||
const {
|
if (action.data.strapiVersion !== state.get('strapiVersion')) {
|
||||||
data: { hasAdmin, uuid, currentEnvironment, autoReload, strapiVersion },
|
|
||||||
} = action;
|
|
||||||
|
|
||||||
if (strapiVersion !== state.get('strapiVersion')) {
|
|
||||||
console.error(
|
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');
|
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
|
return state
|
||||||
.update('isLoading', () => false)
|
.update('isLoading', () => false)
|
||||||
.update('hasAdminUser', () => hasAdmin)
|
.update('hasAdminUser', () => action.data.hasAdmin)
|
||||||
.update('uuid', () => uuid)
|
.update('uuid', () => action.data.uuid);
|
||||||
.update('autoReload', () => autoReload)
|
|
||||||
.update('currentEnvironment', () => currentEnvironment);
|
|
||||||
}
|
}
|
||||||
case PLUGIN_LOADED:
|
case PLUGIN_LOADED:
|
||||||
return state.setIn(['plugins', action.plugin.id], fromJS(action.plugin));
|
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]);
|
return state.deleteIn(['plugins', action.plugin]);
|
||||||
case UNFREEZE_APP:
|
case UNFREEZE_APP:
|
||||||
return state.set('blockApp', false).set('overlayBlockerData', null);
|
return state.set('blockApp', false).set('overlayBlockerData', null);
|
||||||
case UNSET_HAS_USERS_PLUGIN:
|
|
||||||
return state.set('hasUserPlugin', false);
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -9,47 +9,22 @@ const selectApp = () => state => state.get('app');
|
|||||||
* Select the language locale
|
* Select the language locale
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const selectPlugins = () =>
|
const selectPlugins = () => createSelector(selectApp(), appState => appState.get('plugins'));
|
||||||
createSelector(
|
|
||||||
selectApp(),
|
|
||||||
appState => appState.get('plugins')
|
|
||||||
);
|
|
||||||
|
|
||||||
const makeSelectApp = () =>
|
const makeSelectApp = () => createSelector(selectApp(), appState => appState.toJS());
|
||||||
createSelector(
|
|
||||||
selectApp(),
|
|
||||||
appState => appState.toJS()
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectHasUserPlugin = () =>
|
const selectHasUserPlugin = () =>
|
||||||
createSelector(
|
createSelector(selectApp(), appState => appState.get('hasUserPlugin'));
|
||||||
selectApp(),
|
|
||||||
appState => appState.get('hasUserPlugin')
|
|
||||||
);
|
|
||||||
|
|
||||||
const makeSelectShowGlobalAppBlocker = () =>
|
const makeSelectShowGlobalAppBlocker = () =>
|
||||||
createSelector(
|
createSelector(selectApp(), appState => appState.get('showGlobalAppBlocker'));
|
||||||
selectApp(),
|
|
||||||
appState => appState.get('showGlobalAppBlocker')
|
|
||||||
);
|
|
||||||
|
|
||||||
const makeSelectBlockApp = () =>
|
const makeSelectBlockApp = () => createSelector(selectApp(), appState => appState.get('blockApp'));
|
||||||
createSelector(
|
|
||||||
selectApp(),
|
|
||||||
appState => appState.get('blockApp')
|
|
||||||
);
|
|
||||||
|
|
||||||
const makeSelectOverlayBlockerProps = () =>
|
const makeSelectOverlayBlockerProps = () =>
|
||||||
createSelector(
|
createSelector(selectApp(), appState => appState.get('overlayBlockerData'));
|
||||||
selectApp(),
|
|
||||||
appState => appState.get('overlayBlockerData')
|
|
||||||
);
|
|
||||||
|
|
||||||
const makeSelectUuid = () =>
|
const makeSelectUuid = () => createSelector(selectApp(), appState => appState.get('uuid'));
|
||||||
createSelector(
|
|
||||||
selectApp(),
|
|
||||||
appState => appState.get('uuid')
|
|
||||||
);
|
|
||||||
|
|
||||||
export default makeSelectApp;
|
export default makeSelectApp;
|
||||||
export {
|
export {
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
import {
|
import {
|
||||||
FREEZE_APP,
|
FREEZE_APP,
|
||||||
|
GET_DATA_SUCCEEDED,
|
||||||
|
GET_INFOS_DATA_SUCCEEDED,
|
||||||
LOAD_PLUGIN,
|
LOAD_PLUGIN,
|
||||||
PLUGIN_DELETED,
|
PLUGIN_DELETED,
|
||||||
PLUGIN_LOADED,
|
PLUGIN_LOADED,
|
||||||
UNFREEZE_APP,
|
UNFREEZE_APP,
|
||||||
UNSET_HAS_USERS_PLUGIN,
|
|
||||||
UPDATE_PLUGIN,
|
UPDATE_PLUGIN,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
import {
|
import {
|
||||||
freezeApp,
|
freezeApp,
|
||||||
loadPlugin,
|
loadPlugin,
|
||||||
|
getInfosDataSucceeded,
|
||||||
|
getDataSucceeded,
|
||||||
pluginDeleted,
|
pluginDeleted,
|
||||||
pluginLoaded,
|
pluginLoaded,
|
||||||
unfreezeApp,
|
unfreezeApp,
|
||||||
unsetHasUserPlugin,
|
|
||||||
updatePlugin,
|
updatePlugin,
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
|
|
||||||
@ -40,6 +42,30 @@ describe('<App /> 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', () => {
|
describe('loadPlugin', () => {
|
||||||
it('should return the correct type and the passed data', () => {
|
it('should return the correct type and the passed data', () => {
|
||||||
const plugin = {
|
const plugin = {
|
||||||
@ -82,16 +108,6 @@ describe('<App /> actions', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('unsetHasUserPlugin', () => {
|
|
||||||
it('should return the correct type', () => {
|
|
||||||
const expected = {
|
|
||||||
type: UNSET_HAS_USERS_PLUGIN,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(unsetHasUserPlugin()).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('updatePlugin', () => {
|
describe('updatePlugin', () => {
|
||||||
it('should return the correct type and the passed data', () => {
|
it('should return the correct type and the passed data', () => {
|
||||||
const pluginId = 'content-manager';
|
const pluginId = 'content-manager';
|
||||||
@ -104,9 +120,7 @@ describe('<App /> actions', () => {
|
|||||||
updatedValue,
|
updatedValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(updatePlugin(pluginId, updatedKey, updatedValue)).toEqual(
|
expect(updatePlugin(pluginId, updatedKey, updatedValue)).toEqual(expected);
|
||||||
expected
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -4,10 +4,11 @@ import {
|
|||||||
disableGlobalOverlayBlocker,
|
disableGlobalOverlayBlocker,
|
||||||
enableGlobalOverlayBlocker,
|
enableGlobalOverlayBlocker,
|
||||||
freezeApp,
|
freezeApp,
|
||||||
|
getDataSucceeded,
|
||||||
|
getInfosDataSucceeded,
|
||||||
pluginDeleted,
|
pluginDeleted,
|
||||||
pluginLoaded,
|
pluginLoaded,
|
||||||
unfreezeApp,
|
unfreezeApp,
|
||||||
unsetHasUserPlugin,
|
|
||||||
updatePlugin,
|
updatePlugin,
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
import appReducer from '../reducer';
|
import appReducer from '../reducer';
|
||||||
@ -17,6 +18,7 @@ describe('<App /> reducer', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
state = fromJS({
|
state = fromJS({
|
||||||
|
appInfos: {},
|
||||||
autoReload: false,
|
autoReload: false,
|
||||||
blockApp: false,
|
blockApp: false,
|
||||||
currentEnvironment: 'development',
|
currentEnvironment: 'development',
|
||||||
@ -96,9 +98,34 @@ describe('<App /> reducer', () => {
|
|||||||
expect(appReducer(state, unfreezeApp())).toEqual(expectedResult);
|
expect(appReducer(state, unfreezeApp())).toEqual(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle the unsetHasUserPlugin action correclty', () => {
|
describe('GET_INFOS_DATA_SUCCEEDED', () => {
|
||||||
const expectedResult = state.set('hasUserPlugin', false);
|
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
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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 (
|
||||||
|
<Wrapper>
|
||||||
|
<Text fontSize="xs" color="grey" fontWeight="bold">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
<InfoText content={content} />
|
||||||
|
{link && <Link {...link} />}
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Detail.defaultProps = {
|
||||||
|
link: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
Detail.propTypes = {
|
||||||
|
content: PropTypes.string.isRequired,
|
||||||
|
link: PropTypes.object,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Detail;
|
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Padded, Text } from '@buffetjs/core';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const InfoText = ({ content }) => {
|
||||||
|
return (
|
||||||
|
<Padded top size="xs">
|
||||||
|
<Text fontWeight="semiBold" lineHeight="13px">
|
||||||
|
{content}
|
||||||
|
</Text>
|
||||||
|
</Padded>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
InfoText.propTypes = {
|
||||||
|
content: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InfoText;
|
@ -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;
|
@ -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 (
|
||||||
|
<Padded top size="smd">
|
||||||
|
<BaselineAlignement top size="1px" />
|
||||||
|
<LinkText fontWeight="semiBold">
|
||||||
|
<a href={href} target="_blank" rel="noopener noreferrer">
|
||||||
|
{label}
|
||||||
|
<LinkArrow />
|
||||||
|
</a>
|
||||||
|
</LinkText>
|
||||||
|
</Padded>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Link.propTypes = {
|
||||||
|
href: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Link;
|
@ -0,0 +1,7 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
width: 50%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -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';
|
@ -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 (
|
||||||
|
<div>
|
||||||
|
<PageTitle name="Infos" />
|
||||||
|
<Header {...headerProps} />
|
||||||
|
<BaselineAlignement top size="3px" />
|
||||||
|
<Bloc>
|
||||||
|
<Padded left right top size="smd">
|
||||||
|
<Padded left right top size="xs">
|
||||||
|
<Flex justifyContent="space-between">
|
||||||
|
<Detail
|
||||||
|
link={upgradeLink}
|
||||||
|
title={strapiVersion}
|
||||||
|
content={`v${appInfos.strapiVersion}`}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
link={{ label: pricingLabel, href: 'https://strapi.io/pricing' }}
|
||||||
|
title={editionTitle}
|
||||||
|
content={formatMessage({ id: currentPlan })}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Padded top size="lg">
|
||||||
|
<Text fontSize="xs" color="grey" fontWeight="bold">
|
||||||
|
{nodeVersion}
|
||||||
|
</Text>
|
||||||
|
<InfoText content={appInfos.nodeVersion} />
|
||||||
|
</Padded>
|
||||||
|
</Padded>
|
||||||
|
</Padded>
|
||||||
|
<BaselineAlignement top size="60px" />
|
||||||
|
</Bloc>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ApplicationInfosPage);
|
@ -4,16 +4,12 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import React, { memo, useMemo, useEffect } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { get, isNil, upperFirst } from 'lodash';
|
import { get, upperFirst } from 'lodash';
|
||||||
import { auth, LoadingIndicatorPage } from 'strapi-helper-plugin';
|
import { auth, LoadingIndicatorPage } from 'strapi-helper-plugin';
|
||||||
import axios from 'axios';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
import PageTitle from '../../components/PageTitle';
|
import PageTitle from '../../components/PageTitle';
|
||||||
import { useModels } from '../../hooks';
|
import { useModels } from '../../hooks';
|
||||||
import { STRAPI_UPDATE_NOTIF } from '../../config';
|
|
||||||
|
|
||||||
import useFetch from './hooks';
|
import useFetch from './hooks';
|
||||||
import { ALink, Block, Container, LinkWrapper, P, Wave, Separator } from './components';
|
import { ALink, Block, Container, LinkWrapper, P, Wave, Separator } from './components';
|
||||||
@ -65,54 +61,7 @@ const SOCIAL_LINKS = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const HomePage = ({ global: { strapiVersion }, history: { push } }) => {
|
const HomePage = ({ 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 { error, isLoading, posts } = useFetch();
|
const { error, isLoading, posts } = useFetch();
|
||||||
// Temporary until we develop the menu API
|
// Temporary until we develop the menu API
|
||||||
const { collectionTypes, singleTypes, isLoading: isLoadingForModels } = useModels();
|
const { collectionTypes, singleTypes, isLoading: isLoadingForModels } = useModels();
|
||||||
|
@ -30,10 +30,19 @@ import reducer, { initialState } from './reducer';
|
|||||||
import Loader from './Loader';
|
import Loader from './Loader';
|
||||||
import Wrapper from './Wrapper';
|
import Wrapper from './Wrapper';
|
||||||
|
|
||||||
const LeftMenu = forwardRef(({ version, plugins }, ref) => {
|
const LeftMenu = forwardRef(({ latestStrapiReleaseTag, version, plugins }, ref) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const permissions = useContext(UserContext);
|
const permissions = useContext(UserContext);
|
||||||
const { menu: settingsMenu } = useSettingsMenu(true);
|
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 [
|
const [
|
||||||
{
|
{
|
||||||
collectionTypesSectionLinks,
|
collectionTypesSectionLinks,
|
||||||
@ -43,7 +52,9 @@ const LeftMenu = forwardRef(({ version, plugins }, ref) => {
|
|||||||
singleTypesSectionLinks,
|
singleTypesSectionLinks,
|
||||||
},
|
},
|
||||||
dispatch,
|
dispatch,
|
||||||
] = useReducer(reducer, initialState, () => init(initialState, plugins, settingsMenu));
|
] = useReducer(reducer, initialState, () =>
|
||||||
|
init(initialState, plugins, settingsMenu, settingsLinkNotificationCount)
|
||||||
|
);
|
||||||
const generalSectionLinksFiltered = useMemo(() => filterLinks(generalSectionLinks), [
|
const generalSectionLinksFiltered = useMemo(() => filterLinks(generalSectionLinks), [
|
||||||
generalSectionLinks,
|
generalSectionLinks,
|
||||||
]);
|
]);
|
||||||
@ -197,6 +208,7 @@ const LeftMenu = forwardRef(({ version, plugins }, ref) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
LeftMenu.propTypes = {
|
LeftMenu.propTypes = {
|
||||||
|
latestStrapiReleaseTag: PropTypes.string.isRequired,
|
||||||
version: PropTypes.string.isRequired,
|
version: PropTypes.string.isRequired,
|
||||||
plugins: PropTypes.object.isRequired,
|
plugins: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,7 @@ import { SETTINGS_BASE_URL } from '../../config';
|
|||||||
import { sortLinks } from '../../utils';
|
import { sortLinks } from '../../utils';
|
||||||
import { getSettingsMenuLinksPermissions } from './utils';
|
import { getSettingsMenuLinksPermissions } from './utils';
|
||||||
|
|
||||||
const init = (initialState, plugins = {}, settingsMenu = []) => {
|
const init = (initialState, plugins = {}, settingsMenu = [], settingsLinkNotificationCount = 0) => {
|
||||||
const settingsLinkPermissions = getSettingsMenuLinksPermissions(settingsMenu);
|
const settingsLinkPermissions = getSettingsMenuLinksPermissions(settingsMenu);
|
||||||
|
|
||||||
const pluginsLinks = Object.values(plugins).reduce((acc, current) => {
|
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) {
|
if (!settingsLinkPermissions.filter(perm => perm === null).length && settingsLinkIndex !== -1) {
|
||||||
const permissionsPath = ['generalSectionLinks', settingsLinkIndex, 'permissions'];
|
const permissionsPath = ['generalSectionLinks', settingsLinkIndex, 'permissions'];
|
||||||
|
const notificationPath = ['generalSectionLinks', settingsLinkIndex, 'notificationsCount'];
|
||||||
|
|
||||||
set(initialState, permissionsPath, settingsLinkPermissions);
|
set(initialState, permissionsPath, settingsLinkPermissions);
|
||||||
|
set(initialState, notificationPath, settingsLinkNotificationCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortedLinks.length) {
|
if (sortedLinks.length) {
|
||||||
|
@ -13,6 +13,7 @@ const initialState = {
|
|||||||
destination: '/list-plugins',
|
destination: '/list-plugins',
|
||||||
isDisplayed: false,
|
isDisplayed: false,
|
||||||
permissions: adminPermissions.marketplace.main,
|
permissions: adminPermissions.marketplace.main,
|
||||||
|
notificationsCount: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'shopping-basket',
|
icon: 'shopping-basket',
|
||||||
@ -20,6 +21,7 @@ const initialState = {
|
|||||||
destination: '/marketplace',
|
destination: '/marketplace',
|
||||||
isDisplayed: false,
|
isDisplayed: false,
|
||||||
permissions: adminPermissions.marketplace.main,
|
permissions: adminPermissions.marketplace.main,
|
||||||
|
notificationsCount: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'cog',
|
icon: 'cog',
|
||||||
@ -29,6 +31,7 @@ const initialState = {
|
|||||||
// Permissions of this link are retrieved in the init phase
|
// Permissions of this link are retrieved in the init phase
|
||||||
// using the settings menu
|
// using the settings menu
|
||||||
permissions: [],
|
permissions: [],
|
||||||
|
notificationsCount: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
singleTypesSectionLinks: [],
|
singleTypesSectionLinks: [],
|
||||||
|
@ -112,6 +112,7 @@ describe('ADMIN | LeftMenu | init', () => {
|
|||||||
title: 'Settings.webhooks.title',
|
title: 'Settings.webhooks.title',
|
||||||
to: '/settings/webhooks',
|
to: '/settings/webhooks',
|
||||||
name: 'webhooks',
|
name: 'webhooks',
|
||||||
|
|
||||||
permissions: [
|
permissions: [
|
||||||
{ action: 'admin::webhook.create', subject: null },
|
{ action: 'admin::webhook.create', subject: null },
|
||||||
{ action: 'admin::webhook.read', subject: null },
|
{ action: 'admin::webhook.read', subject: null },
|
||||||
@ -223,6 +224,7 @@ describe('ADMIN | LeftMenu | init', () => {
|
|||||||
label: 'app.components.LeftMenuLinkContainer.settings',
|
label: 'app.components.LeftMenuLinkContainer.settings',
|
||||||
isDisplayed: false,
|
isDisplayed: false,
|
||||||
destination: SETTINGS_BASE_URL,
|
destination: SETTINGS_BASE_URL,
|
||||||
|
notificationsCount: 0,
|
||||||
permissions: [
|
permissions: [
|
||||||
// webhooks
|
// webhooks
|
||||||
{ action: 'admin::webhook.create', subject: null },
|
{ action: 'admin::webhook.create', subject: null },
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
|
const LeftMenuIcon = styled(({ ...props }) => <FontAwesomeIcon {...props} icon="circle" />)`
|
||||||
|
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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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 (
|
||||||
|
<Wrapper>
|
||||||
|
<Link to="/settings/application-infos">
|
||||||
|
<Icon />
|
||||||
|
<Text lineHeight="34px">
|
||||||
|
<FormattedMessage id="Settings.application.title" />
|
||||||
|
</Text>
|
||||||
|
{showNotif && <Notif />}
|
||||||
|
</Link>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ApplicationDetailLink);
|
@ -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;
|
@ -2,7 +2,7 @@ import React, { memo } from 'react';
|
|||||||
import { useGlobalContext } from 'strapi-helper-plugin';
|
import { useGlobalContext } from 'strapi-helper-plugin';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import PageTitle from '../../components/SettingsPageTitle';
|
import PageTitle from '../../../../components/SettingsPageTitle';
|
||||||
|
|
||||||
const SettingDispatcher = () => {
|
const SettingDispatcher = () => {
|
||||||
const { plugins } = useGlobalContext();
|
const { plugins } = useGlobalContext();
|
@ -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';
|
@ -24,23 +24,28 @@ import HeaderSearch from '../../components/HeaderSearch';
|
|||||||
import PageTitle from '../../components/PageTitle';
|
import PageTitle from '../../components/PageTitle';
|
||||||
import { useSettingsMenu } from '../../hooks';
|
import { useSettingsMenu } from '../../hooks';
|
||||||
import { retrieveGlobalLinks } from '../../utils';
|
import { retrieveGlobalLinks } from '../../utils';
|
||||||
|
import ApplicationInfosPage from '../ApplicationInfosPage';
|
||||||
import SettingsSearchHeaderProvider from '../SettingsHeaderSearchContextProvider';
|
import SettingsSearchHeaderProvider from '../SettingsHeaderSearchContextProvider';
|
||||||
import UsersEditPage from '../Users/ProtectedEditPage';
|
import UsersEditPage from '../Users/ProtectedEditPage';
|
||||||
import UsersListPage from '../Users/ProtectedListPage';
|
import UsersListPage from '../Users/ProtectedListPage';
|
||||||
import RolesEditPage from '../Roles/ProtectedEditPage';
|
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 {
|
import {
|
||||||
createRoute,
|
createRoute,
|
||||||
findFirstAllowedEndpoint,
|
|
||||||
createPluginsLinksRoutes,
|
createPluginsLinksRoutes,
|
||||||
makeUniqueRoutes,
|
makeUniqueRoutes,
|
||||||
getSectionsToDisplay,
|
getSectionsToDisplay,
|
||||||
} from './utils';
|
} 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() {
|
function SettingsPage() {
|
||||||
const { settingId } = useParams();
|
const { settingId } = useParams();
|
||||||
@ -50,14 +55,6 @@ function SettingsPage() {
|
|||||||
const { isLoading, menu } = useSettingsMenu();
|
const { isLoading, menu } = useSettingsMenu();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const pluginsGlobalLinks = useMemo(() => retrieveGlobalLinks(plugins), [plugins]);
|
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 <Route /> that needs to be created by the plugins
|
// Create all the <Route /> that needs to be created by the plugins
|
||||||
// For instance the upload plugin needs to create a <Route />
|
// For instance the upload plugin needs to create a <Route />
|
||||||
@ -95,8 +92,8 @@ function SettingsPage() {
|
|||||||
return <LoadingIndicatorPage />;
|
return <LoadingIndicatorPage />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!settingId && firstAvailableEndpoint) {
|
if (!settingId) {
|
||||||
return <Redirect to={firstAvailableEndpoint} />;
|
return <Redirect to={`${settingsBaseURL}/application-infos`} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingTitle = formatMessage({ id: 'app.components.LeftMenuLinkContainer.settings' });
|
const settingTitle = formatMessage({ id: 'app.components.LeftMenuLinkContainer.settings' });
|
||||||
@ -109,14 +106,23 @@ function SettingsPage() {
|
|||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-3">
|
<div className="col-md-3">
|
||||||
<LeftMenu>
|
<MenuWrapper>
|
||||||
{filteredMenu.map(item => {
|
<ApplicationDetailLink />
|
||||||
return <LeftMenuList {...item} key={item.id} />;
|
<StyledLeftMenu>
|
||||||
})}
|
{filteredMenu.map(item => {
|
||||||
</LeftMenu>
|
return <LeftMenuList {...item} key={item.id} />;
|
||||||
|
})}
|
||||||
|
</StyledLeftMenu>
|
||||||
|
</MenuWrapper>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-md-9">
|
<div className="col-md-9">
|
||||||
<Switch>
|
<Switch>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${settingsBaseURL}/application-infos`}
|
||||||
|
component={ApplicationInfosPage}
|
||||||
|
/>
|
||||||
<Route exact path={`${settingsBaseURL}/roles`} component={ProtectedRolesListPage} />
|
<Route exact path={`${settingsBaseURL}/roles`} component={ProtectedRolesListPage} />
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
|
@ -66,6 +66,13 @@
|
|||||||
"Roles.RoleRow.user-count.plural": "{number} users",
|
"Roles.RoleRow.user-count.plural": "{number} users",
|
||||||
"Roles.RoleRow.user-count.singular": "{number} user",
|
"Roles.RoleRow.user-count.singular": "{number} user",
|
||||||
"Roles.components.List.empty.withSearch": "There is no role corresponding to the search ({search})...",
|
"Roles.components.List.empty.withSearch": "There is no role corresponding to the search ({search})...",
|
||||||
|
"Settings.application.title": "Application",
|
||||||
|
"Settings.application.description": "See your project's details",
|
||||||
|
"Settings.application.link-pricing": "See all pricing",
|
||||||
|
"Settings.application.link-upgrade": "Upgrade your project",
|
||||||
|
"Settings.application.strapi-version": "STRAPI VERSION",
|
||||||
|
"Settings.application.node-version": "NODE VERSION",
|
||||||
|
"Settings.application.edition-title": "CURRENT PLAN",
|
||||||
"Settings.PageTitle": "Settings - {name}",
|
"Settings.PageTitle": "Settings - {name}",
|
||||||
"Settings.error": "Error",
|
"Settings.error": "Error",
|
||||||
"Settings.global": "Global Settings",
|
"Settings.global": "Global Settings",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user