Merge pull request #15738 from strapi/chore/ee-seats-fe-review

This commit is contained in:
Josh 2023-02-08 11:26:51 +00:00 committed by GitHub
commit 0a42f13814
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 122 additions and 211 deletions

View File

@ -9,7 +9,6 @@ import {
} from '@strapi/helper-plugin';
import { useQueries } from 'react-query';
import get from 'lodash/get';
import useLicenseLimitInfos from 'ee_else_ce/hooks/useLicenseLimitInfos';
import packageJSON from '../../../../package.json';
import { useConfigurations } from '../../hooks';
import PluginsInitializer from '../PluginsInitializer';
@ -73,10 +72,6 @@ const AuthenticatedApp = () => {
}
}, [userRoles, appInfos]);
// This hook fetches license allowances in 'ee' mode and stores data in redux for global access.
// It does nothing in 'ce'.
useLicenseLimitInfos();
useEffect(() => {
const getUserId = async () => {
const userId = await hashAdminUserEmail(userInfo);

View File

@ -52,11 +52,6 @@ jest.mock('../../RBACProvider', () => {
return Compo;
});
jest.mock('ee_else_ce/hooks/useLicenseLimitInfos', () => ({
__esModule: true,
default: jest.fn(),
}));
const queryClient = new QueryClient({
defaultOptions: {
queries: {

View File

@ -10,5 +10,5 @@ export { default as usePermissionsDataManager } from './usePermissionsDataManage
export { default as useReleaseNotification } from './useReleaseNotification';
export { default as useThemeToggle } from './useThemeToggle';
export { default as useRegenerate } from './useRegenerate';
export { default as useLicenseLimitInfos } from './useLicenseLimitInfos';
export { default as useLicenseLimit } from './useLicenseLimits';
export { default as useLicenseLimitNotification } from './useLicenseLimitNotification';

View File

@ -1,5 +0,0 @@
const useLicenseLimitInfos = () => {
return null;
};
export default useLicenseLimitInfos;

View File

@ -0,0 +1,3 @@
const useLicenseLimits = () => {};
export default useLicenseLimits;

View File

@ -101,13 +101,11 @@ const ListPage = () => {
const isLoading =
(status !== 'success' && status !== 'error') || (status === 'success' && isFetching);
const createAction = canCreate ? <CreateAction onClick={handleToggle} /> : undefined;
return (
<Main aria-busy={isLoading}>
<SettingsPageTitle name="Users" />
<HeaderLayout
primaryAction={createAction}
primaryAction={canCreate && <CreateAction onClick={handleToggle} />}
title={title}
subtitle={formatMessage({
id: 'Settings.permissions.users.listview.header.subtitle',

View File

@ -114,8 +114,9 @@
"Settings.application.get-help": "Get help",
"Settings.application.link-pricing": "See all pricing plans",
"Settings.application.link-upgrade": "Upgrade your admin panel",
"Settings.application.ee.link-contact-sales": "Contact sales",
"Settings.application.ee.link-add-seats": "Add seats",
"Settings.application.ee.admin-seats.count": "{currentUserCount}/{permittedSeats}",
"Settings.application.ee.admin-seats.at-limit-tooltip": "At limit: add seats to invite more users",
"Settings.application.ee.admin-seats.add-seats": "{isHostedOnStrapiCloud, select, true {Add seats} false {Contact sales}}",
"Settings.application.node-version": "node version",
"Settings.application.strapi-version": "strapi version",
"Settings.application.strapiVersion": "strapi version",
@ -845,10 +846,8 @@
"notification.version.update.message": "A new version of Strapi is available!",
"notification.warning.title": "Warning:",
"notification.warning.404": "404 - Not found",
"notification.ee.warning.over-seat-limit": "Add seats to re-enable Users. If you already did it but it's not reflected in Strapi yet, make sure to restart your app.",
"notification.ee.warning.over-seat-limit.title": "Over seat limit ({currentUserCount}/{permittedSeats})",
"notification.ee.warning.at-seat-limit": "Add seats to invite more Users. If you already did it but it's not reflected in Strapi yet, make sure to restart your app.",
"notification.ee.warning.at-seat-limit.title": "At seat limit ({currentUserCount}/{permittedSeats})",
"notification.ee.warning.over-.message": "Add seats to {licenseLimitStatus, select, OVER_LIMIT {invite} AT_LIMIT {re-enable}} Users. If you already did it but it's not reflected in Strapi yet, make sure to restart your app.",
"notification.ee.warning.at-seat-limit.title": "{licenseLimitStatus, select, OVER_LIMIT {Over} AT_LIMIT {At}} seat limit ({currentUserCount}/{permittedSeats})",
"or": "OR",
"request.error.model.unknown": "This model doesn't exist",
"skipToContent": "Skip to content",

View File

@ -1,4 +1,4 @@
// eslint-disable-next-line import/prefer-default-export
export { default as useAuthProviders } from './useAuthProviders';
export { default as useLicenseLimitNotification } from './useLicenseLimitNotification';
export { default as useLicenseLimitInfos } from './useLicenseLimitInfos';
export { default as useLicenseLimits } from './useLicenseLimits';

View File

@ -1,70 +0,0 @@
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useFetchClient } from '@strapi/helper-plugin';
import { useQuery } from 'react-query';
import { produce } from 'immer';
import { useInjectReducer } from '../../../../admin/src/hooks/useInjectReducer';
const NS = 'StrapiAdmin/ee_license-info';
const ACTION_EE_LICENSE_INFO_SET_DATA = 'StrapiAdmin/EE_LICENSE_INFO_GET_DATA';
const initalState = {
serverState: {
currentUserCount: null,
permittedSeats: null,
shouldNotify: false,
licenseLimitStatus: null,
isHostedOnStrapiCloud: false,
licenseType: null,
},
};
const reducer = (state = initalState, action) =>
/* eslint-disable-next-line consistent-return */
produce(state, (draft) => {
switch (action.type) {
case ACTION_EE_LICENSE_INFO_SET_DATA: {
draft.serverState = action.payload;
break;
}
default:
return draft;
}
});
const actionSetData = (payload) => {
return {
type: ACTION_EE_LICENSE_INFO_SET_DATA,
payload,
};
};
const useLicenseLimitInfos = () => {
const instance = useFetchClient();
const fetchLicenseLimitInfo = async () => {
const {
data: { data },
} = await instance.get('/admin/license-limit-information');
return data;
};
const { data, status } = useQuery('license-limit-info', fetchLicenseLimitInfo);
const state = useSelector((state) => state?.[NS] ?? initalState);
const dispatch = useDispatch();
useInjectReducer(NS, reducer);
useEffect(() => {
if (status === 'success' && data) {
dispatch(actionSetData(data));
}
}, [data, status, dispatch]);
return state.serverState;
};
export default useLicenseLimitInfos;

View File

@ -4,110 +4,76 @@
*
*/
import { useEffect } from 'react';
import { useIntl } from 'react-intl';
import { useLocation } from 'react-router';
import { useNotification } from '@strapi/helper-plugin';
import useLicenseLimitInfos from '../useLicenseLimitInfos';
import useLicenseLimits from '../useLicenseLimits';
const notificationBody = (
currentUserCount,
permittedSeats,
licenseLimitStatus,
isHostedOnStrapiCloud
) => {
let notification = {};
const linkURL = isHostedOnStrapiCloud
? 'https://cloud.strapi.io/profile/billing'
: 'https://strapi.chargebeeportal.com/portal/v2/login?forward=portal_main';
const linkLabel = isHostedOnStrapiCloud ? 'ADD SEATS' : 'CONTACT SALES';
const STORAGE_KEY_PREFIX = 'strapi-notification-seat-limit-disabled';
if (licenseLimitStatus === 'OVER_LIMIT') {
notification = {
type: 'warning',
message: {
id: 'notification.ee.warning.over-seat-limit',
defaultMessage:
"Add seats to re-enable users. If you already did it but it's not reflected in Strapi yet, make sure to restart your app.",
},
// Title is translated in the Notification component
title: {
id: 'notification.ee.warning.over-seat-limit.title',
defaultMessage: 'Over seat limit ({currentUserCount}/{permittedSeats})',
values: { currentUserCount, permittedSeats },
},
link: {
url: linkURL,
label: linkLabel,
},
blockTransition: true,
};
return notification;
}
if (licenseLimitStatus === 'AT_LIMIT') {
notification = {
type: 'softWarning',
message: {
id: 'notification.ee.warning.at-seat-limit',
defaultMessage:
"Add seats to re-enable users. If you already did it but it's not reflected in Strapi yet, make sure to restart your app.",
},
title: {
id: 'notification.ee.warning.at-seat-limit.title',
defaultMessage: 'At seat limit ({currentUserCount}/{permittedSeats})',
values: { currentUserCount, permittedSeats },
},
link: {
url: linkURL,
label: linkLabel,
},
blockTransition: true,
};
return notification;
}
return notification;
};
const shouldDisplayNotification = (pathname) => {
const isLocation = (string) => pathname.includes(string);
const shownInSession = window.sessionStorage.getItem(`notification-${pathname}`);
if (isLocation('/') && shownInSession) {
return false;
}
if (isLocation('users') && shownInSession) {
return false;
}
return true;
};
const BILLING_STRAPI_CLOUD_URL = 'https://cloud.strapi.io/profile/billing';
const BILLING_SELF_HOSTED_URL =
'https://strapi.chargebeeportal.com/portal/v2/login?forward=portal_main';
const useLicenseLimitNotification = () => {
let licenseLimitInfos = useLicenseLimitInfos();
const { formatMessage } = useIntl();
let { license } = useLicenseLimits();
const toggleNotification = useNotification();
const location = useLocation();
const { pathname } = useLocation();
// eslint-disable-next-line consistent-return
useEffect(() => {
if (!licenseLimitInfos || !licenseLimitInfos.permittedSeats) return;
if (!shouldDisplayNotification(location.pathname)) return;
if (!license?.data) {
return;
}
const { currentUserCount, permittedSeats, licenseLimitStatus, isHostedOnStrapiCloud } =
licenseLimitInfos;
const notification = notificationBody(
currentUserCount,
permittedSeats,
licenseLimitStatus,
isHostedOnStrapiCloud
);
const onClose = () => window.sessionStorage.setItem(`notification-${location.pathname}`, true);
toggleNotification({ ...notification, onClose });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
license?.data ?? {};
const shouldDisplayNotification =
permittedSeats &&
window.sessionStorage.getItem(`${STORAGE_KEY_PREFIX}-${pathname}`) &&
['/', '/users'].every(pathname.includes);
if (shouldDisplayNotification) {
toggleNotification({
type: licenseLimitStatus === 'OVER_LIMIT' ? 'warning' : 'softWarning',
message: formatMessage(
{
id: 'notification.ee.warning.seat-limit.message',
defaultMessage:
"Add seats to invite more Users. If you already did it but it's not reflected in Strapi yet, make sure to restart your app.",
},
{ licenseLimitStatus }
),
title: formatMessage(
{
id: 'notification.ee.warning.seat-limit.title',
defaultMessage: 'Over seat limit ({currentUserCount}/{permittedSeats})',
},
{
licenseLimitStatus,
currentUserCount,
permittedSeats,
}
),
link: {
url: isHostedOnStrapiCloud ? BILLING_STRAPI_CLOUD_URL : BILLING_SELF_HOSTED_URL,
label: formatMessage(
{
id: 'notification.ee.warning.seat-limit.link',
defaultMessage:
'{isHostedOnStrapiCloud, select, true {ADD SEATS} other {CONTACT SALES}}',
},
{ isHostedOnStrapiCloud }
),
},
blockTransition: true,
onClose() {
window.sessionStorage.setItem(`${STORAGE_KEY_PREFIX}-${pathname}`, true);
},
});
}
}, [toggleNotification, license.data, pathname, formatMessage]);
};
export default useLicenseLimitNotification;

View File

@ -0,0 +1,19 @@
import { useFetchClient } from '@strapi/helper-plugin';
import { useQuery } from 'react-query';
const useLicenseLimits = () => {
const { get } = useFetchClient();
const fetchLicenseLimitInfo = async () => {
const {
data: { data },
} = await get('/admin/license-limit-information');
return data;
};
const license = useQuery(['ee', 'license-limit-info'], fetchLicenseLimitInfo);
return { license };
};
export default useLicenseLimits;

View File

@ -9,19 +9,20 @@ import { Stack } from '@strapi/design-system/Stack';
import { Link } from '@strapi/design-system/v2/Link';
import ExternalLink from '@strapi/icons/ExternalLink';
import ExclamationMarkCircle from '@strapi/icons/ExclamationMarkCircle';
import { useLicenseLimitInfos } from '../../../../../../hooks';
import { pxToRem } from '@strapi/helper-plugin';
import { useLicenseLimits } from '../../../../../../hooks';
const BILLING_STRAPI_CLOUD_URL = 'https://cloud.strapi.io/profile/billing';
const BILLING_SELF_HOSTED_URL = 'https://share.hsforms.com/1WhxtbTkJSUmfqqEuv4pwuA43qp4';
const AdminSeatInfo = () => {
const { formatMessage } = useIntl();
const licenseLimitInfos = useLicenseLimitInfos();
const { license } = useLicenseLimits();
const { licenseLimitStatus, currentUserCount, permittedSeats, isHostedOnStrapiCloud } =
licenseLimitInfos;
const linkURL = isHostedOnStrapiCloud
? 'https://cloud.strapi.io/profile/billing'
: 'https://share.hsforms.com/1WhxtbTkJSUmfqqEuv4pwuA43qp4';
license?.data ?? {};
if (!permittedSeats) {
return <></>;
return null;
}
return (
@ -39,34 +40,44 @@ const AdminSeatInfo = () => {
textColor={licenseLimitStatus === 'OVER_LIMIT' ? 'danger500' : ''}
fontWeight={licenseLimitStatus === 'OVER_LIMIT' ? 'bold' : ''}
>
{currentUserCount || 'NA'}
{formatMessage(
{
id: 'Settings.application.ee.admin-seats.count',
defaultMessage: '{currentUserCount}/{permittedSeats}',
},
{ permittedSeats, currentUserCount }
)}
</Typography>
<Typography as="p">/</Typography>
<Typography as="p">{permittedSeats || 'NA'}</Typography>
</Flex>
{licenseLimitStatus === 'AT_LIMIT' && (
<Tooltip
description={formatMessage({
id: 'Settings.application.admin-seats.at-limit-tooltip',
id: 'Settings.application.ee.admin-seats.at-limit-tooltip',
defaultMessage: 'At limit: add seats to invite more users',
})}
>
<Icon
width={`${14 / 16}rem`}
height={`${14 / 16}rem`}
width={`${pxToRem(14)}rem`}
height={`${pxToRem(14)}rem`}
color="danger500"
as={ExclamationMarkCircle}
/>
</Tooltip>
)}
</Stack>
<Link href={linkURL} isExternal endIcon={<ExternalLink />}>
{formatMessage({
id: isHostedOnStrapiCloud
? 'Settings.application.ee.link-add-seats'
: 'Settings.application.ee.link-contact-sales',
defaultMessage: isHostedOnStrapiCloud ? 'Add seats' : 'Contact sales',
})}
<Link
href={isHostedOnStrapiCloud ? BILLING_STRAPI_CLOUD_URL : BILLING_SELF_HOSTED_URL}
isExternal
endIcon={<ExternalLink />}
>
{formatMessage(
{
id: 'Settings.application.ee.admin-seats.add-seats',
defaultMessage:
'{isHostedOnStrapiCloud, select, true {Add seats} false {Contact sales}}',
},
{ isHostedOnStrapiCloud }
)}
</Link>
</GridItem>
);

View File

@ -7,11 +7,11 @@ import { Icon } from '@strapi/design-system/Icon';
import { Stack } from '@strapi/design-system';
import Envelop from '@strapi/icons/Envelop';
import ExclamationMarkCircle from '@strapi/icons/ExclamationMarkCircle';
import { useLicenseLimitInfos } from '../../../../../../hooks';
import { useLicenseLimits } from '../../../../../../hooks';
const CreateAction = ({ onClick }) => {
const { formatMessage } = useIntl();
const licenseLimitInfos = useLicenseLimitInfos();
const licenseLimitInfos = useLicenseLimits();
const { licenseLimitStatus, permittedSeats } = licenseLimitInfos;
return (