diff --git a/packages/client/src/components/panels/InvitePanel/index.js b/packages/client/src/components/panels/InvitePanel/index.js index 7633df58a1..da952601bc 100644 --- a/packages/client/src/components/panels/InvitePanel/index.js +++ b/packages/client/src/components/panels/InvitePanel/index.js @@ -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 = ( + <> + + {t("Common:PaidUsersExceedsLimit", { + count: maxCountManagersByQuota + invitePaidUsersCount, + limit: maxCountManagersByQuota, + })} + +   + {!isRoomAdmin && ( + + ), + }} + /> + )} + + ); + } + + 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, }; }, )( diff --git a/packages/client/src/components/panels/InvitePanel/sub-components/InviteInput.js b/packages/client/src/components/panels/InvitePanel/sub-components/InviteInput.js index b810807b06..8fa310419f 100644 --- a/packages/client/src/components/panels/InvitePanel/sub-components/InviteInput.js +++ b/packages/client/src/components/panels/InvitePanel/sub-components/InviteInput.js @@ -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, }; }, )( diff --git a/packages/client/src/components/panels/InvitePanel/sub-components/Item.js b/packages/client/src/components/panels/InvitePanel/sub-components/Item.js index 1bccecb7b6..4a27bf2003 100644 --- a/packages/client/src/components/panels/InvitePanel/sub-components/Item.js +++ b/packages/client/src/components/panels/InvitePanel/sub-components/Item.js @@ -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)); diff --git a/packages/client/src/store/DialogsStore.js b/packages/client/src/store/DialogsStore.js index a642cfe6a8..26526d1971 100644 --- a/packages/client/src/store/DialogsStore.js +++ b/packages/client/src/store/DialogsStore.js @@ -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 }; }); diff --git a/public/locales/en/Common.json b/public/locales/en/Common.json index 61acc3af18..21442aee9c 100644 --- a/public/locales/en/Common.json +++ b/public/locales/en/Common.json @@ -38,6 +38,7 @@ "Bytes": "bytes", "CancelButton": "Cancel", "ChangeButton": "Change", + "ChangeUserPermissions": "Change user permissions to the invited people or <1>upgrade your tariff plan.", "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",