mirror of
https://github.com/strapi/strapi.git
synced 2025-09-03 05:39:36 +00:00
chore(admin): convert more admin components (#18433)
* chore(admin): convert LanguageProvider to ts * chore(admin): convert more contexts * chore(helper-plugin): produce declaration maps * fix(admin): LANGUAGE_LOCAL_STORAGE_KEY export * chore(admin): convert useMenu * chore(admin): convert LeftMenu to TS * test(helper-plugin): fix utils * chore: fix tests
This commit is contained in:
parent
c7b5014253
commit
054cea409d
@ -9,7 +9,7 @@ import { Helmet } from 'react-helmet';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
import Logo from './assets/images/logo-strapi-2022.svg';
|
||||
import localStorageKey from './components/LanguageProvider/utils/localStorageKey';
|
||||
import { LANGUAGE_LOCAL_STORAGE_KEY } from './components/LanguageProvider';
|
||||
import Providers from './components/Providers';
|
||||
import { customFields, Plugin } from './core/apis';
|
||||
import configureStore from './core/store/configureStore';
|
||||
@ -462,7 +462,7 @@ class StrapiApp {
|
||||
href: this.configurations.head.favicon,
|
||||
},
|
||||
]}
|
||||
htmlAttributes={{ lang: localStorage.getItem(localStorageKey) || 'en' }}
|
||||
htmlAttributes={{ lang: localStorage.getItem(LANGUAGE_LOCAL_STORAGE_KEY) || 'en' }}
|
||||
/>
|
||||
<BrowserRouter basename={basename}>
|
||||
<App store={store} />
|
||||
|
@ -12,7 +12,7 @@ import { useQueries } from 'react-query';
|
||||
// TODO: DS add loader
|
||||
|
||||
import packageJSON from '../../../../package.json';
|
||||
import { useConfiguration } from '../../hooks';
|
||||
import { useConfiguration } from '../../hooks/useConfiguration';
|
||||
import { getFullName, hashAdminUserEmail } from '../../utils';
|
||||
import { NpsSurvey } from '../NpsSurvey';
|
||||
import { PluginsInitializer } from '../PluginsInitializer';
|
||||
|
129
packages/core/admin/admin/src/components/LanguageProvider.tsx
Normal file
129
packages/core/admin/admin/src/components/LanguageProvider.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
*
|
||||
* LanguageProvider
|
||||
*
|
||||
* this component connects the redux state language locale to the
|
||||
* IntlProvider component and i18n messages (loaded from `app/translations`)
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import defaultsDeep from 'lodash/defaultsDeep';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* LocalesContext
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
interface LocalesContextValue {
|
||||
changeLocale: (locale: keyof State['localeNames']) => void;
|
||||
localeNames: Record<string, string>;
|
||||
}
|
||||
|
||||
const LocalesContext = React.createContext<LocalesContextValue>({
|
||||
changeLocale: () => {
|
||||
throw new Error('LocalesContext: changeLocale() is not implemented');
|
||||
},
|
||||
localeNames: {},
|
||||
});
|
||||
|
||||
const useLocales = () => React.useContext(LocalesContext);
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* LanguageProvider
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
const LANGUAGE_LOCAL_STORAGE_KEY = 'strapi-admin-language';
|
||||
|
||||
interface LanguageProviderProps {
|
||||
children: React.ReactNode;
|
||||
localeNames: Record<string, string>;
|
||||
messages: Record<string, Record<string, string>>;
|
||||
}
|
||||
|
||||
const LanguageProvider = ({ children, localeNames, messages }: LanguageProviderProps) => {
|
||||
const [{ locale }, dispatch] = React.useReducer<React.Reducer<State, Action>, State>(
|
||||
reducer,
|
||||
initialState,
|
||||
() => {
|
||||
const languageFromLocaleStorage = window.localStorage.getItem(LANGUAGE_LOCAL_STORAGE_KEY);
|
||||
if (languageFromLocaleStorage && localeNames[languageFromLocaleStorage]) {
|
||||
return {
|
||||
locale: languageFromLocaleStorage,
|
||||
localeNames,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
locale: 'en',
|
||||
localeNames,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Set user language in local storage.
|
||||
window.localStorage.setItem(LANGUAGE_LOCAL_STORAGE_KEY, locale);
|
||||
document.documentElement.setAttribute('lang', locale);
|
||||
}, [locale]);
|
||||
|
||||
const changeLocale = React.useCallback((locale: keyof State['localeNames']) => {
|
||||
dispatch({
|
||||
type: 'CHANGE_LOCALE',
|
||||
locale,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const appMessages = defaultsDeep(messages[locale], messages.en);
|
||||
|
||||
const contextValue = React.useMemo(
|
||||
() => ({ changeLocale, localeNames }),
|
||||
[changeLocale, localeNames]
|
||||
);
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} defaultLocale="en" messages={appMessages} textComponent="span">
|
||||
<LocalesContext.Provider value={contextValue}>{children}</LocalesContext.Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* Reducer
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
interface State {
|
||||
localeNames: Record<string, string>;
|
||||
locale: keyof State['localeNames'];
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
localeNames: { en: 'English' },
|
||||
locale: 'en',
|
||||
};
|
||||
|
||||
interface ChangeLocaleAction {
|
||||
type: 'CHANGE_LOCALE';
|
||||
locale: keyof State['localeNames'];
|
||||
}
|
||||
|
||||
type Action = ChangeLocaleAction;
|
||||
|
||||
const reducer = (state = initialState, action: Action) => {
|
||||
switch (action.type) {
|
||||
case 'CHANGE_LOCALE': {
|
||||
const { locale } = action;
|
||||
|
||||
if (!state.localeNames[locale]) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return { ...state, locale };
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export { LanguageProvider, useLocales, LANGUAGE_LOCAL_STORAGE_KEY };
|
@ -1,54 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* LanguageProvider
|
||||
*
|
||||
* this component connects the redux state language locale to the
|
||||
* IntlProvider component and i18n messages (loaded from `app/translations`)
|
||||
*/
|
||||
|
||||
import React, { useEffect, useReducer } from 'react';
|
||||
|
||||
import defaultsDeep from 'lodash/defaultsDeep';
|
||||
import PropTypes from 'prop-types';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import LocalesProvider from '../LocalesProvider';
|
||||
|
||||
import init from './init';
|
||||
import reducer, { initialState } from './reducer';
|
||||
import localStorageKey from './utils/localStorageKey';
|
||||
|
||||
const LanguageProvider = ({ children, localeNames, messages }) => {
|
||||
const [{ locale }, dispatch] = useReducer(reducer, initialState, () => init(localeNames));
|
||||
|
||||
useEffect(() => {
|
||||
// Set user language in local storage.
|
||||
window.localStorage.setItem(localStorageKey, locale);
|
||||
document.documentElement.setAttribute('lang', locale);
|
||||
}, [locale]);
|
||||
|
||||
const changeLocale = (locale) => {
|
||||
dispatch({
|
||||
type: 'CHANGE_LOCALE',
|
||||
locale,
|
||||
});
|
||||
};
|
||||
|
||||
const appMessages = defaultsDeep(messages[locale], messages.en);
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} defaultLocale="en" messages={appMessages} textComponent="span">
|
||||
<LocalesProvider changeLocale={changeLocale} localeNames={localeNames}>
|
||||
{children}
|
||||
</LocalesProvider>
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
LanguageProvider.propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
localeNames: PropTypes.objectOf(PropTypes.string).isRequired,
|
||||
messages: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default LanguageProvider;
|
@ -1,13 +0,0 @@
|
||||
import localStorageKey from './utils/localStorageKey';
|
||||
|
||||
const init = (localeNames) => {
|
||||
const languageFromLocaleStorage = window.localStorage.getItem(localStorageKey);
|
||||
const appLanguage = localeNames[languageFromLocaleStorage] ? languageFromLocaleStorage : 'en';
|
||||
|
||||
return {
|
||||
locale: appLanguage,
|
||||
localeNames,
|
||||
};
|
||||
};
|
||||
|
||||
export default init;
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* LanguageProvider reducer
|
||||
*
|
||||
*/
|
||||
|
||||
const initialState = {
|
||||
localeNames: { en: 'English' },
|
||||
locale: 'en',
|
||||
};
|
||||
|
||||
const languageProviderReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case 'CHANGE_LOCALE': {
|
||||
const { locale } = action;
|
||||
|
||||
if (!state.localeNames[locale]) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return { ...state, locale };
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default languageProviderReducer;
|
||||
export { initialState };
|
@ -1,34 +0,0 @@
|
||||
import init from '../init';
|
||||
|
||||
const localeNames = { en: 'English', fr: 'Français' };
|
||||
|
||||
describe('LanguageProvider | init', () => {
|
||||
afterEach(() => {
|
||||
localStorage.removeItem('strapi-admin-language');
|
||||
});
|
||||
|
||||
it('should return the language from the localStorage', () => {
|
||||
localStorage.setItem('strapi-admin-language', 'fr');
|
||||
|
||||
expect(init(localeNames)).toEqual({
|
||||
locale: 'fr',
|
||||
localeNames,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return "en" when the strapi-admin-language is not set in the locale storage', () => {
|
||||
expect(init(localeNames)).toEqual({
|
||||
locale: 'en',
|
||||
localeNames,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return "en" when the language from the local storage is not included in the localeNames', () => {
|
||||
localStorage.setItem('strapi-admin-language', 'foo');
|
||||
|
||||
expect(init(localeNames)).toEqual({
|
||||
locale: 'en',
|
||||
localeNames,
|
||||
});
|
||||
});
|
||||
});
|
@ -1,41 +0,0 @@
|
||||
import reducer, { initialState } from '../reducer';
|
||||
|
||||
describe('LanguageProvider | reducer', () => {
|
||||
let state;
|
||||
|
||||
beforeEach(() => {
|
||||
state = initialState;
|
||||
});
|
||||
|
||||
it('should return the initialState', () => {
|
||||
const action = { type: undefined };
|
||||
|
||||
expect(reducer(state, action)).toEqual(initialState);
|
||||
});
|
||||
|
||||
it('should change the locale correctly when the locale is defined in the localeNames', () => {
|
||||
state = {
|
||||
localeNames: { en: 'English', fr: 'Français' },
|
||||
locale: 'en',
|
||||
};
|
||||
|
||||
const action = { type: 'CHANGE_LOCALE', locale: 'fr' };
|
||||
const expected = {
|
||||
localeNames: { en: 'English', fr: 'Français' },
|
||||
locale: 'fr',
|
||||
};
|
||||
|
||||
expect(reducer(state, action)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should not change the locale when the language is not defined in the localeNames', () => {
|
||||
state = {
|
||||
localeNames: { en: 'English', fr: 'Français' },
|
||||
locale: 'en',
|
||||
};
|
||||
|
||||
const action = { type: 'CHANGE_LOCALE', locale: 'foo' };
|
||||
|
||||
expect(reducer(state, action)).toEqual(state);
|
||||
});
|
||||
});
|
@ -1,3 +0,0 @@
|
||||
const localStorageKey = 'strapi-admin-language';
|
||||
|
||||
export default localStorageKey;
|
@ -1,4 +1,4 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { Box, Divider, Flex, FocusTrap, Typography } from '@strapi/design-system';
|
||||
import {
|
||||
@ -19,12 +19,12 @@ import {
|
||||
useTracking,
|
||||
} from '@strapi/helper-plugin';
|
||||
import { Exit, Write } from '@strapi/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { NavLink as RouterNavLink, useHistory, useLocation } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useConfiguration } from '../../hooks';
|
||||
import { useConfiguration } from '../hooks/useConfiguration';
|
||||
import { Menu } from '../hooks/useMenu';
|
||||
|
||||
const LinkUserWrapper = styled(Box)`
|
||||
width: ${150 / 16}rem;
|
||||
@ -33,7 +33,7 @@ const LinkUserWrapper = styled(Box)`
|
||||
left: ${({ theme }) => theme.spaces[5]};
|
||||
`;
|
||||
|
||||
const LinkUser = styled(RouterNavLink)`
|
||||
const LinkUser = styled(RouterNavLink)<{ logout?: boolean }>`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@ -54,9 +54,11 @@ const LinkUser = styled(RouterNavLink)`
|
||||
}
|
||||
`;
|
||||
|
||||
const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
|
||||
const buttonRef = useRef();
|
||||
const [userLinksVisible, setUserLinksVisible] = useState(false);
|
||||
interface LeftMenuProps extends Pick<Menu, 'generalSectionLinks' | 'pluginsSectionLinks'> {}
|
||||
|
||||
const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }: LeftMenuProps) => {
|
||||
const navUserRef = React.useRef<HTMLDivElement>(null!);
|
||||
const [userLinksVisible, setUserLinksVisible] = React.useState(false);
|
||||
const {
|
||||
logos: { menu },
|
||||
} = useConfiguration();
|
||||
@ -83,16 +85,19 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
|
||||
history.push('/auth/login');
|
||||
};
|
||||
|
||||
const handleBlur = (e) => {
|
||||
const handleBlur: React.FocusEventHandler = (e) => {
|
||||
if (
|
||||
!e.currentTarget.contains(e.relatedTarget) &&
|
||||
/**
|
||||
* TODO: can we replace this by just using the navUserRef?
|
||||
*/
|
||||
e.relatedTarget?.parentElement?.id !== 'main-nav-user-button'
|
||||
) {
|
||||
setUserLinksVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClickOnLink = (destination = null) => {
|
||||
const handleClickOnLink = (destination: string) => {
|
||||
trackUsage('willNavigate', { from: pathname, to: destination });
|
||||
};
|
||||
|
||||
@ -126,6 +131,7 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
|
||||
<NavSections>
|
||||
<NavLink
|
||||
as={RouterNavLink}
|
||||
// @ts-expect-error the props from the passed as prop are not inferred // joined together
|
||||
to="/content-manager"
|
||||
icon={<Write />}
|
||||
onClick={() => handleClickOnLink('/content-manager')}
|
||||
@ -146,6 +152,7 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
|
||||
return (
|
||||
<NavLink
|
||||
as={RouterNavLink}
|
||||
// @ts-expect-error the props from the passed as prop are not inferred // joined together
|
||||
to={link.to}
|
||||
key={link.to}
|
||||
icon={<Icon />}
|
||||
@ -172,8 +179,11 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
|
||||
<NavLink
|
||||
as={RouterNavLink}
|
||||
badgeContent={
|
||||
(link.notificationsCount > 0 && link.notificationsCount.toString()) || undefined
|
||||
link.notificationsCount && link.notificationsCount > 0
|
||||
? link.notificationsCount.toString()
|
||||
: undefined
|
||||
}
|
||||
// @ts-expect-error the props from the passed as prop are not inferred // joined together
|
||||
to={link.to}
|
||||
key={link.to}
|
||||
icon={<LinkIcon />}
|
||||
@ -190,7 +200,7 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
|
||||
<NavFooter>
|
||||
<NavUser
|
||||
id="main-nav-user-button"
|
||||
ref={buttonRef}
|
||||
ref={navUserRef}
|
||||
onClick={handleToggleUserLinks}
|
||||
initials={initials}
|
||||
>
|
||||
@ -244,9 +254,4 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
|
||||
);
|
||||
};
|
||||
|
||||
LeftMenu.propTypes = {
|
||||
generalSectionLinks: PropTypes.array.isRequired,
|
||||
pluginsSectionLinks: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
export default LeftMenu;
|
||||
export { LeftMenu };
|
@ -1,5 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
const LocalesProviderContext = createContext();
|
||||
|
||||
export default LocalesProviderContext;
|
@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import LocalesProviderContext from './context';
|
||||
|
||||
const LocalesProvider = ({ changeLocale, children, localeNames }) => {
|
||||
return (
|
||||
<LocalesProviderContext.Provider value={{ changeLocale, localeNames }}>
|
||||
{children}
|
||||
</LocalesProviderContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
LocalesProvider.propTypes = {
|
||||
changeLocale: PropTypes.func.isRequired,
|
||||
children: PropTypes.element.isRequired,
|
||||
localeNames: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default LocalesProvider;
|
@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import LocalesProvider from '../index';
|
||||
|
||||
describe('LocalesProvider', () => {
|
||||
it('should not crash', () => {
|
||||
const { container } = render(
|
||||
<LocalesProvider
|
||||
changeLocale={jest.fn()}
|
||||
localeNames={{ en: 'English' }}
|
||||
messages={{ en: {} }}
|
||||
>
|
||||
<div>Test</div>
|
||||
</LocalesProvider>
|
||||
);
|
||||
|
||||
expect(container.firstChild).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
Test
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
});
|
@ -1,11 +0,0 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import LocalesProviderContext from './context';
|
||||
|
||||
const useLocalesProvider = () => {
|
||||
const { changeLocale, localeNames, messages } = useContext(LocalesProviderContext);
|
||||
|
||||
return { changeLocale, localeNames, messages };
|
||||
};
|
||||
|
||||
export default useLocalesProvider;
|
@ -12,10 +12,10 @@ import PropTypes from 'prop-types';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { AdminContext } from '../../contexts';
|
||||
import { AdminContext } from '../../contexts/admin';
|
||||
import { ConfigurationProvider } from '../ConfigurationProvider';
|
||||
import GuidedTour from '../GuidedTour';
|
||||
import LanguageProvider from '../LanguageProvider';
|
||||
import { LanguageProvider } from '../LanguageProvider';
|
||||
import { Theme } from '../Theme';
|
||||
import { ThemeToggleProvider } from '../ThemeToggleProvider';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
export default function useLocalesProvider() {
|
||||
export function useLocalesProvider() {
|
||||
return {
|
||||
changeLocale() {},
|
||||
localeNames: { en: 'English' },
|
@ -1,43 +1,39 @@
|
||||
import React from 'react';
|
||||
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { render } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import en from '../../../translations/en.json';
|
||||
import fr from '../../../translations/fr.json';
|
||||
import useLocalesProvider from '../../LocalesProvider/useLocalesProvider';
|
||||
import LanguageProvider from '../index';
|
||||
import en from '../../translations/en.json';
|
||||
import fr from '../../translations/fr.json';
|
||||
import { LanguageProvider, useLocales } from '../LanguageProvider';
|
||||
|
||||
const messages = { en, fr };
|
||||
const localeNames = { en: 'English', fr: 'Français' };
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
describe('LanguageProvider', () => {
|
||||
afterEach(() => {
|
||||
localStorage.removeItem('strapi-admin-language');
|
||||
});
|
||||
|
||||
it('should not crash', () => {
|
||||
const { container } = render(
|
||||
const { getByText } = render(
|
||||
<LanguageProvider messages={messages} localeNames={localeNames}>
|
||||
<div>Test</div>
|
||||
</LanguageProvider>
|
||||
);
|
||||
|
||||
expect(container.firstChild).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
Test
|
||||
</div>
|
||||
`);
|
||||
expect(getByText('Test')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should change the locale and set the strapi-admin-language item in the localStorage', async () => {
|
||||
const Test = () => {
|
||||
const { locale } = useIntl();
|
||||
const { changeLocale } = useLocalesProvider();
|
||||
const { changeLocale } = useLocales();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{localeNames[locale]}</h1>
|
||||
<h1>{localeNames[locale as keyof typeof messages]}</h1>
|
||||
<button type="button" onClick={() => changeLocale('fr')}>
|
||||
CHANGE
|
||||
</button>
|
||||
@ -45,7 +41,7 @@ describe('LanguageProvider', () => {
|
||||
);
|
||||
};
|
||||
|
||||
render(
|
||||
const { getByText, getByRole } = render(
|
||||
<LanguageProvider messages={messages} localeNames={localeNames}>
|
||||
<Test />
|
||||
</LanguageProvider>
|
||||
@ -53,11 +49,11 @@ describe('LanguageProvider', () => {
|
||||
|
||||
expect(localStorage.getItem('strapi-admin-language')).toEqual('en');
|
||||
|
||||
expect(screen.getByText('English')).toBeInTheDocument();
|
||||
expect(getByText('English')).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByText('CHANGE'));
|
||||
await user.click(getByRole('button', { name: 'CHANGE' }));
|
||||
|
||||
expect(screen.getByText('Français')).toBeInTheDocument();
|
||||
expect(getByText('Français')).toBeInTheDocument();
|
||||
expect(localStorage.getItem('strapi-admin-language')).toEqual('fr');
|
||||
});
|
||||
});
|
@ -1,5 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
const AdminContext = createContext({});
|
||||
|
||||
export default AdminContext;
|
@ -1,18 +0,0 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const MarketPlaceContext = createContext({});
|
||||
|
||||
const MarketPlaceContextProvider = ({ children, ...rest }) => {
|
||||
return <MarketPlaceContext.Provider value={rest}>{children}</MarketPlaceContext.Provider>;
|
||||
};
|
||||
|
||||
const useMarketPlaceContext = () => useContext(MarketPlaceContext);
|
||||
|
||||
MarketPlaceContextProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
downloadPlugin: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export { MarketPlaceContext, MarketPlaceContextProvider, useMarketPlaceContext };
|
18
packages/core/admin/admin/src/contexts/admin.ts
Normal file
18
packages/core/admin/admin/src/contexts/admin.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import * as React from 'react';
|
||||
|
||||
interface AdminContextValue {
|
||||
/**
|
||||
* TODO: this should come from `StrapiApp['getAdminInjectedComponents']`
|
||||
*/
|
||||
getAdminInjectedComponents: () => unknown;
|
||||
}
|
||||
|
||||
const AdminContext = React.createContext<AdminContextValue>({
|
||||
getAdminInjectedComponents() {
|
||||
throw new Error('AdminContext: getAdminInjectedComponents() not implemented');
|
||||
},
|
||||
});
|
||||
|
||||
const useAdmin = () => React.useContext(AdminContext);
|
||||
|
||||
export { AdminContext, useAdmin };
|
@ -1 +0,0 @@
|
||||
export { default as AdminContext } from './Admin';
|
@ -1,5 +1,3 @@
|
||||
export { useConfiguration } from './useConfiguration';
|
||||
export { useContentTypes } from './useContentTypes';
|
||||
export { default as useMenu } from './useMenu';
|
||||
export { default as useSettingsForm } from './useSettingsForm';
|
||||
export { default as useSettingsMenu } from './useSettingsMenu';
|
||||
|
153
packages/core/admin/admin/src/hooks/useMenu.ts
Normal file
153
packages/core/admin/admin/src/hooks/useMenu.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import {
|
||||
Permission,
|
||||
hasPermissions,
|
||||
useAppInfo,
|
||||
useRBACProvider,
|
||||
useStrapiApp,
|
||||
StrapiAppContextValue,
|
||||
} from '@strapi/helper-plugin';
|
||||
import { Cog, Puzzle, ShoppingCart } from '@strapi/icons';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
// @ts-expect-error - no types, yet.
|
||||
import { selectAdminPermissions } from '../pages/App/selectors';
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* useMenu
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
type MenuItem = StrapiAppContextValue['menu'][number];
|
||||
|
||||
export interface Menu {
|
||||
generalSectionLinks: MenuItem[];
|
||||
pluginsSectionLinks: MenuItem[];
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const useMenu = () => {
|
||||
const { allPermissions: userPermissions } = useRBACProvider();
|
||||
const { shouldUpdateStrapi } = useAppInfo();
|
||||
const { menu } = useStrapiApp();
|
||||
const permissions = useSelector(selectAdminPermissions);
|
||||
const [menuWithUserPermissions, setMenuWithUserPermissions] = React.useState<Menu>({
|
||||
generalSectionLinks: [
|
||||
{
|
||||
icon: Puzzle,
|
||||
intlLabel: {
|
||||
id: 'global.plugins',
|
||||
defaultMessage: 'Plugins',
|
||||
},
|
||||
to: '/list-plugins',
|
||||
// @ts-expect-error - we need the permissions type from the plugin
|
||||
permissions: permissions.marketplace.main,
|
||||
},
|
||||
{
|
||||
icon: ShoppingCart,
|
||||
intlLabel: {
|
||||
id: 'global.marketplace',
|
||||
defaultMessage: 'Marketplace',
|
||||
},
|
||||
to: '/marketplace',
|
||||
// @ts-expect-error - we need the permissions type from the plugin
|
||||
permissions: permissions.marketplace.main,
|
||||
},
|
||||
{
|
||||
icon: Cog,
|
||||
intlLabel: {
|
||||
id: 'global.settings',
|
||||
defaultMessage: 'Settings',
|
||||
},
|
||||
to: '/settings',
|
||||
// Permissions of this link are retrieved in the init phase
|
||||
// using the settings menu
|
||||
permissions: [],
|
||||
notificationsCount: 0,
|
||||
},
|
||||
],
|
||||
pluginsSectionLinks: [],
|
||||
isLoading: true,
|
||||
});
|
||||
const generalSectionLinksRef = React.useRef(menuWithUserPermissions.generalSectionLinks);
|
||||
|
||||
React.useEffect(() => {
|
||||
async function applyMenuPermissions() {
|
||||
const authorizedPluginSectionLinks = await getPluginSectionLinks(userPermissions, menu);
|
||||
|
||||
const authorizedGeneralSectionLinks = await getGeneralLinks(
|
||||
userPermissions,
|
||||
generalSectionLinksRef.current,
|
||||
shouldUpdateStrapi
|
||||
);
|
||||
|
||||
setMenuWithUserPermissions((state) => ({
|
||||
...state,
|
||||
generalSectionLinks: authorizedGeneralSectionLinks,
|
||||
pluginsSectionLinks: authorizedPluginSectionLinks,
|
||||
isLoading: false,
|
||||
}));
|
||||
}
|
||||
|
||||
applyMenuPermissions();
|
||||
}, [
|
||||
setMenuWithUserPermissions,
|
||||
generalSectionLinksRef,
|
||||
userPermissions,
|
||||
menu,
|
||||
permissions,
|
||||
shouldUpdateStrapi,
|
||||
]);
|
||||
|
||||
return menuWithUserPermissions;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* Utils
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
const getGeneralLinks = async (
|
||||
userPermissions: Permission[],
|
||||
generalSectionRawLinks: MenuItem[],
|
||||
shouldUpdateStrapi: boolean = false
|
||||
) => {
|
||||
const generalSectionLinksPermissions = await Promise.all(
|
||||
generalSectionRawLinks.map(({ permissions }) => hasPermissions(userPermissions, permissions))
|
||||
);
|
||||
|
||||
const authorizedGeneralSectionLinks = generalSectionRawLinks.filter(
|
||||
(_, index) => generalSectionLinksPermissions[index]
|
||||
);
|
||||
|
||||
const settingsLinkIndex = authorizedGeneralSectionLinks.findIndex(
|
||||
(obj) => obj.to === '/settings'
|
||||
);
|
||||
|
||||
if (settingsLinkIndex === -1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const authorizedGeneralLinksClone = cloneDeep(authorizedGeneralSectionLinks);
|
||||
|
||||
authorizedGeneralLinksClone[settingsLinkIndex].notificationsCount = shouldUpdateStrapi ? 1 : 0;
|
||||
|
||||
return authorizedGeneralLinksClone;
|
||||
};
|
||||
|
||||
const getPluginSectionLinks = async (
|
||||
userPermissions: Permission[],
|
||||
pluginsSectionRawLinks: MenuItem[]
|
||||
) => {
|
||||
const pluginSectionLinksPermissions = await Promise.all(
|
||||
pluginsSectionRawLinks.map(({ permissions }) => hasPermissions(userPermissions, permissions))
|
||||
);
|
||||
|
||||
const authorizedPluginSectionLinks = pluginsSectionRawLinks.filter(
|
||||
(_, index) => pluginSectionLinksPermissions[index]
|
||||
);
|
||||
|
||||
return authorizedPluginSectionLinks;
|
||||
};
|
||||
|
||||
export { useMenu };
|
@ -1,86 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { useAppInfo, useRBACProvider, useStrapiApp } from '@strapi/helper-plugin';
|
||||
import { Cog, Puzzle, ShoppingCart } from '@strapi/icons';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { selectAdminPermissions } from '../../pages/App/selectors';
|
||||
|
||||
import getGeneralLinks from './utils/getGeneralLinks';
|
||||
import getPluginSectionLinks from './utils/getPluginSectionLinks';
|
||||
|
||||
const useMenu = () => {
|
||||
const { allPermissions: userPermissions } = useRBACProvider();
|
||||
const { shouldUpdateStrapi } = useAppInfo();
|
||||
const { menu } = useStrapiApp();
|
||||
const permissions = useSelector(selectAdminPermissions);
|
||||
const [menuWithUserPermissions, setMenuWithUserPermissions] = React.useState({
|
||||
generalSectionLinks: [
|
||||
{
|
||||
icon: Puzzle,
|
||||
intlLabel: {
|
||||
id: 'global.plugins',
|
||||
defaultMessage: 'Plugins',
|
||||
},
|
||||
to: '/list-plugins',
|
||||
permissions: permissions.marketplace.main,
|
||||
},
|
||||
{
|
||||
icon: ShoppingCart,
|
||||
intlLabel: {
|
||||
id: 'global.marketplace',
|
||||
defaultMessage: 'Marketplace',
|
||||
},
|
||||
to: '/marketplace',
|
||||
permissions: permissions.marketplace.main,
|
||||
},
|
||||
{
|
||||
icon: Cog,
|
||||
intlLabel: {
|
||||
id: 'global.settings',
|
||||
defaultMessage: 'Settings',
|
||||
},
|
||||
to: '/settings',
|
||||
// Permissions of this link are retrieved in the init phase
|
||||
// using the settings menu
|
||||
permissions: [],
|
||||
notificationsCount: 0,
|
||||
},
|
||||
],
|
||||
pluginsSectionLinks: [],
|
||||
isLoading: true,
|
||||
});
|
||||
const generalSectionLinksRef = React.useRef(menuWithUserPermissions.generalSectionLinks);
|
||||
|
||||
React.useEffect(() => {
|
||||
async function applyMenuPermissions() {
|
||||
const authorizedPluginSectionLinks = await getPluginSectionLinks(userPermissions, menu);
|
||||
|
||||
const authorizedGeneralSectionLinks = await getGeneralLinks(
|
||||
userPermissions,
|
||||
generalSectionLinksRef.current,
|
||||
shouldUpdateStrapi
|
||||
);
|
||||
|
||||
setMenuWithUserPermissions((state) => ({
|
||||
...state,
|
||||
generalSectionLinks: authorizedGeneralSectionLinks,
|
||||
pluginsSectionLinks: authorizedPluginSectionLinks,
|
||||
isLoading: false,
|
||||
}));
|
||||
}
|
||||
|
||||
applyMenuPermissions();
|
||||
}, [
|
||||
setMenuWithUserPermissions,
|
||||
generalSectionLinksRef,
|
||||
userPermissions,
|
||||
menu,
|
||||
permissions,
|
||||
shouldUpdateStrapi,
|
||||
]);
|
||||
|
||||
return menuWithUserPermissions;
|
||||
};
|
||||
|
||||
export default useMenu;
|
@ -1,13 +0,0 @@
|
||||
import { hasPermissions } from '@strapi/helper-plugin';
|
||||
|
||||
/**
|
||||
* This function resolves an array of Promises<boolean>
|
||||
* It puts at a specific index the status of a specific permission.
|
||||
* While this might look weird, we then iterate on this array
|
||||
* and check the different CT/ST/general/plugin sections
|
||||
* and make an index based comparisons
|
||||
*/
|
||||
const checkPermissions = (userPermissions, permissionsToCheck) =>
|
||||
permissionsToCheck.map(({ permissions }) => hasPermissions(userPermissions, permissions));
|
||||
|
||||
export default checkPermissions;
|
@ -1,31 +0,0 @@
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
|
||||
import checkPermissions from './checkPermissions';
|
||||
|
||||
const getGeneralLinks = async (userPermissions, generalSectionRawLinks, shouldUpdateStrapi) => {
|
||||
const generalSectionPermissionsPromises = checkPermissions(
|
||||
userPermissions,
|
||||
generalSectionRawLinks
|
||||
);
|
||||
const generalSectionLinksPermissions = await Promise.all(generalSectionPermissionsPromises);
|
||||
|
||||
const authorizedGeneralSectionLinks = generalSectionRawLinks.filter(
|
||||
(_, index) => generalSectionLinksPermissions[index]
|
||||
);
|
||||
|
||||
const settingsLinkIndex = authorizedGeneralSectionLinks.findIndex(
|
||||
(obj) => obj.to === '/settings'
|
||||
);
|
||||
|
||||
if (settingsLinkIndex === -1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const authorizedGeneralLinksClone = cloneDeep(authorizedGeneralSectionLinks);
|
||||
|
||||
authorizedGeneralLinksClone[settingsLinkIndex].notificationsCount = shouldUpdateStrapi ? 1 : 0;
|
||||
|
||||
return authorizedGeneralLinksClone;
|
||||
};
|
||||
|
||||
export default getGeneralLinks;
|
@ -1,17 +0,0 @@
|
||||
import checkPermissions from './checkPermissions';
|
||||
|
||||
const getPluginSectionLinks = async (userPermissions, pluginsSectionRawLinks) => {
|
||||
const pluginSectionPermissionsPromises = checkPermissions(
|
||||
userPermissions,
|
||||
pluginsSectionRawLinks
|
||||
);
|
||||
const pluginSectionLinksPermissions = await Promise.all(pluginSectionPermissionsPromises);
|
||||
|
||||
const authorizedPluginSectionLinks = pluginsSectionRawLinks.filter(
|
||||
(_, index) => pluginSectionLinksPermissions[index]
|
||||
);
|
||||
|
||||
return authorizedPluginSectionLinks;
|
||||
};
|
||||
|
||||
export default getPluginSectionLinks;
|
@ -1,17 +0,0 @@
|
||||
import checkPermissions from '../checkPermissions';
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
hasPermissions: () => Promise.resolve(true),
|
||||
}));
|
||||
|
||||
describe('checkPermissions', () => {
|
||||
it('creates an array of boolean corresponding to the permission state', async () => {
|
||||
const userPermissions = {};
|
||||
const permissions = [{ permissions: {} }, { permissions: {} }];
|
||||
|
||||
const expected = [true, true];
|
||||
const actual = await Promise.all(checkPermissions(userPermissions, permissions));
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
@ -1,223 +0,0 @@
|
||||
import { hasPermissions } from '@strapi/helper-plugin';
|
||||
|
||||
import getGeneralLinks from '../getGeneralLinks';
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
hasPermissions: jest.fn().mockResolvedValue(true),
|
||||
}));
|
||||
|
||||
describe('getGeneralLinks', () => {
|
||||
it('resolves valid general links from real data', async () => {
|
||||
const permissions = [
|
||||
{
|
||||
id: 458,
|
||||
action: 'plugin::i18n.locale.read',
|
||||
subject: null,
|
||||
properties: {},
|
||||
conditions: [],
|
||||
},
|
||||
{
|
||||
id: 459,
|
||||
action: 'plugin::content-manager.explorer.create',
|
||||
subject: 'api::article.article',
|
||||
properties: {
|
||||
fields: ['Name'],
|
||||
locales: ['en'],
|
||||
},
|
||||
conditions: [],
|
||||
},
|
||||
{
|
||||
id: 460,
|
||||
action: 'plugin::content-manager.explorer.read',
|
||||
subject: 'api::article.article',
|
||||
properties: {
|
||||
fields: ['Name'],
|
||||
locales: ['en'],
|
||||
},
|
||||
conditions: [],
|
||||
},
|
||||
{
|
||||
id: 461,
|
||||
action: 'plugin::content-manager.explorer.read',
|
||||
subject: 'api::article.article',
|
||||
properties: {
|
||||
fields: ['Name'],
|
||||
locales: ['fr-FR'],
|
||||
},
|
||||
conditions: [],
|
||||
},
|
||||
{
|
||||
id: 462,
|
||||
action: 'plugin::content-manager.explorer.update',
|
||||
subject: 'api::article.article',
|
||||
properties: {
|
||||
fields: ['Name'],
|
||||
locales: ['fr-FR'],
|
||||
},
|
||||
conditions: [],
|
||||
},
|
||||
];
|
||||
const generalSectionRawLinks = [
|
||||
{
|
||||
icon: 'list',
|
||||
label: 'app.components.LeftMenuLinkContainer.listPlugins',
|
||||
to: '/list-plugins',
|
||||
isDisplayed: false,
|
||||
permissions: [
|
||||
{
|
||||
action: 'admin::marketplace.read',
|
||||
subject: null,
|
||||
},
|
||||
],
|
||||
notificationsCount: 0,
|
||||
},
|
||||
{
|
||||
icon: 'shopping-basket',
|
||||
label: 'app.components.LeftMenuLinkContainer.installNewPlugin',
|
||||
to: '/marketplace',
|
||||
isDisplayed: false,
|
||||
permissions: [
|
||||
{
|
||||
action: 'admin::marketplace.read',
|
||||
subject: null,
|
||||
},
|
||||
],
|
||||
notificationsCount: 0,
|
||||
},
|
||||
{
|
||||
icon: 'cog',
|
||||
label: 'app.components.LeftMenuLinkContainer.settings',
|
||||
isDisplayed: true,
|
||||
to: '/settings',
|
||||
permissions: [],
|
||||
notificationsCount: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const expected = [
|
||||
{
|
||||
icon: 'list',
|
||||
label: 'app.components.LeftMenuLinkContainer.listPlugins',
|
||||
to: '/list-plugins',
|
||||
isDisplayed: false,
|
||||
permissions: [
|
||||
{
|
||||
action: 'admin::marketplace.read',
|
||||
subject: null,
|
||||
},
|
||||
],
|
||||
notificationsCount: 0,
|
||||
},
|
||||
{
|
||||
icon: 'shopping-basket',
|
||||
label: 'app.components.LeftMenuLinkContainer.installNewPlugin',
|
||||
to: '/marketplace',
|
||||
isDisplayed: false,
|
||||
permissions: [
|
||||
{
|
||||
action: 'admin::marketplace.read',
|
||||
subject: null,
|
||||
},
|
||||
],
|
||||
notificationsCount: 0,
|
||||
},
|
||||
{
|
||||
icon: 'cog',
|
||||
label: 'app.components.LeftMenuLinkContainer.settings',
|
||||
isDisplayed: true,
|
||||
to: '/settings',
|
||||
permissions: [],
|
||||
notificationsCount: 0,
|
||||
},
|
||||
];
|
||||
const actual = await getGeneralLinks(permissions, generalSectionRawLinks, false);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('resolves an empty array when the to (/settings) is not in the authorized links', async () => {
|
||||
hasPermissions.mockImplementation(() => Promise.resolve(false));
|
||||
|
||||
const permissions = [
|
||||
{
|
||||
id: 458,
|
||||
action: 'plugin::i18n.locale.read',
|
||||
subject: null,
|
||||
properties: {},
|
||||
conditions: [],
|
||||
},
|
||||
{
|
||||
id: 459,
|
||||
action: 'plugin::content-manager.explorer.create',
|
||||
subject: 'api::article.article',
|
||||
properties: {
|
||||
fields: ['Name'],
|
||||
locales: ['en'],
|
||||
},
|
||||
conditions: [],
|
||||
},
|
||||
{
|
||||
id: 460,
|
||||
action: 'plugin::content-manager.explorer.read',
|
||||
subject: 'api::article.article',
|
||||
properties: {
|
||||
fields: ['Name'],
|
||||
locales: ['en'],
|
||||
},
|
||||
conditions: [],
|
||||
},
|
||||
{
|
||||
id: 461,
|
||||
action: 'plugin::content-manager.explorer.read',
|
||||
subject: 'api::article.article',
|
||||
properties: {
|
||||
fields: ['Name'],
|
||||
locales: ['fr-FR'],
|
||||
},
|
||||
conditions: [],
|
||||
},
|
||||
{
|
||||
id: 462,
|
||||
action: 'plugin::content-manager.explorer.update',
|
||||
subject: 'api::article.article',
|
||||
properties: {
|
||||
fields: ['Name'],
|
||||
locales: ['fr-FR'],
|
||||
},
|
||||
conditions: [],
|
||||
},
|
||||
];
|
||||
const generalSectionRawLinks = [
|
||||
{
|
||||
icon: 'list',
|
||||
label: 'app.components.LeftMenuLinkContainer.listPlugins',
|
||||
to: '/list-plugins',
|
||||
isDisplayed: false,
|
||||
permissions: [],
|
||||
notificationsCount: 0,
|
||||
},
|
||||
{
|
||||
icon: 'shopping-basket',
|
||||
label: 'app.components.LeftMenuLinkContainer.installNewPlugin',
|
||||
to: '/marketplace',
|
||||
isDisplayed: false,
|
||||
permissions: [],
|
||||
notificationsCount: 0,
|
||||
},
|
||||
{
|
||||
icon: 'cog',
|
||||
label: 'app.components.LeftMenuLinkContainer.settings',
|
||||
isDisplayed: false,
|
||||
to: '/settings',
|
||||
permissions: [],
|
||||
notificationsCount: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const expected = [];
|
||||
const actual = await getGeneralLinks(permissions, generalSectionRawLinks, false);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
@ -1,129 +0,0 @@
|
||||
import getPluginSectionLinks from '../getPluginSectionLinks';
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
hasPermissions: jest.fn().mockResolvedValue(true),
|
||||
}));
|
||||
|
||||
describe('getPluginSectionLinks', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('creates an array of boolean corresponding to the permission state', async () => {
|
||||
const userPermissions = [
|
||||
{
|
||||
id: 458,
|
||||
action: 'plugin::i18n.locale.read',
|
||||
subject: null,
|
||||
properties: {},
|
||||
conditions: [],
|
||||
},
|
||||
{
|
||||
id: 459,
|
||||
action: 'plugin::content-manager.explorer.create',
|
||||
subject: 'api::article.article',
|
||||
properties: {
|
||||
fields: ['Name'],
|
||||
locales: ['en'],
|
||||
},
|
||||
conditions: [],
|
||||
},
|
||||
{
|
||||
id: 460,
|
||||
action: 'plugin::content-manager.explorer.read',
|
||||
subject: 'api::article.article',
|
||||
properties: {
|
||||
fields: ['Name'],
|
||||
locales: ['en'],
|
||||
},
|
||||
conditions: [],
|
||||
},
|
||||
{
|
||||
id: 461,
|
||||
action: 'plugin::content-manager.explorer.read',
|
||||
subject: 'api::article.article',
|
||||
properties: {
|
||||
fields: ['Name'],
|
||||
locales: ['fr-FR'],
|
||||
},
|
||||
conditions: [],
|
||||
},
|
||||
{
|
||||
id: 462,
|
||||
action: 'plugin::content-manager.explorer.update',
|
||||
subject: 'api::article.article',
|
||||
properties: {
|
||||
fields: ['Name'],
|
||||
locales: ['fr-FR'],
|
||||
},
|
||||
conditions: [],
|
||||
},
|
||||
];
|
||||
|
||||
const pluginsSectionRawLinks = [
|
||||
{
|
||||
destination: '/plugins/content-type-builder',
|
||||
icon: 'paint-brush',
|
||||
label: {
|
||||
id: 'content-type-builder.plugin.name',
|
||||
defaultMessage: 'Content-Types Builder',
|
||||
},
|
||||
permissions: [
|
||||
{
|
||||
action: 'plugin::content-type-builder.read',
|
||||
subject: null,
|
||||
},
|
||||
],
|
||||
isDisplayed: false,
|
||||
},
|
||||
{
|
||||
destination: '/plugins/upload',
|
||||
icon: 'cloud-upload-alt',
|
||||
label: {
|
||||
id: 'upload.plugin.name',
|
||||
defaultMessage: 'Media Library',
|
||||
},
|
||||
permissions: [
|
||||
{
|
||||
action: 'plugin::upload.read',
|
||||
subject: null,
|
||||
},
|
||||
{
|
||||
action: 'plugin::upload.assets.create',
|
||||
subject: null,
|
||||
},
|
||||
{
|
||||
action: 'plugin::upload.assets.update',
|
||||
subject: null,
|
||||
},
|
||||
],
|
||||
isDisplayed: false,
|
||||
},
|
||||
];
|
||||
|
||||
const expected = [
|
||||
{
|
||||
destination: '/plugins/content-type-builder',
|
||||
icon: 'paint-brush',
|
||||
isDisplayed: false,
|
||||
label: { defaultMessage: 'Content-Types Builder', id: 'content-type-builder.plugin.name' },
|
||||
permissions: [{ action: 'plugin::content-type-builder.read', subject: null }],
|
||||
},
|
||||
{
|
||||
destination: '/plugins/upload',
|
||||
icon: 'cloud-upload-alt',
|
||||
isDisplayed: false,
|
||||
label: { defaultMessage: 'Media Library', id: 'upload.plugin.name' },
|
||||
permissions: [
|
||||
{ action: 'plugin::upload.read', subject: null },
|
||||
{ action: 'plugin::upload.assets.create', subject: null },
|
||||
{ action: 'plugin::upload.assets.update', subject: null },
|
||||
],
|
||||
},
|
||||
];
|
||||
const actual = await getPluginSectionLinks(userPermissions, pluginsSectionRawLinks);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
@ -9,10 +9,10 @@ import React from 'react';
|
||||
import { SingleSelect, SingleSelectOption } from '@strapi/design-system';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import useLocalesProvider from '../../../components/LocalesProvider/useLocalesProvider';
|
||||
import { useLocales } from '../../../components/LanguageProvider';
|
||||
|
||||
const LocaleToggle = () => {
|
||||
const { changeLocale, localeNames } = useLocalesProvider();
|
||||
const { changeLocale, localeNames } = useLocales();
|
||||
const { locale } = useIntl();
|
||||
|
||||
return (
|
||||
|
@ -13,8 +13,9 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import GuidedTourModal from '../../components/GuidedTour/Modal';
|
||||
import LeftMenu from '../../components/LeftMenu';
|
||||
import { useConfiguration, useMenu } from '../../hooks';
|
||||
import { LeftMenu } from '../../components/LeftMenu';
|
||||
import { useConfiguration } from '../../hooks/useConfiguration';
|
||||
import { useMenu } from '../../hooks/useMenu';
|
||||
import AppLayout from '../../layouts/AppLayout';
|
||||
import { createRoute } from '../../utils';
|
||||
import { SET_APP_RUNTIME_STATUS } from '../App/constants';
|
||||
|
@ -7,7 +7,7 @@ import { createMemoryHistory } from 'history';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { Router } from 'react-router-dom';
|
||||
|
||||
import { useMenu } from '../../../hooks';
|
||||
import { useMenu } from '../../../hooks/useMenu';
|
||||
import Admin from '../index';
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
@ -34,14 +34,15 @@ jest.mock('@strapi/helper-plugin', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../../hooks', () => ({
|
||||
jest.mock('../../../hooks/useMenu', () => ({
|
||||
useMenu: jest.fn(() => ({ isLoading: true, generalSectionLinks: [], pluginsSectionLinks: [] })),
|
||||
useConfiguration: jest.fn(() => ({ showTutorials: false })),
|
||||
}));
|
||||
|
||||
jest.mock('../../../components/LeftMenu', () => () => {
|
||||
return <div>menu</div>;
|
||||
});
|
||||
jest.mock('../../../components/LeftMenu', () => ({
|
||||
LeftMenu() {
|
||||
return <div>menu</div>;
|
||||
},
|
||||
}));
|
||||
jest.mock('../../HomePage', () => () => {
|
||||
return <div>HomePage</div>;
|
||||
});
|
||||
|
@ -23,7 +23,7 @@ import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import { PrivateRoute } from '../../components/PrivateRoute';
|
||||
import { ADMIN_PERMISSIONS_CE } from '../../constants';
|
||||
import { useConfiguration } from '../../hooks';
|
||||
import { useConfiguration } from '../../hooks/useConfiguration';
|
||||
import { useEnterprise } from '../../hooks/useEnterprise';
|
||||
import { createRoute, makeUniqueRoutes } from '../../utils';
|
||||
import AuthPage from '../AuthPage';
|
||||
|
@ -8,18 +8,6 @@ import { Router } from 'react-router-dom';
|
||||
|
||||
import Oops from '..';
|
||||
|
||||
jest.mock('../../../../../components/LocalesProvider/useLocalesProvider', () => () => ({
|
||||
changeLocale() {},
|
||||
localeNames: { en: 'English' },
|
||||
messages: ['test'],
|
||||
}));
|
||||
jest.mock('../../../../../hooks/useConfiguration', () => ({
|
||||
useConfiguration: () => ({
|
||||
logos: {
|
||||
auth: { custom: 'customAuthLogo.png', default: 'defaultAuthLogo.png' },
|
||||
},
|
||||
}),
|
||||
}));
|
||||
describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
it('should render and match the snapshot', () => {
|
||||
const history = createMemoryHistory();
|
||||
@ -34,7 +22,7 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
);
|
||||
|
||||
expect(container.firstChild).toMatchInlineSnapshot(`
|
||||
.c9 {
|
||||
.c8 {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
display: block;
|
||||
@ -44,20 +32,20 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c21 {
|
||||
.c20 {
|
||||
font-weight: 600;
|
||||
font-size: 2rem;
|
||||
line-height: 1.25;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c22 {
|
||||
.c21 {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c26 {
|
||||
.c25 {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #4945ff;
|
||||
@ -85,12 +73,12 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
.c12 {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 64px;
|
||||
}
|
||||
|
||||
.c15 {
|
||||
.c14 {
|
||||
background: #ffffff;
|
||||
padding-top: 48px;
|
||||
padding-right: 56px;
|
||||
@ -100,12 +88,12 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
box-shadow: 0px 1px 4px rgba(33,33,52,0.1);
|
||||
}
|
||||
|
||||
.c20 {
|
||||
.c19 {
|
||||
padding-top: 24px;
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
.c23 {
|
||||
.c22 {
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
@ -176,7 +164,7 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.c17 {
|
||||
.c16 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
@ -190,7 +178,7 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.c24 {
|
||||
.c23 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
@ -232,21 +220,21 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
box-shadow: #4945ff 0px 0px 0px 2px;
|
||||
}
|
||||
|
||||
.c12 > svg {
|
||||
.c11 > svg {
|
||||
width: 0.375rem;
|
||||
}
|
||||
|
||||
.c12 > svg > path {
|
||||
.c11 > svg > path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
.c9 {
|
||||
-webkit-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.c11 {
|
||||
.c10 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -257,16 +245,11 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.c27[data-state='checked'] .c8 {
|
||||
font-weight: bold;
|
||||
color: #4945ff;
|
||||
}
|
||||
|
||||
.c14:focus-visible {
|
||||
.c13:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c25 {
|
||||
.c24 {
|
||||
display: -webkit-inline-box;
|
||||
display: -webkit-inline-flex;
|
||||
display: -ms-inline-flexbox;
|
||||
@ -282,23 +265,23 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c25 svg {
|
||||
.c24 svg {
|
||||
font-size: 0.625rem;
|
||||
}
|
||||
|
||||
.c25 svg path {
|
||||
.c24 svg path {
|
||||
fill: #4945ff;
|
||||
}
|
||||
|
||||
.c25:hover {
|
||||
.c24:hover {
|
||||
color: #7b79ff;
|
||||
}
|
||||
|
||||
.c25:active {
|
||||
.c24:active {
|
||||
color: #271fe0;
|
||||
}
|
||||
|
||||
.c25:after {
|
||||
.c24:after {
|
||||
-webkit-transition-property: all;
|
||||
transition-property: all;
|
||||
-webkit-transition-duration: 0.2s;
|
||||
@ -313,11 +296,11 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.c25:focus-visible {
|
||||
.c24:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c25:focus-visible:after {
|
||||
.c24:focus-visible:after {
|
||||
border-radius: 8px;
|
||||
content: '';
|
||||
position: absolute;
|
||||
@ -328,16 +311,16 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
border: 2px solid #4945ff;
|
||||
}
|
||||
|
||||
.c19 {
|
||||
.c18 {
|
||||
height: 4.5rem;
|
||||
}
|
||||
|
||||
.c16 {
|
||||
.c15 {
|
||||
margin: 0 auto;
|
||||
width: 552px;
|
||||
}
|
||||
|
||||
.c18 {
|
||||
.c17 {
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
@ -373,13 +356,11 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
class="c6 c7"
|
||||
>
|
||||
<span
|
||||
class="c8 c9 c10"
|
||||
class="c8 c9"
|
||||
>
|
||||
<span
|
||||
class="c11"
|
||||
>
|
||||
English
|
||||
</span>
|
||||
class="c10"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
@ -387,7 +368,7 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="c12"
|
||||
class="c11"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
@ -411,45 +392,45 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
</div>
|
||||
</header>
|
||||
<div
|
||||
class="c13"
|
||||
class="c12"
|
||||
>
|
||||
<main
|
||||
aria-labelledby="main-content-title"
|
||||
class="c14"
|
||||
class="c13"
|
||||
id="main-content"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="c15 c16"
|
||||
class="c14 c15"
|
||||
>
|
||||
<div
|
||||
class="c17 c18"
|
||||
class="c16 c17"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="c19"
|
||||
src="customAuthLogo.png"
|
||||
class="c18"
|
||||
src=""
|
||||
/>
|
||||
<div
|
||||
class="c20"
|
||||
class="c19"
|
||||
>
|
||||
<h1
|
||||
class="c8 c21"
|
||||
class="c20"
|
||||
>
|
||||
Oops...
|
||||
</h1>
|
||||
</div>
|
||||
<span
|
||||
class="c8 c22"
|
||||
class="c21"
|
||||
>
|
||||
Your account has been suspended.
|
||||
</span>
|
||||
<div
|
||||
class="c23"
|
||||
class="c22"
|
||||
>
|
||||
<span
|
||||
class="c8 c22"
|
||||
class="c21"
|
||||
>
|
||||
If this is a mistake, please contact your administrator.
|
||||
</span>
|
||||
@ -457,17 +438,17 @@ describe('ADMIN | PAGES | AUTH | Oops', () => {
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c24"
|
||||
class="c23"
|
||||
>
|
||||
<div
|
||||
class="c23"
|
||||
class="c22"
|
||||
>
|
||||
<a
|
||||
class="c25"
|
||||
class="c24"
|
||||
href="/auth/login"
|
||||
>
|
||||
<span
|
||||
class="c8 c26"
|
||||
class="c25"
|
||||
>
|
||||
Sign in
|
||||
</span>
|
||||
|
@ -16,8 +16,6 @@ jest.mock('../../../../../hooks/useConfiguration', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../components/LocalesProvider/useLocalesProvider');
|
||||
|
||||
describe('AUTH | Register', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -9,18 +9,6 @@ import * as yup from 'yup';
|
||||
|
||||
import ResetPassword from '..';
|
||||
|
||||
jest.mock('../../../../../components/LocalesProvider/useLocalesProvider', () => () => ({
|
||||
changeLocale() {},
|
||||
localeNames: { en: 'English' },
|
||||
messages: ['test'],
|
||||
}));
|
||||
jest.mock('../../../../../hooks/useConfiguration', () => ({
|
||||
useConfiguration: () => ({
|
||||
logos: {
|
||||
auth: { custom: 'customAuthLogo.png', default: 'defaultAuthLogo.png' },
|
||||
},
|
||||
}),
|
||||
}));
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
useNotification: () => jest.fn({}),
|
||||
@ -498,11 +486,6 @@ describe('ADMIN | PAGES | AUTH | ResetPassword', () => {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.c47[data-state='checked'] .c8 {
|
||||
font-weight: bold;
|
||||
color: #4945ff;
|
||||
}
|
||||
|
||||
.c29 {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
@ -697,9 +680,7 @@ describe('ADMIN | PAGES | AUTH | ResetPassword', () => {
|
||||
>
|
||||
<span
|
||||
class="c11"
|
||||
>
|
||||
English
|
||||
</span>
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
@ -753,7 +734,7 @@ describe('ADMIN | PAGES | AUTH | ResetPassword', () => {
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="c19"
|
||||
src="customAuthLogo.png"
|
||||
src=""
|
||||
/>
|
||||
<div
|
||||
class="c20"
|
||||
|
@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
|
||||
import { Redirect, useHistory, useRouteMatch } from 'react-router-dom';
|
||||
|
||||
import persistStateToLocaleStorage from '../../components/GuidedTour/utils/persistStateToLocaleStorage';
|
||||
import useLocalesProvider from '../../components/LocalesProvider/useLocalesProvider';
|
||||
import { useLocales } from '../../components/LanguageProvider';
|
||||
import { useEnterprise } from '../../hooks/useEnterprise';
|
||||
import formatAPIErrors from '../../utils/formatAPIErrors';
|
||||
|
||||
@ -23,7 +23,7 @@ const AuthPage = ({ hasAdmin, setHasAdmin }) => {
|
||||
push,
|
||||
location: { search },
|
||||
} = useHistory();
|
||||
const { changeLocale } = useLocalesProvider();
|
||||
const { changeLocale } = useLocales();
|
||||
const { setSkipped } = useGuidedTour();
|
||||
const { trackUsage } = useTracking();
|
||||
const {
|
||||
|
@ -26,7 +26,7 @@ import { Helmet } from 'react-helmet';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
||||
|
||||
import useLocalesProvider from '../../components/LocalesProvider/useLocalesProvider';
|
||||
import { useLocales } from '../../components/LanguageProvider';
|
||||
import { useThemeToggle } from '../../hooks/useThemeToggle';
|
||||
import { getFullName } from '../../utils';
|
||||
|
||||
@ -36,7 +36,7 @@ import UserInfo from './components/UserInfo';
|
||||
import schema from './utils/schema';
|
||||
|
||||
const ProfilePage = () => {
|
||||
const { changeLocale, localeNames } = useLocalesProvider();
|
||||
const { changeLocale, localeNames } = useLocales();
|
||||
const { setUserDisplayName } = useAppInfo();
|
||||
const queryClient = useQueryClient();
|
||||
const { formatMessage } = useIntl();
|
||||
|
@ -5,8 +5,6 @@ import { rest } from 'msw';
|
||||
|
||||
import ProfilePage from '../index';
|
||||
|
||||
jest.mock('../../../components/LocalesProvider/useLocalesProvider');
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
useFocusWhenNavigate: jest.fn(),
|
||||
|
@ -5,7 +5,7 @@ import { useTracking } from '@strapi/helper-plugin';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import { useConfiguration } from '../../../../../../hooks';
|
||||
import { useConfiguration } from '../../../../../../hooks/useConfiguration';
|
||||
import { DIMENSION, SIZE } from '../../utils/constants';
|
||||
import LogoInput from '../LogoInput';
|
||||
|
||||
|
@ -25,7 +25,7 @@ const PROJECT_SETTINGS_DATA_FIXTURES = {
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('../../../../../../../hooks', () => ({
|
||||
jest.mock('../../../../../../../hooks/useConfiguration', () => ({
|
||||
useConfiguration: jest.fn(() => ({
|
||||
logos: {
|
||||
menu: { custom: 'customMenuLogo.png', default: 'defaultMenuLogo.png' },
|
||||
|
@ -29,7 +29,7 @@ import { useIntl } from 'react-intl';
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { useConfiguration } from '../../../../hooks';
|
||||
import { useConfiguration } from '../../../../hooks/useConfiguration';
|
||||
import { useEnterprise } from '../../../../hooks/useEnterprise';
|
||||
import { selectAdminPermissions } from '../../../App/selectors';
|
||||
|
||||
|
@ -16,7 +16,7 @@ jest.mock('@strapi/helper-plugin', () => ({
|
||||
useRBAC: jest.fn(() => ({ allowedActions: { canRead: true, canUpdate: true } })),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../hooks', () => ({
|
||||
jest.mock('../../../../../hooks/useConfiguration', () => ({
|
||||
useConfiguration: jest.fn(() => ({
|
||||
logos: {
|
||||
menu: { custom: 'customMenuLogo.png', default: 'defaultMenuLogo.png' },
|
||||
|
@ -7,7 +7,7 @@ import { createMemoryHistory } from 'history';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { Router } from 'react-router-dom';
|
||||
|
||||
import LanguageProvider from '../../../../../../../../components/LanguageProvider';
|
||||
import { LanguageProvider } from '../../../../../../../../components/LanguageProvider';
|
||||
import en from '../../../../../../../../translations/en.json';
|
||||
import WebhookForm from '../index';
|
||||
|
||||
|
@ -9,19 +9,6 @@ import { Router } from 'react-router-dom';
|
||||
|
||||
import UseCasePage from '../index';
|
||||
|
||||
jest.mock('../../../components/LocalesProvider/useLocalesProvider', () => () => ({
|
||||
changeLocale() {},
|
||||
localeNames: { en: 'English' },
|
||||
messages: ['test'],
|
||||
}));
|
||||
jest.mock('../../../hooks/useConfiguration', () => ({
|
||||
useConfiguration: () => ({
|
||||
logos: {
|
||||
auth: { custom: 'customAuthLogo.png', default: 'defaultAuthLogo.png' },
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
useNotification: jest.fn(),
|
||||
@ -590,9 +577,7 @@ describe('Admin | UseCasePage', () => {
|
||||
>
|
||||
<span
|
||||
class="c11"
|
||||
>
|
||||
English
|
||||
</span>
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
@ -643,7 +628,7 @@ describe('Admin | UseCasePage', () => {
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="c19"
|
||||
src="customAuthLogo.png"
|
||||
src=""
|
||||
/>
|
||||
<div
|
||||
class="c20"
|
||||
|
@ -1,2 +1 @@
|
||||
export { default as useAdminProvider } from './useAdminProvider';
|
||||
export { default as useInjectionZone } from './useInjectionZone';
|
||||
|
@ -1,11 +0,0 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AdminContext } from '../../../contexts';
|
||||
|
||||
const useAdminProvider = () => {
|
||||
const context = useContext(AdminContext);
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
export default useAdminProvider;
|
@ -1,7 +1,7 @@
|
||||
import useAdminProvider from '../useAdminProvider';
|
||||
import { useAdmin } from '../../../contexts/admin';
|
||||
|
||||
const useInjectionZone = (area) => {
|
||||
const { getAdminInjectedComponents } = useAdminProvider();
|
||||
const { getAdminInjectedComponents } = useAdmin();
|
||||
|
||||
const [moduleName, page, position] = area.split('.');
|
||||
|
||||
|
@ -23,8 +23,7 @@ import { createStore } from 'redux';
|
||||
|
||||
// @ts-expect-error – no types yet.
|
||||
import ModelsContext from '../src/content-manager/contexts/ModelsContext';
|
||||
// @ts-expect-error – no types yet.
|
||||
import AdminContext from '../src/contexts/Admin';
|
||||
import { AdminContext } from '../src/contexts/admin';
|
||||
import { ConfigurationContext } from '../src/contexts/configuration';
|
||||
|
||||
import { server } from './server';
|
||||
@ -63,6 +62,7 @@ const Providers = ({ children, initialEntries }: ProvidersProps) => {
|
||||
<NotificationsProvider>
|
||||
<RBACContext.Provider
|
||||
value={{
|
||||
refetchPermissions: jest.fn(),
|
||||
allPermissions: [
|
||||
...fixtures.permissions.allPermissions,
|
||||
{
|
||||
|
5
packages/core/helper-plugin/.npmignore
Normal file
5
packages/core/helper-plugin/.npmignore
Normal file
@ -0,0 +1,5 @@
|
||||
############################
|
||||
# dts
|
||||
############################
|
||||
|
||||
dist/**/*.d.ts.map
|
@ -23,8 +23,8 @@
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"require": "./dist/index.js",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js",
|
||||
"source": "./src/index.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
|
@ -9,12 +9,17 @@ type Permission = domain.permission.Permission;
|
||||
* Context
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
type RBACContextValue = {
|
||||
allPermissions?: Permission[]; // The permissions of the current user.
|
||||
refetchPermissions?: QueryObserverBaseResult<Permission[]>;
|
||||
export type RBACContextValue = {
|
||||
allPermissions: Permission[]; // The permissions of the current user.
|
||||
refetchPermissions: QueryObserverBaseResult<Permission[]>['refetch'];
|
||||
};
|
||||
|
||||
const RBACContext: React.Context<RBACContextValue> = React.createContext({});
|
||||
const RBACContext = React.createContext<RBACContextValue>({
|
||||
allPermissions: [],
|
||||
refetchPermissions: async () => {
|
||||
throw new Error('RBACContext: refetchPermissions() not implemented');
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated Use RBACContext instead.
|
||||
|
@ -1,16 +1,23 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { LinkProps } from 'react-router-dom';
|
||||
|
||||
import { TranslationMessage } from '../types';
|
||||
|
||||
import type { domain } from '@strapi/permissions';
|
||||
|
||||
type Permission = domain.permission.Permission;
|
||||
|
||||
interface MenuItem {
|
||||
interface MenuItem extends Pick<LinkProps, 'to'> {
|
||||
to: string;
|
||||
icon: React.ComponentType;
|
||||
icon: React.ElementType;
|
||||
intlLabel: TranslationMessage;
|
||||
permissions?: Permission[];
|
||||
/**
|
||||
* TODO: add type from the BE for what an Admin Permission looks like –
|
||||
* most likely shared throught the helper plugin...? or duplicated, idm.
|
||||
*/
|
||||
permissions: Permission[];
|
||||
notificationsCount?: number;
|
||||
Component?: React.ComponentType;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ import { IntlProvider } from 'react-intl';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { MemoryRouter, MemoryRouterProps } from 'react-router-dom';
|
||||
|
||||
import { RBACContext } from '../src/features/RBAC';
|
||||
import { RBACContext, RBACContextValue } from '../src/features/RBAC';
|
||||
|
||||
import { server } from './server';
|
||||
|
||||
@ -26,9 +26,10 @@ interface ProvidersProps {
|
||||
}
|
||||
|
||||
const Providers = ({ children, initialEntries }: ProvidersProps) => {
|
||||
const rbacContextValue = React.useMemo(
|
||||
const rbacContextValue: RBACContextValue = React.useMemo(
|
||||
() => ({
|
||||
allPermissions: fixtures.permissions.allPermissions,
|
||||
refetchPermissions: jest.fn(),
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
@ -2,6 +2,7 @@
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["**/*.test.tsx", "**/*.test.ts"],
|
||||
"compilerOptions": {
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist"
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user