Client: Added a notification about how much the quota is exceeded when trying to invite a paid user.

This commit is contained in:
Tatiana Lopaeva 2024-08-08 13:29:43 +03:00
parent 6e5dd2eccf
commit 3094409fa8
5 changed files with 126 additions and 9 deletions

View File

@ -32,7 +32,7 @@ import React, {
useRef,
} from "react";
import { observer, inject } from "mobx-react";
import { withTranslation } from "react-i18next";
import { withTranslation, Trans } from "react-i18next";
import { DeviceType, EmployeeType } from "@docspace/shared/enums";
import { LOADER_TIMEOUT } from "@docspace/shared/constants";
@ -60,6 +60,10 @@ import { Scrollbar } from "@docspace/shared/components/scrollbar";
import InfoBar from "./sub-components/InfoBar";
import InvitePanelLoader from "./sub-components/InvitePanelLoader";
import { Link } from "@docspace/shared/components/link";
import { Text } from "@docspace/shared/components/text";
import { combineUrl } from "@docspace/shared/utils/combineUrl";
import { ColorTheme, ThemeId } from "@docspace/shared/components/color-theme";
const InvitePanel = ({
folders,
@ -81,6 +85,9 @@ const InvitePanel = ({
getUsersList,
filter,
currentDeviceType,
isRoomAdmin,
maxCountManagersByQuota,
invitePaidUsersCount,
}) => {
const [invitePanelIsLoding, setInvitePanelIsLoading] = useState(
roomId !== -1,
@ -245,6 +252,22 @@ const InvitePanel = ({
return () => document.removeEventListener("keyup", onKeyPress);
});
const onClickPayments = () => {
const paymentPageUrl = combineUrl(
"/portal-settings",
"/payments/portal-payments",
);
toastr.clear();
window.DocSpace.navigate(paymentPageUrl);
setInvitePanelOptions({
visible: false,
hideSelector: false,
defaultAccess: 1,
});
};
const onClickSend = async (e) => {
const invitations = inviteItems.map((item) => {
let newItem = {};
@ -289,7 +312,40 @@ const InvitePanel = ({
updateInfoPanelMembers(t);
}
} catch (err) {
toastr.error(err);
let error = err;
if (err?.response?.status === 402) {
error = (
<>
<Text as="span">
{t("Common:PaidUsersExceedsLimit", {
count: maxCountManagersByQuota + invitePaidUsersCount,
limit: maxCountManagersByQuota,
})}
</Text>
&nbsp;
{!isRoomAdmin && (
<Trans
t={t}
i18nKey="ChangeUserPermissions"
ns="Common"
components={{
1: (
<ColorTheme
tag="a"
themeId={ThemeId.Link}
onClick={onClickPayments}
target="_blank"
/>
),
}}
/>
)}
</>
);
}
toastr.error(error);
setIsLoading(false);
} finally {
if (roomId === -1) {
@ -462,6 +518,8 @@ export default inject(
filesStore,
dialogsStore,
infoPanelStore,
authStore,
currentQuotaStore,
}) => {
const { theme, currentDeviceType } = settingsStore;
@ -480,11 +538,16 @@ export default inject(
setInviteItems,
setInvitePanelOptions,
setInviteLanguage,
invitePaidUsersCount,
} = dialogsStore;
const { getFolderInfo, setRoomSecurity, getRoomSecurityInfo, folders } =
filesStore;
const { isRoomAdmin } = authStore;
const { maxCountManagersByQuota } = currentQuotaStore;
return {
folders,
setInviteLanguage,
@ -506,6 +569,9 @@ export default inject(
getUsersList,
filter,
currentDeviceType,
isRoomAdmin,
maxCountManagersByQuota,
invitePaidUsersCount,
};
},
)(

View File

@ -93,7 +93,8 @@ const InviteInput = ({
i18n,
setCultureKey,
standalone,
isPaidUserLimit,
isPaidUserAccess,
setInvitePaidUsersCount,
}) => {
const isPublicRoomType = roomType === RoomsType.PublicRoom;
@ -136,6 +137,8 @@ const InviteInput = ({
if (addresses.length > 1) {
return addresses.map((address) => {
isPaidUserAccess(selectedAccess) && setInvitePaidUsersCount();
return {
email: address.email,
id: uid(),
@ -147,6 +150,8 @@ const InviteInput = ({
});
}
isPaidUserAccess(selectedAccess) && setInvitePaidUsersCount();
return {
email: addresses[0].email,
id: uid(),
@ -591,10 +596,11 @@ export default inject(
inviteItems,
setInviteLanguage,
culture,
setInvitePaidUsersCount,
isPaidUserAccess,
} = dialogsStore;
const { culture: language, standalone } = settingsStore;
const { isPaidUserLimit } = currentQuotaStore;
return {
language,
@ -607,7 +613,8 @@ export default inject(
defaultAccess: invitePanelOptions.defaultAccess,
isOwner,
standalone,
isPaidUserLimit,
isPaidUserAccess,
setInvitePaidUsersCount,
};
},
)(

View File

@ -27,14 +27,16 @@
import InfoEditReactSvgUrl from "PUBLIC_DIR/images/info.edit.react.svg?url";
import AtReactSvgUrl from "PUBLIC_DIR/images/@.react.svg?url";
import AlertSvgUrl from "PUBLIC_DIR/images/icons/12/alert.react.svg?url";
import React, { useState, useEffect } from "react";
import { inject, observer } from "mobx-react";
import { Avatar } from "@docspace/shared/components/avatar";
import { Text } from "@docspace/shared/components/text";
import { parseAddresses } from "@docspace/shared/utils";
import { getAccessOptions } from "../utils";
import { getUserTypeLabel } from "@docspace/shared/utils/common";
import { getAccessOptions } from "../utils";
import {
StyledEditInput,
StyledEditButton,
@ -61,6 +63,8 @@ const Item = ({
setIsOpenItemAccess,
isMobileView,
standalone,
isPaidUserAccess,
setInvitePaidUsersCount,
}) => {
const {
avatar,
@ -162,7 +166,9 @@ const Item = ({
const errors = !!parseErrors.length ? parseErrors : [];
setParseErrors(errors);
changeInviteItem({ id, email: value, errors }).then(() => errorsInList());
changeInviteItem({ id, email: value, errors, access }).then(() =>
errorsInList(),
);
};
const changeValue = (e) => {
@ -176,6 +182,8 @@ const Item = ({
const removeItem = () => {
const newItems = inviteItems.filter((item) => item.id !== id);
if (isPaidUserAccess(item.access)) setInvitePaidUsersCount(-1);
setInviteItems(newItems);
};
@ -279,4 +287,11 @@ const Item = ({
);
};
export default Item;
export default inject(({ dialogsStore }) => {
const { isPaidUserAccess, setInvitePaidUsersCount } = dialogsStore;
return {
isPaidUserAccess,
setInvitePaidUsersCount,
};
})(observer(Item));

View File

@ -26,6 +26,7 @@
import { getNewFiles } from "@docspace/shared/api/files";
import {
EmployeeType,
FilesSelectorFilterTypes,
FilterType,
ShareAccessRights,
@ -128,6 +129,8 @@ class DialogsStore {
file: null,
};
invitePaidUsersCount = 0;
constructor(
authStore,
treeFoldersStore,
@ -433,10 +436,34 @@ class DialogsStore {
this.inviteItems = inviteItems;
};
setInvitePaidUsersCount = (modifier = 1) => {
this.invitePaidUsersCount = this.invitePaidUsersCount + modifier;
};
isPaidUserAccess = (selectedAccess) => {
return (
selectedAccess === EmployeeType.Admin ||
selectedAccess === EmployeeType.Collaborator ||
selectedAccess === EmployeeType.User
);
};
changeInviteItem = async (item) =>
runInAction(() => {
const index = this.inviteItems.findIndex((iItem) => iItem.id === item.id);
const isPrevAccessPaid = this.isPaidUserAccess(
this.inviteItems[index].access,
);
const isCurrAccessPaid = this.isPaidUserAccess(item.access);
let modifier = 0;
if (isPrevAccessPaid && !isCurrAccessPaid) modifier = -1;
if (!isPrevAccessPaid && isCurrAccessPaid) modifier = 1;
this.setInvitePaidUsersCount(modifier);
this.inviteItems[index] = { ...this.inviteItems[index], ...item };
});

View File

@ -38,6 +38,7 @@
"Bytes": "bytes",
"CancelButton": "Cancel",
"ChangeButton": "Change",
"ChangeUserPermissions": "Change user permissions to the invited people or <1>upgrade your tariff plan</1>.",
"ChangeQuota": "Change quota",
"ChangesSavedSuccessfully": "Changes saved successfully",
"ChooseFromTemplates": "Choose from Templates",
@ -320,6 +321,7 @@
"Owner": "Owner",
"PageOfTotalPage": "{{page}} of {{totalPage}}",
"Paid": "Paid",
"PaidUsersExceedsLimit": "The number of admins/power users exceeds the limit: {{count}} / {{limit}}.",
"Password": "Password",
"PasswordLimitDigits": "digits",
"PasswordLimitMessage": "Password must contain",