Created useAppInfos hook

Signed-off-by: soupette <cyril.lpz@gmail.com>
This commit is contained in:
soupette 2021-05-21 12:50:05 +02:00
parent c420370c9d
commit 4db2e962d1
22 changed files with 302 additions and 65 deletions

View File

@ -0,0 +1,44 @@
import React from 'react';
import { LoadingIndicatorPage, AppInfosContext } from '@strapi/helper-plugin';
import { useQueries } from 'react-query';
import PluginsInitializer from '../PluginsInitializer';
import RBACProvider from '../RBACProvider';
import { fetchAppInfo, fetchCurrentUserPermissions } from './utils/api';
const AuthenticatedApp = () => {
// TODO: clean components that depends on this
// This part is just to prepare the refactoring of the Admin page
const [
{ data: appInfos, status },
{ data: permissions, status: fetchPermissionsStatus, refetch, isFetched, isFetching },
] = useQueries([
{ queryKey: 'app-infos', queryFn: fetchAppInfo },
{
queryKey: 'admin-users-permission',
queryFn: fetchCurrentUserPermissions,
},
]);
const shouldShowNotDependentQueriesLoader =
(isFetching && isFetched) || status === 'loading' || fetchPermissionsStatus === 'loading';
if (shouldShowNotDependentQueriesLoader) {
return <LoadingIndicatorPage />;
}
// TODO add error state
if (status === 'error') {
return <div>error...</div>;
}
return (
<AppInfosContext.Provider value={appInfos}>
<RBACProvider permissions={permissions} refetchPermissions={refetch}>
<PluginsInitializer />
</RBACProvider>
</AppInfosContext.Provider>
);
};
export default AuthenticatedApp;

View File

@ -0,0 +1,31 @@
import axiosInstance from '../../../utils/axiosInstance';
const fetchAppInfo = async () => {
try {
const { data, headers } = await axiosInstance.get('/admin/information');
if (!headers['content-type'].includes('application/json')) {
throw new Error('Not found');
}
return data.data;
} catch (error) {
throw new Error(error);
}
};
const fetchCurrentUserPermissions = async () => {
try {
const { data, headers } = await axiosInstance.get('/admin/users/me/permissions');
if (!headers['content-type'].includes('application/json')) {
throw new Error('Not found');
}
return data.data;
} catch (err) {
throw new Error(err);
}
};
export { fetchAppInfo, fetchCurrentUserPermissions };

View File

@ -5,11 +5,13 @@
*/
import React from 'react';
import { PropTypes } from 'prop-types';
import { useAppInfos } from '@strapi/helper-plugin';
import Wrapper, { A } from './Wrapper';
function LeftMenuFooter({ version }) {
function LeftMenuFooter() {
const projectType = process.env.STRAPI_ADMIN_PROJECT_TYPE;
const { strapiVersion } = useAppInfos();
return (
<Wrapper>
@ -19,12 +21,12 @@ function LeftMenuFooter({ version }) {
</A>
&nbsp;
<A
href={`https://github.com/strapi/strapi/releases/tag/v${version}`}
href={`https://github.com/strapi/strapi/releases/tag/v${strapiVersion}`}
key="github"
target="_blank"
rel="noopener noreferrer"
>
v{version}
v{strapiVersion}
</A>
&nbsp;
<A href="https://strapi.io" target="_blank" rel="noopener noreferrer">
@ -35,8 +37,4 @@ function LeftMenuFooter({ version }) {
);
}
LeftMenuFooter.propTypes = {
version: PropTypes.string.isRequired,
};
export default LeftMenuFooter;

View File

@ -6,7 +6,7 @@ import { Footer, Header, Loader, LinksContainer, LinksSection } from './compos';
import Wrapper from './Wrapper';
import useMenuSections from './useMenuSections';
const LeftMenu = ({ shouldUpdateStrapi, version, plugins, setUpdateMenu }) => {
const LeftMenu = ({ shouldUpdateStrapi, plugins, setUpdateMenu }) => {
const location = useLocation();
const {
@ -81,14 +81,13 @@ const LeftMenu = ({ shouldUpdateStrapi, version, plugins, setUpdateMenu }) => {
/>
)}
</LinksContainer>
<Footer key="footer" version={version} />
<Footer key="footer" />
</Wrapper>
);
};
LeftMenu.propTypes = {
shouldUpdateStrapi: PropTypes.bool.isRequired,
version: PropTypes.string.isRequired,
plugins: PropTypes.object.isRequired,
setUpdateMenu: PropTypes.func.isRequired,
};

View File

@ -1,12 +1,12 @@
import React, { useReducer, useRef } from 'react';
import { LoadingIndicatorPage, useStrapiApp } from '@strapi/helper-plugin';
import Admin from '../../pages/Admin';
import ReleaseNotification from '../ReleaseNotification';
import init from './init';
import reducer, { initialState } from './reducer';
const PluginsInitializer = () => {
const { plugins: appPlugins } = useStrapiApp();
const [{ plugins }, dispatch] = useReducer(reducer, initialState, () => init(appPlugins));
const setPlugin = useRef(pluginId => {
dispatch({ type: 'SET_PLUGIN_READY', pluginId });
@ -35,7 +35,12 @@ const PluginsInitializer = () => {
);
}
return <Admin plugins={plugins} />;
return (
<>
<ReleaseNotification />
<Admin plugins={plugins} />
</>
);
};
export default PluginsInitializer;

View File

@ -32,7 +32,7 @@ const PrivateRoute = ({ component: Component, path, ...rest }) => (
);
PrivateRoute.propTypes = {
component: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
component: PropTypes.oneOfType([PropTypes.node, PropTypes.func, PropTypes.element]).isRequired,
path: PropTypes.string.isRequired,
};

View File

@ -0,0 +1,10 @@
import { RESET_STORE, SET_PERMISSIONS } from './constants';
const resetStore = () => ({ type: RESET_STORE });
const setPermissions = permissions => ({
type: SET_PERMISSIONS,
permissions,
});
export { resetStore, setPermissions };

View File

@ -0,0 +1,2 @@
export const RESET_STORE = 'StrapiAdmin/RBACProvider/RESET_STORE';
export const SET_PERMISSIONS = 'StrapiAdmin/RBACProvider/SET_PERMISSIONS';

View File

@ -0,0 +1,35 @@
import React, { createContext, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { LoadingIndicatorPage } from '@strapi/helper-plugin';
import PropTypes from 'prop-types';
import { resetStore, setPermissions } from './actions';
export const C = createContext();
const RBACProvider = ({ children, permissions, refetchPermissions }) => {
const { allPermissions } = useSelector(state => state.rbacProvider);
const dispatch = useDispatch();
useEffect(() => {
dispatch(setPermissions(permissions));
return () => {
console.log('up');
dispatch(resetStore());
};
}, [permissions, dispatch]);
if (!allPermissions) {
return <LoadingIndicatorPage />;
}
return <C.Provider value={refetchPermissions}>{children}</C.Provider>;
};
RBACProvider.propTypes = {
children: PropTypes.element.isRequired,
permissions: PropTypes.array.isRequired,
refetchPermissions: PropTypes.func.isRequired,
};
export default RBACProvider;

View File

@ -0,0 +1,53 @@
/*
*
* RBACProvider reducer
* The goal of this reducer is to provide
* the plugins with an access to the user's permissions
* in our middleware system
*
*/
import produce from 'immer';
import { RESET_STORE, SET_PERMISSIONS } from './constants';
const initialState = {
allPermissions: null,
adminPermissions: {},
collectionTypesRelatedPermissions: {},
pluginsPermissions: {},
};
const reducer = (state = initialState, action) =>
// eslint-disable-next-line consistent-return
produce(state, draftState => {
switch (action.type) {
case SET_PERMISSIONS: {
draftState.allPermissions = action.permissions;
draftState.collectionTypesRelatedPermissions = action.permissions
.filter(perm => perm.subject)
.reduce((acc, current) => {
const { subject, action } = current;
if (!acc[subject]) {
acc[subject] = {};
}
acc[subject] = acc[subject][action]
? { ...acc[subject], [action]: [...acc[subject][action], current] }
: { ...acc[subject], [action]: [current] };
return acc;
}, {});
break;
}
case RESET_STORE: {
return initialState;
}
default:
return state;
}
});
export default reducer;
export { initialState };

View File

@ -0,0 +1,44 @@
import { useEffect } from 'react';
import { useQuery } from 'react-query';
import { useAppInfos, useNotification } from '@strapi/helper-plugin';
import checkLatestStrapiVersion from './utils/checkLatestStrapiVersion';
import fetchStrapiLatestRelease from './utils/api';
const { STRAPI_ADMIN_UPDATE_NOTIFICATION } = process.env;
const canFetchRelease = STRAPI_ADMIN_UPDATE_NOTIFICATION === 'false';
const showUpdateNotif = !JSON.parse(localStorage.getItem('STRAPI_UPDATE_NOTIF'));
const ReleaseNotification = () => {
const { strapiVersion } = useAppInfos();
const toggleNotification = useNotification();
const { data: tag_name, status } = useQuery({
queryKey: 'strapi-release',
queryFn: fetchStrapiLatestRelease,
enabled: canFetchRelease,
});
useEffect(() => {
if (status === 'success' && showUpdateNotif) {
const shouldUpdateStrapi = checkLatestStrapiVersion(strapiVersion, tag_name);
if (shouldUpdateStrapi) {
toggleNotification({
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),
});
}
}
}, [status, tag_name, strapiVersion, toggleNotification]);
return null;
};
export default ReleaseNotification;

View File

@ -0,0 +1,19 @@
import axios from 'axios';
import packageJSON from '../../../../../package.json';
const strapiVersion = packageJSON.version;
const fetchStrapiLatestRelease = async () => {
try {
const {
data: { tag_name },
} = await axios.get('https://api.github.com/repos/strapi/strapi/releases/latest');
return tag_name;
} catch (err) {
// Don't throw an error
return strapiVersion;
}
};
export default fetchStrapiLatestRelease;

View File

@ -21,7 +21,6 @@ import {
request,
NotificationsContext,
} from '@strapi/helper-plugin';
import { checkLatestStrapiVersion } from '../../utils';
import adminPermissions from '../../permissions';
import Header from '../../components/Header/index';
@ -110,50 +109,8 @@ export class Admin extends React.Component {
}
};
fetchStrapiLatestRelease = async () => {
const {
global: { strapiVersion },
getStrapiLatestReleaseSucceeded,
} = this.props;
if (process.env.STRAPI_ADMIN_UPDATE_NOTIFICATION === 'true') {
try {
const {
data: { tag_name },
} = await axios.get('https://api.github.com/repos/strapi/strapi/releases/latest');
const shouldUpdateStrapi = checkLatestStrapiVersion(strapiVersion, tag_name);
getStrapiLatestReleaseSucceeded(tag_name, shouldUpdateStrapi);
const showUpdateNotif = !JSON.parse(localStorage.getItem('STRAPI_UPDATE_NOTIF'));
if (!showUpdateNotif) {
return;
}
if (shouldUpdateStrapi) {
this.context.toggleNotification({
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
}
}
};
initApp = async () => {
await this.fetchAppInfo();
await this.fetchStrapiLatestRelease();
};
renderPluginDispatcher = props => {
@ -194,7 +151,6 @@ export class Admin extends React.Component {
<Wrapper>
<LeftMenu
shouldUpdateStrapi={shouldUpdateStrapi}
version={strapiVersion}
plugins={plugins}
setUpdateMenu={this.setUpdateMenu}
/>

View File

@ -10,7 +10,7 @@ import { Switch, Route } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
import { LoadingIndicatorPage, auth, request, useNotification } from '@strapi/helper-plugin';
import PluginsInitializer from '../../components/PluginsInitializer';
import AuthenticatedApp from '../../components/AuthenticatedApp';
import PrivateRoute from '../../components/PrivateRoute';
import AuthPage from '../AuthPage';
import NotFoundPage from '../NotFoundPage';
@ -20,10 +20,6 @@ import { getDataSucceeded } from './actions';
import routes from './utils/routes';
import { makeUniqueRoutes, createRoute } from '../SettingsPage/utils';
window.strapi = Object.assign(window.strapi || {}, {
lockAppWithOverlay: () => console.log('todo unlockAppWithOverlay'),
});
function App(props) {
const toggleNotification = useNotification();
const getDataRef = useRef();
@ -118,7 +114,7 @@ function App(props) {
)}
exact
/>
<PrivateRoute path="/" component={PluginsInitializer} />
<PrivateRoute path="/" component={AuthenticatedApp} />
<Route path="" component={NotFoundPage} />
</Switch>
</Content>

View File

@ -3,6 +3,7 @@ import adminReducer from './pages/Admin/reducer';
import languageProviderReducer from './components/LanguageProvider/reducer';
import menuReducer from './components/LeftMenu/reducer';
import permissionsManagerReducer from './components/PermissionsManager/reducer';
import rbacProviderReducer from './components/RBACProvider/reducer';
const reducers = {
admin: adminReducer,
@ -10,6 +11,7 @@ const reducers = {
language: languageProviderReducer,
menu: menuReducer,
permissionsManager: permissionsManagerReducer,
rbacProvider: rbacProviderReducer,
};
export default reducers;

View File

@ -0,0 +1,13 @@
import axios from 'axios';
import { auth } from '@strapi/helper-plugin';
const instance = axios.create({
baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
timeout: 1000,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${auth.getToken()}`,
},
});
export default instance;

View File

@ -5,5 +5,5 @@ export { default as retrieveGlobalLinks } from './retrieveGlobalLinks';
export { default as retrievePluginsMenu } from './retrievePluginsMenu';
export { default as sortLinks } from './sortLinks';
export { default as getExistingActions } from './getExistingActions';
export { default as checkLatestStrapiVersion } from './checkLatestStrapiVersion';
export { default as getRequestUrl } from './getRequestUrl';

View File

@ -0,0 +1,12 @@
/**
*
* AppInfosContext
*
*
*/
import { createContext } from 'react';
const AppInfosContext = createContext();
export default AppInfosContext;

View File

@ -0,0 +1,16 @@
/**
*
* useAppInfos
*
*/
import { useContext } from 'react';
import AppInfosContext from '../../contexts/AppInfosContext';
const useAppInfos = () => {
const appInfos = useContext(AppInfosContext);
return appInfos;
};
export default useAppInfos;

View File

@ -106,6 +106,7 @@ export { default as PopUpWarningIcon } from './components/PopUpWarning/Icon';
export { default as PopUpWarningModal } from './components/PopUpWarning/StyledModal';
// Contexts
export { default as AppInfosContext } from './contexts/AppInfosContext';
export { default as AutoReloadOverlayBockerContext } from './contexts/AutoReloadOverlayBockerContext';
export { default as NotificationsContext } from './contexts/NotificationsContext';
export { default as OverlayBlockerContext } from './contexts/OverlayBlockerContext';
@ -114,6 +115,7 @@ export { default as UserContext } from './contexts/UserContext';
export { default as ContentManagerEditViewDataManagerContext } from './contexts/ContentManagerEditViewDataManagerContext';
// Hooks
export { default as useAppInfos } from './hooks/useAppInfos';
export { default as useContentManagerEditViewDataManager } from './hooks/useContentManagerEditViewDataManager';
export { default as useQuery } from './hooks/useQuery';
export { default as useLibrary } from './hooks/useLibrary';