@@ -262,7 +273,10 @@ const CreateRoomTemplate = (props) => {
);
};
-export default inject(({ settingsStore }) => {
+export default inject(({ settingsStore, dialogsStore }) => {
const { folderFormValidation } = settingsStore;
- return { folderFormValidation };
+ const { setTemplateAccessSettingsVisible: setAccessSettingsIsVisible } =
+ dialogsStore;
+
+ return { folderFormValidation, setAccessSettingsIsVisible };
})(observer(CreateRoomTemplate));
diff --git a/packages/client/src/components/panels/TemplateAccessSettingsPanel/StyledInvitePanel.js b/packages/client/src/components/panels/TemplateAccessSettingsPanel/StyledInvitePanel.js
new file mode 100644
index 0000000000..ef45d4cc46
--- /dev/null
+++ b/packages/client/src/components/panels/TemplateAccessSettingsPanel/StyledInvitePanel.js
@@ -0,0 +1,553 @@
+// (c) Copyright Ascensio System SIA 2009-2024
+//
+// This program is a free software product.
+// You can redistribute it and/or modify it under the terms
+// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
+// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
+// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
+// any third-party rights.
+//
+// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
+// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
+// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+//
+// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
+//
+// The interactive user interfaces in modified source and object code versions of the Program must
+// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
+//
+// Pursuant to Section 7(b) of the License you must retain the original Product logo when
+// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
+// trademark law for use of our trademarks.
+//
+// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
+// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
+// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+
+import styled, { css } from "styled-components";
+import { Heading } from "@docspace/shared/components/heading";
+import { TextInput } from "@docspace/shared/components/text-input";
+import { ComboBox } from "@docspace/shared/components/combobox";
+import { Box } from "@docspace/shared/components/box";
+import { DropDown } from "@docspace/shared/components/drop-down";
+import { Text } from "@docspace/shared/components/text";
+import { Button } from "@docspace/shared/components/button";
+import { HelpButton } from "@docspace/shared/components/help-button";
+import { Link } from "@docspace/shared/components/link";
+import { ToggleButton } from "@docspace/shared/components/toggle-button";
+import { mobile, commonIconsStyles } from "@docspace/shared/utils";
+import CheckIcon from "PUBLIC_DIR/images/check.edit.react.svg";
+import CrossIcon from "PUBLIC_DIR/images/cross.edit.react.svg";
+import CrossIconMobile from "PUBLIC_DIR/images/cross.react.svg";
+import DeleteIcon from "PUBLIC_DIR/images/mobile.actions.remove.react.svg";
+import { isMobile, desktop, commonInputStyles } from "@docspace/shared/utils";
+import Base from "@docspace/shared/themes/base";
+
+const fillAvailableWidth = css`
+ width: 100%;
+ width: -moz-available;
+ width: -webkit-fill-available;
+ width: fill-available;
+`;
+
+const StyledInvitePanel = styled.div`
+ @media ${mobile} {
+ user-select: none;
+ height: auto;
+ width: auto;
+ background: ${(props) => props.theme.infoPanel.blurColor};
+ backdrop-filter: blur(3px);
+ z-index: 309;
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+
+ .invite_panel {
+ background-color: ${(props) => props.theme.infoPanel.backgroundColor};
+ border-left: ${(props) =>
+ `1px solid ${props.theme.infoPanel.borderColor}`};
+ position: absolute;
+ border: none;
+ right: 0;
+ bottom: 0;
+ height: calc(100% - 64px);
+ width: 100vw;
+ max-width: 100vw;
+ }
+ }
+
+ .invite-panel-body {
+ height: calc(100% - 55px - 73px);
+
+ .scroll-body {
+ ${(props) =>
+ props.theme.interfaceDirection === "rtl"
+ ? css`
+ padding-left: 0px !important;
+ `
+ : css`
+ padding-right: 0px !important;
+ `}
+
+ @media ${desktop} {
+ width: 480px;
+ min-width: auto !important;
+ }
+ }
+
+ ${(props) =>
+ !props.addUsersPanelVisible &&
+ isMobile() &&
+ props.theme.interfaceDirection !== "rtl" &&
+ css`
+ .trackYVisible {
+ .scroller {
+ margin-right: -20px !important;
+ }
+ }
+ `}
+ }
+`;
+
+const ScrollList = styled.div`
+ width: 100%;
+ height: ${(props) =>
+ props.scrollAllPanelContent && props.isTotalListHeight
+ ? "auto"
+ : props.offsetTop && `calc(100% - ${props.offsetTop}px)`};
+
+ ${!isMobile() &&
+ css`
+ .row-item {
+ width: 448px !important;
+ }
+ `}
+`;
+
+const StyledBlock = styled.div`
+ padding: ${(props) => (props.noPadding ? "0px" : "0 16px")};
+ border-bottom: ${(props) => props.theme.filesPanels.sharing.borderBottom};
+`;
+
+StyledBlock.defaultProps = { theme: Base };
+
+const StyledInviteUserBody = styled.div`
+ display: flex;
+ flex-direction: column;
+ overflow: auto;
+`;
+
+const StyledHeading = styled(Heading)`
+ font-weight: 700;
+ font-size: 21px;
+`;
+
+const StyledSubHeader = styled(Heading)`
+ font-weight: 700;
+ font-size: 16px;
+ padding-left: 16px;
+ padding-right: 16px;
+ margin: 16px 0 8px 0;
+
+ ${(props) =>
+ props.inline &&
+ css`
+ display: inline-flex;
+ align-items: center;
+ gap: 16px;
+ `};
+`;
+
+const StyledDescription = styled(Text)`
+ padding-left: 16px;
+ padding-right: 16px;
+ color: ${(props) =>
+ props.theme.createEditRoomDialog.commonParam.descriptionColor};
+ margin-bottom: 16px;
+
+ font-weight: 400;
+ font-size: 12px;
+ line-height: 16px;
+`;
+
+StyledDescription.defaultProps = { theme: Base };
+
+const StyledRow = styled.div`
+ width: calc(100% - 32px) !important;
+
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+
+ min-height: 41px;
+
+ margin-inline-start: 16px;
+ box-sizing: border-box;
+ border-bottom: none;
+
+ a {
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 16px;
+ }
+
+ .invite-panel_access-selector {
+ margin-inline-start: auto;
+ margin-inline-end: 0;
+
+ ${({ hasWarning }) => hasWarning && `margin-inline-start: 0;`}
+ }
+
+ .warning {
+ margin-inline-start: auto;
+ }
+
+ .combo-button-label {
+ color: ${(props) => props.theme.text.disableColor};
+ }
+ .combo-buttons_expander-icon path {
+ fill: ${(props) => props.theme.text.disableColor};
+ }
+`;
+
+const StyledInviteInput = styled.div`
+ ${fillAvailableWidth}
+
+ margin: 0px 16px;
+
+ .input-link {
+ height: 32px;
+ border: 0px;
+
+ > input {
+ height: 30px;
+ }
+ }
+
+ display: flex;
+ border: 1px solid rgb(208, 213, 218);
+ border-radius: 3px;
+
+ .copy-link-icon {
+ padding: 0;
+
+ &:hover {
+ svg path {
+ fill: ${(props) => props.theme.inputBlock.hoverIconColor} !important;
+ }
+ }
+
+ svg path {
+ fill: ${(props) => props.theme.inputBlock.iconColor} !important;
+ }
+ }
+
+ input[type="search"]::-webkit-search-decoration,
+ input[type="search"]::-webkit-search-cancel-button,
+ input[type="search"]::-webkit-search-results-button,
+ input[type="search"]::-webkit-search-results-decoration {
+ -webkit-appearance: none;
+ appearance: none;
+ }
+
+ .append {
+ display: ${(props) => (props.isShowCross ? "flex" : "none")};
+ align-items: center;
+ padding-right: 8px;
+ cursor: default;
+ }
+
+ ${commonInputStyles}
+
+ :focus-within {
+ border-color: ${(props) => props.theme.inputBlock.borderColor};
+ }
+`;
+
+const StyledAccessSelector = styled.div`
+ ${(props) =>
+ props.theme.interfaceDirection === "rtl"
+ ? css`
+ margin-left: 16px;
+ `
+ : css`
+ margin-right: 16px;
+ `}
+`;
+
+const StyledEditInput = styled(TextInput)`
+ width: 100%;
+`;
+
+const StyledInviteInputContainer = styled.div`
+ position: relative;
+ display: flex;
+ align-items: center;
+ width: 100%;
+ margin-bottom: 20px;
+
+ .header_aside-panel {
+ max-width: 100% !important;
+ }
+`;
+
+const StyledDropDown = styled(DropDown)`
+ ${(props) => props.width && `width: ${props.width}px`};
+
+ .list-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ height: 48px;
+
+ .list-item_content {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+
+ .email-list_avatar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ overflow: hidden;
+ }
+
+ .email-list_add-button {
+ display: flex;
+ margin-left: auto;
+ align-items: center;
+ gap: 4px;
+
+ p {
+ color: #4781d1;
+ }
+
+ svg path {
+ fill: #4781d1;
+ }
+ }
+ }
+`;
+
+const SearchItemText = styled(Text)`
+ line-height: ${({ theme }) =>
+ theme.interfaceDirection === "rtl" ? `20px` : `16px`};
+
+ text-overflow: ellipsis;
+ overflow: hidden;
+ font-size: ${(props) =>
+ props.primary ? "14px" : props.info ? "11px" : "12px"};
+ font-weight: ${(props) => (props.primary || props.info ? "600" : "400")};
+
+ color: ${(props) =>
+ (props.primary && !props.disabled) || props.info
+ ? props.theme.text.color
+ : props.theme.text.emailColor};
+ ${(props) => props.info && `margin-inline-start: auto`}
+`;
+
+SearchItemText.defaultProps = { theme: Base };
+
+const StyledEditButton = styled(Button)`
+ width: 32px;
+ height: 32px;
+ padding: 0px;
+`;
+
+const iconStyles = css`
+ ${commonIconsStyles}
+ path {
+ fill: ${(props) => props.theme.filesEditingWrapper.fill} !important;
+ }
+ :hover {
+ fill: ${(props) => props.theme.filesEditingWrapper.hoverFill} !important;
+ }
+`;
+
+const StyledCheckIcon = styled(CheckIcon)`
+ ${iconStyles}
+`;
+
+StyledCheckIcon.defaultProps = { theme: Base };
+
+const StyledCrossIcon = styled(CrossIcon)`
+ ${iconStyles}
+`;
+
+StyledCrossIcon.defaultProps = { theme: Base };
+
+const StyledDeleteIcon = styled(DeleteIcon)`
+ ${(props) =>
+ props.theme.interfaceDirection === "rtl"
+ ? css`
+ margin-right: auto;
+ `
+ : css`
+ margin-left: auto;
+ `}
+
+ ${iconStyles}
+`;
+
+StyledDeleteIcon.defaultProps = { theme: Base };
+
+const StyledHelpButton = styled(HelpButton)`
+ margin-inline-start: 8px;
+`;
+
+const StyledButtons = styled(Box)`
+ padding: 16px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+
+ position: absolute;
+ bottom: 0px;
+ width: 100%;
+ background: ${(props) => props.theme.filesPanels.sharing.backgroundButtons};
+ border-top: ${(props) => props.theme.filesPanels.sharing.borderTop};
+`;
+
+const StyledLink = styled(Link)`
+ float: ${({ theme }) =>
+ theme.interfaceDirection === "rtl" ? `left` : `right`};
+`;
+
+const ResetLink = styled(Link)`
+ float: ${({ theme }) =>
+ theme.interfaceDirection === "rtl" ? `right` : `left`};
+ padding: 0 16px;
+ margin-bottom: 16px;
+ font-size: 13px;
+ color: ${(props) => props.theme.createEditRoomDialog.commonParam.textColor};
+ font-style: normal;
+ line-height: 15px;
+`;
+
+StyledButtons.defaultProps = { theme: Base };
+
+const StyledToggleButton = styled(ToggleButton)`
+ ${(props) =>
+ props.theme.interfaceDirection === "rtl"
+ ? css`
+ left: 8px;
+ `
+ : css`
+ right: 8px;
+ `}
+ margin-top: -4px;
+`;
+
+const StyledControlContainer = styled.div`
+ width: 17px;
+ height: 17px;
+ position: absolute;
+
+ cursor: pointer;
+
+ align-items: center;
+ justify-content: center;
+ z-index: 450;
+
+ @media ${mobile} {
+ display: flex;
+
+ top: -27px;
+ right: 10px;
+ left: unset;
+ }
+`;
+const StyledInviteLanguage = styled.div`
+ padding-left: 16px;
+ padding-right: 16px;
+ margin-top: -12px;
+ display: flex;
+ align-items: center;
+ justify-content: start;
+ height: 28px;
+ color: ${(props) =>
+ props.theme.createEditRoomDialog.commonParam.descriptionColor};
+ margin-bottom: 4px;
+ font-size: 13px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 20px;
+ .list-link {
+ margin-left: 4px;
+ color: ${(props) => props.theme.createEditRoomDialog.commonParam.textColor};
+ }
+
+ .invitation-language {
+ color: ${(props) =>
+ props.theme.createEditRoomDialog.commonParam.descriptionColor};
+ }
+ .language-combo-box {
+ .combo-button {
+ padding-left: 6px;
+ padding-right: 6px;
+ }
+
+ .combo-buttons_arrow-icon {
+ margin-left: 0px;
+ }
+
+ .combo-button_closed:not(:hover) .combo-button-label {
+ color: ${(props) =>
+ props.theme.createEditRoomDialog.commonParam.descriptionColor};
+ }
+ .combo-button_closed:not(:hover) .combo-buttons_arrow-icon {
+ svg {
+ path {
+ fill: ${(props) =>
+ props.theme.createEditRoomDialog.commonParam.descriptionColor};
+ }
+ }
+ }
+ }
+
+ .language-combo-box-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ }
+`;
+const StyledCrossIconMobile = styled(CrossIconMobile)`
+ width: 17px;
+ height: 17px;
+ z-index: 455;
+ path {
+ fill: ${(props) => props.theme.catalog.control.fill};
+ }
+`;
+
+StyledCrossIcon.defaultProps = { theme: Base };
+export {
+ StyledBlock,
+ StyledHeading,
+ StyledInvitePanel,
+ StyledRow,
+ StyledSubHeader,
+ StyledInviteInput,
+ StyledInviteInputContainer,
+ StyledDropDown,
+ SearchItemText,
+ StyledEditInput,
+ StyledEditButton,
+ StyledCheckIcon,
+ StyledCrossIcon,
+ StyledHelpButton,
+ StyledDeleteIcon,
+ StyledButtons,
+ StyledLink,
+ ResetLink,
+ ScrollList,
+ StyledAccessSelector,
+ StyledToggleButton,
+ StyledDescription,
+ StyledInviteLanguage,
+ StyledControlContainer,
+ StyledCrossIconMobile,
+ StyledInviteUserBody,
+};
diff --git a/packages/client/src/components/panels/TemplateAccessSettingsPanel/index.js b/packages/client/src/components/panels/TemplateAccessSettingsPanel/index.js
new file mode 100644
index 0000000000..e29128ea7a
--- /dev/null
+++ b/packages/client/src/components/panels/TemplateAccessSettingsPanel/index.js
@@ -0,0 +1,309 @@
+// (c) Copyright Ascensio System SIA 2009-2024
+//
+// This program is a free software product.
+// You can redistribute it and/or modify it under the terms
+// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
+// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
+// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
+// any third-party rights.
+//
+// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
+// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
+// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+//
+// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
+//
+// The interactive user interfaces in modified source and object code versions of the Program must
+// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
+//
+// Pursuant to Section 7(b) of the License you must retain the original Product logo when
+// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
+// trademark law for use of our trademarks.
+//
+// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
+// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
+// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+
+import { useEffect, useState, useMemo, useRef } from "react";
+import { observer, inject } from "mobx-react";
+import { withTranslation } from "react-i18next";
+import { DeviceType } from "@docspace/shared/enums";
+import { Backdrop } from "@docspace/shared/components/backdrop";
+import { Aside } from "@docspace/shared/components/aside";
+import { Button } from "@docspace/shared/components/button";
+import { toastr } from "@docspace/shared/components/toast";
+import { Portal } from "@docspace/shared/components/portal";
+import { isDesktop, isMobile, size } from "@docspace/shared/utils";
+
+import {
+ StyledBlock,
+ StyledHeading,
+ StyledInvitePanel,
+ StyledButtons,
+ StyledControlContainer,
+ StyledCrossIconMobile,
+ StyledSubHeader,
+ StyledToggleButton,
+ StyledDescription,
+} from "./StyledInvitePanel";
+
+import ItemsList from "./sub-components/ItemsList";
+import InviteInput from "./sub-components/InviteInput";
+import { Scrollbar } from "@docspace/shared/components/scrollbar";
+import { IconButton } from "@docspace/shared/components/icon-button";
+import ArrowPathReactSvgUrl from "PUBLIC_DIR/images/arrow.path.react.svg?url";
+
+const TemplateAccessSettingsPanel = ({
+ t,
+ visible,
+ setIsVisible,
+ setInfoPanelIsMobileHidden,
+ currentDeviceType,
+}) => {
+ const [isAvailable, setIsAvailable] = useState(false);
+ const [inviteItems, setInviteItems] = useState([]);
+
+ const [hasErrors, setHasErrors] = useState(false);
+
+ const [scrollAllPanelContent, setScrollAllPanelContent] = useState(false);
+ const [addUsersPanelVisible, setAddUsersPanelVisible] = useState(false);
+ const [isMobileView, setIsMobileView] = useState(isMobile());
+
+ const invitePanelBodyRef = useRef();
+
+ const zIndex = 311;
+
+ useEffect(() => {
+ const hasErrors = inviteItems.some((item) => !!item.errors?.length);
+
+ setHasErrors(hasErrors);
+ }, [inviteItems]);
+
+ useEffect(() => {
+ onCheckHeight();
+ window.addEventListener("resize", onCheckHeight);
+ return () => {
+ window.removeEventListener("resize", onCheckHeight);
+ window.removeEventListener("mousedown", onMouseDown);
+ };
+ }, []);
+
+ useEffect(() => {
+ isMobileView && window.addEventListener("mousedown", onMouseDown);
+ }, [isMobileView]);
+
+ const onMouseDown = (e) => {
+ if (e.target.id === "InvitePanelWrapper") onClose();
+ };
+
+ const onCheckHeight = () => {
+ setScrollAllPanelContent(!isDesktop());
+ setIsMobileView(isMobile());
+ };
+
+ const onAvailableChange = () => {
+ setIsAvailable(!isAvailable);
+ };
+
+ const onClose = () => {
+ setInfoPanelIsMobileHidden(false);
+ setIsVisible(false);
+ };
+
+ const onKeyPress = (e) =>
+ (e.key === "Esc" || e.key === "Escape") && onClose();
+
+ useEffect(() => {
+ document.addEventListener("keyup", onKeyPress);
+ return () => document.removeEventListener("keyup", onKeyPress);
+ });
+
+ const roomType = 2; //TODO: Templates
+ // const roomType = -1;
+ const hasInvitedUsers = !!inviteItems.length;
+
+ const bodyInvitePanel = useMemo(() => {
+ return (
+ <>
+
+
+ {t("Files:TemplateAvailable")}
+
+
+
+
+ {t("Files:TemplateAvailableDescription")}
+
+
+
+
{t("Files:AccessToTemplate")}
+ {hasInvitedUsers && (
+
+ )}
+ >
+ );
+ }, [
+ t,
+ roomType,
+ onClose,
+ setHasErrors,
+ scrollAllPanelContent,
+ hasInvitedUsers,
+ invitePanelBodyRef,
+ ]);
+
+ const invitePanelNode = (
+ <>
+
+ console.log("onArrowClick")} //TODO: Templates
+ />
+ {t("Files:AccessSettings")}
+
+ {
+ <>
+ {scrollAllPanelContent ? (
+
+ {bodyInvitePanel}
+
+ ) : (
+ bodyInvitePanel
+ )}
+
+
+
+ >
+ }
+ >
+ );
+
+ const invitePanelComponent = (
+
+ {isMobileView ? (
+
+
+
+
+ {invitePanelNode}
+
+ ) : (
+ <>
+
+
+ >
+ )}
+
+ );
+
+ const renderPortalInvitePanel = () => {
+ const rootElement = document.getElementById("root");
+
+ return (
+
+ );
+ };
+
+ return currentDeviceType === DeviceType.mobile
+ ? renderPortalInvitePanel()
+ : invitePanelComponent;
+};
+
+export default inject(
+ ({ settingsStore, peopleStore, dialogsStore, infoPanelStore }) => {
+ const { theme, currentDeviceType } = settingsStore;
+
+ const { getUsersByQuery } = peopleStore.usersStore;
+ const { setIsMobileHidden: setInfoPanelIsMobileHidden } = infoPanelStore;
+
+ const { templateAccessSettingsVisible, setTemplateAccessSettingsVisible } =
+ dialogsStore;
+
+ return {
+ getUsersByQuery,
+ theme,
+ visible: templateAccessSettingsVisible,
+ setIsVisible: setTemplateAccessSettingsVisible,
+ setInfoPanelIsMobileHidden,
+ currentDeviceType,
+ };
+ },
+)(
+ withTranslation([
+ "InviteDialog",
+ "SharingPanel",
+ "Translations",
+ "Common",
+ "InfoPanel",
+ "PeopleSelector",
+ ])(observer(TemplateAccessSettingsPanel)),
+);
diff --git a/packages/client/src/components/panels/TemplateAccessSettingsPanel/sub-components/InviteInput.js b/packages/client/src/components/panels/TemplateAccessSettingsPanel/sub-components/InviteInput.js
new file mode 100644
index 0000000000..79c1402e24
--- /dev/null
+++ b/packages/client/src/components/panels/TemplateAccessSettingsPanel/sub-components/InviteInput.js
@@ -0,0 +1,371 @@
+// (c) Copyright Ascensio System SIA 2009-2024
+//
+// This program is a free software product.
+// You can redistribute it and/or modify it under the terms
+// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
+// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
+// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
+// any third-party rights.
+//
+// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
+// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
+// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+//
+// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
+//
+// The interactive user interfaces in modified source and object code versions of the Program must
+// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
+//
+// Pursuant to Section 7(b) of the License you must retain the original Product logo when
+// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
+// trademark law for use of our trademarks.
+//
+// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
+// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
+// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+
+import debounce from "lodash.debounce";
+import { inject, observer } from "mobx-react";
+import { withTranslation } from "react-i18next";
+import { useMemo, useState, useCallback, useEffect, useRef } from "react";
+
+import { Avatar } from "@docspace/shared/components/avatar";
+import { TextInput } from "@docspace/shared/components/text-input";
+import { DropDownItem } from "@docspace/shared/components/drop-down-item";
+import { toastr } from "@docspace/shared/components/toast";
+import { Aside } from "@docspace/shared/components/aside";
+import { Backdrop } from "@docspace/shared/components/backdrop";
+import PeopleSelector from "@docspace/shared/selectors/People";
+import Filter from "@docspace/shared/api/people/filter";
+import { getMembersList } from "@docspace/shared/api/people";
+import {
+ AccountsSearchArea,
+ EmployeeType,
+ RoomsType,
+ ShareAccessRights,
+} from "@docspace/shared/enums";
+import withCultureNames from "SRC_DIR/HOCs/withCultureNames";
+import { checkIfAccessPaid } from "SRC_DIR/helpers";
+
+import AddUsersPanel from "../../AddUsersPanel";
+import { getTopFreeRole } from "../utils";
+
+import {
+ StyledSubHeader,
+ StyledLink,
+ StyledInviteInput,
+ StyledInviteInputContainer,
+ StyledDropDown,
+ SearchItemText,
+ StyledDescription,
+ StyledCrossIcon,
+} from "../StyledInvitePanel";
+
+const minSearchValue = 1;
+const PEOPLE_TAB_ID = "0";
+
+const InviteInput = ({
+ t,
+ roomId = 254, //TODO: Templates
+ onClose,
+ roomType,
+ inviteItems,
+ setInviteItems,
+ addUsersPanelVisible,
+ setAddUsersPanelVisible,
+ isMobileView,
+}) => {
+ const [inputValue, setInputValue] = useState("");
+ const [selectedTab, setSelectedTab] = useState("");
+ const [usersList, setUsersList] = useState([]);
+ const [isAddEmailPanelBlocked, setIsAddEmailPanelBlocked] = useState(true);
+ const [dropDownWidth, setDropDownWidth] = useState(0);
+ const searchRef = useRef();
+
+ console.log("roomId", roomId);
+
+ const isPublicRoomType = roomType === RoomsType.PublicRoom;
+
+ const dropDownMaxHeight = usersList.length > 5 ? { maxHeight: 240 } : {};
+ const foundUsers = usersList.map((user) => getItemContent(user));
+
+ useEffect(() => {
+ setTimeout(() => {
+ const width = searchRef?.current?.offsetWidth ?? 0;
+ if (width !== dropDownWidth) setDropDownWidth(width);
+ }, 0);
+ });
+
+ const searchByQuery = async (value) => {
+ console.log("searchByQuery");
+ return;
+
+ const query = value.trim();
+
+ if (query.length >= minSearchValue) {
+ const searchArea = isPublicRoomType
+ ? AccountsSearchArea.People
+ : AccountsSearchArea.Any;
+ const filter = Filter.getFilterWithOutDisabledUser();
+ filter.role = [EmployeeType.Admin, EmployeeType.User];
+ filter.search = query;
+
+ const users = await getMembersList(searchArea, roomId, filter);
+
+ setUsersList(users.items);
+
+ if (users.total) setIsAddEmailPanelBlocked(false);
+ }
+
+ if (!query) {
+ setInputValue("");
+ setUsersList([]);
+ setIsAddEmailPanelBlocked(true);
+ }
+ };
+
+ const debouncedSearch = useCallback(
+ debounce((value) => searchByQuery(value), 300),
+ [],
+ );
+
+ const onChange = (e) => {
+ const value = e.target.value;
+ onChangeInput(value);
+ };
+
+ const onChangeInput = (value) => {
+ const clearValue = value.trim();
+
+ setInputValue(value);
+
+ if (clearValue.length < minSearchValue) {
+ setUsersList([]);
+ setIsAddEmailPanelBlocked(true);
+ return;
+ }
+
+ if (roomId !== -1) {
+ debouncedSearch(clearValue);
+ }
+
+ setIsAddEmailPanelBlocked(true);
+ };
+
+ const removeExist = (items) => {
+ const filtered = items.reduce((unique, o) => {
+ !unique.some((obj) =>
+ obj.isGroup ? obj.id === o.id : obj.email === o.email,
+ ) && unique.push(o);
+
+ return unique;
+ }, []);
+
+ if (items.length > filtered.length) toastr.warning(t("UsersAlreadyAdded"));
+
+ return filtered;
+ };
+
+ const getItemContent = (item) => {
+ const {
+ avatar,
+ displayName,
+ name: groupName,
+ email,
+ id,
+ shared,
+ isGroup = false,
+ } = item;
+
+ const addUser = () => {
+ console.log("addUser");
+ if (item.isOwner || item.isAdmin)
+ item.access = ShareAccessRights.RoomManager;
+
+ // if (isGroup && checkIfAccessPaid(item.access)) {
+ // const topFreeRole = getTopFreeRole(t, roomType);
+ // item.access = topFreeRole.access;
+ // item.warning = t("GroupMaxAvailableRoleWarning", {
+ // role: topFreeRole.label,
+ // });
+ // }
+
+ const items = removeExist([item, ...inviteItems]);
+ setInviteItems(items);
+
+ setInputValue("");
+ setUsersList([]);
+ setIsAddEmailPanelBlocked(true);
+ };
+
+ return (
+
+
+
+
+ {displayName || groupName}
+
+ {email}
+
+ {shared && {t("Common:Invited")}}
+
+ );
+ };
+
+ const addItems = (users) => {
+ console.log("addItems", users);
+ const topFreeRole = getTopFreeRole(t, roomType);
+ users.forEach((u) => {
+ if (u.isGroup && checkIfAccessPaid(u.access)) {
+ u.access = topFreeRole.access;
+ u.warning = t("GroupMaxAvailableRoleWarning", {
+ role: topFreeRole.label,
+ });
+ }
+ });
+
+ const items = [...users, ...inviteItems];
+
+ const filtered = removeExist(items);
+
+ setInviteItems(filtered);
+ setInputValue("");
+ setUsersList([]);
+ };
+
+ const getSelectedTab = (item) => setSelectedTab(item);
+
+ const openUsersPanel = () => {
+ setInputValue("");
+ setAddUsersPanelVisible(true);
+ setIsAddEmailPanelBlocked(true);
+ };
+
+ const closeUsersPanel = () => {
+ setAddUsersPanelVisible(false);
+ };
+
+ const onClearInput = () => onChangeInput("");
+
+ const invitedUsers = useMemo(
+ () => inviteItems.map((item) => item.id),
+ [inviteItems],
+ );
+
+ const filter = new Filter();
+ filter.role = [EmployeeType.Admin, EmployeeType.User]; // 1(EmployeeType.User) - RoomAdmin | 3(EmployeeType.Admin) - DocSpaceAdmin
+
+ return (
+ <>
+
+ {t("Files:AddUsersOrGroups")}
+
+
+ {t("Translations:ChooseFromList")}
+
+
+
+ {t("Files:AddUsersOrGroupsDescription")}
+
+
+
+
+
+
+
+
+
+
+ {isAddEmailPanelBlocked ? (
+ <>>
+ ) : (
+
+ {foundUsers}
+
+ )}
+
+ {addUsersPanelVisible && (
+
+ )}
+
+ >
+ );
+};
+
+export default inject(({ dialogsStore }) => {
+ const { invitePanelOptions } = dialogsStore;
+
+ return {
+ roomId: invitePanelOptions.roomId,
+ };
+})(
+ withCultureNames(
+ withTranslation(["InviteDialog", "Common", "Translations"])(
+ observer(InviteInput),
+ ),
+ ),
+);
diff --git a/packages/client/src/components/panels/TemplateAccessSettingsPanel/sub-components/Item.js b/packages/client/src/components/panels/TemplateAccessSettingsPanel/sub-components/Item.js
new file mode 100644
index 0000000000..e13560dbb6
--- /dev/null
+++ b/packages/client/src/components/panels/TemplateAccessSettingsPanel/sub-components/Item.js
@@ -0,0 +1,267 @@
+// (c) Copyright Ascensio System SIA 2009-2024
+//
+// This program is a free software product.
+// You can redistribute it and/or modify it under the terms
+// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
+// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
+// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
+// any third-party rights.
+//
+// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
+// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
+// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+//
+// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
+//
+// The interactive user interfaces in modified source and object code versions of the Program must
+// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
+//
+// Pursuant to Section 7(b) of the License you must retain the original Product logo when
+// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
+// trademark law for use of our trademarks.
+//
+// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
+// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
+// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+
+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 { useState, useEffect } from "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 {
+ StyledEditInput,
+ StyledEditButton,
+ StyledCheckIcon,
+ StyledCrossIcon,
+ StyledHelpButton,
+ StyledDeleteIcon,
+ StyledInviteUserBody,
+} from "../StyledInvitePanel";
+import { filterGroupRoleOptions, filterUserRoleOptions } from "SRC_DIR/helpers";
+
+const Item = ({
+ t,
+ item,
+ setInviteItems,
+ inviteItems,
+ changeInviteItem,
+ setHasErrors,
+ roomType,
+ isOwner,
+ standalone,
+}) => {
+ const {
+ avatar,
+ displayName,
+ email,
+ id,
+ errors,
+ access,
+ isGroup,
+ name: groupName,
+ warning,
+ } = item;
+
+ const name = isGroup
+ ? groupName
+ : !!avatar
+ ? displayName !== ""
+ ? displayName
+ : email
+ : email;
+ const source = !!avatar ? avatar : isGroup ? "" : AtReactSvgUrl;
+
+ const [edit, setEdit] = useState(false);
+ const [inputValue, setInputValue] = useState(name);
+ const [parseErrors, setParseErrors] = useState(errors);
+
+ console.log("roomType", roomType);
+
+ const accesses = getAccessOptions(
+ t,
+ roomType,
+ true,
+ true,
+ isOwner,
+ standalone,
+ );
+
+ const filteredAccesses = item.isGroup
+ ? filterGroupRoleOptions(accesses)
+ : filterUserRoleOptions(accesses, item, true);
+
+ console.log("filteredAccesses", filteredAccesses);
+
+ const defaultAccess = filteredAccesses.find(
+ (option) => option.access === +access,
+ );
+ const getUserType = (item) => {
+ if (item.isOwner) return "owner";
+ if (item.isAdmin) return "admin";
+ if (item.isRoomAdmin) return "manager";
+ if (item.isCollaborator) return "collaborator";
+ return "user";
+ };
+
+ const type = getUserType(item);
+
+ const typeLabel = item?.isEmailInvite
+ ? getUserTypeLabel(defaultAccess.type, t)
+ : (type === "user" && defaultAccess?.type !== type) ||
+ (defaultAccess?.type === "manager" && type !== "admin")
+ ? getUserTypeLabel(defaultAccess.type, t)
+ : getUserTypeLabel(type, t);
+
+ const errorsInList = () => {
+ const hasErrors = inviteItems.some((item) => !!item.errors?.length);
+ setHasErrors(hasErrors);
+ };
+
+ const onEdit = (e) => {
+ if (e.detail === 2) {
+ setEdit(true);
+ }
+ };
+
+ const cancelEdit = (e) => {
+ setInputValue(name);
+ setEdit(false);
+ };
+
+ const saveEdit = (e) => {
+ const value = inputValue === "" ? name : inputValue;
+
+ setEdit(false);
+ validateValue(value);
+ };
+
+ const onKeyPress = (e) => {
+ if (edit) {
+ if (e.key === "Enter") {
+ saveEdit();
+ }
+ }
+ };
+
+ useEffect(() => {
+ document.addEventListener("keyup", onKeyPress);
+ return () => document.removeEventListener("keyup", onKeyPress);
+ });
+
+ const validateValue = (value) => {
+ const email = parseAddresses(value);
+ const parseErrors = email[0].parseErrors;
+ const errors = !!parseErrors.length ? parseErrors : [];
+
+ setParseErrors(errors);
+ changeInviteItem({ id, email: value, errors }).then(() => errorsInList());
+ };
+
+ const changeValue = (e) => {
+ const value = e.target.value.trim();
+
+ setInputValue(value);
+ };
+
+ const hasError = parseErrors && !!parseErrors.length;
+
+ const removeItem = () => {
+ const newItems = inviteItems.filter((item) => item.id !== id);
+
+ setInviteItems(newItems);
+ };
+
+ const selectItemAccess = (selected) => {
+ if (selected.key === "remove") return removeItem();
+
+ changeInviteItem({ id, access: selected.access });
+ };
+
+ const textProps = !!avatar || isGroup ? {} : { onClick: onEdit };
+
+ const displayBody = (
+ <>
+
+
+ {inputValue}
+
+
+ {!isGroup && (
+
+ {`${typeLabel} | ${email}`}
+
+ )}
+
+
+ {hasError ? (
+ <>
+
+
+ >
+ ) : (
+ <>
+ {warning && (
+
+
+
+ )}
+ >
+ )}
+ >
+ );
+
+ const okIcon =
;
+ const cancelIcon =
;
+
+ const editBody = (
+ <>
+
+
+
+ >
+ );
+
+ return (
+ <>
+
+ {edit ? editBody : displayBody}
+ >
+ );
+};
+
+export default Item;
diff --git a/packages/client/src/components/panels/TemplateAccessSettingsPanel/sub-components/ItemsList.js b/packages/client/src/components/panels/TemplateAccessSettingsPanel/sub-components/ItemsList.js
new file mode 100644
index 0000000000..9aa2f15fe3
--- /dev/null
+++ b/packages/client/src/components/panels/TemplateAccessSettingsPanel/sub-components/ItemsList.js
@@ -0,0 +1,202 @@
+// (c) Copyright Ascensio System SIA 2009-2024
+//
+// This program is a free software product.
+// You can redistribute it and/or modify it under the terms
+// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
+// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
+// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
+// any third-party rights.
+//
+// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
+// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
+// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+//
+// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
+//
+// The interactive user interfaces in modified source and object code versions of the Program must
+// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
+//
+// Pursuant to Section 7(b) of the License you must retain the original Product logo when
+// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
+// trademark law for use of our trademarks.
+//
+// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
+// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
+// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+
+import React, { useState, useEffect, useRef, memo, useCallback } from "react";
+import { inject, observer } from "mobx-react";
+import { FixedSizeList as List } from "react-window";
+import { CustomScrollbarsVirtualList } from "@docspace/shared/components/scrollbar";
+import useResizeObserver from "use-resize-observer";
+import Item from "./Item";
+
+import { StyledRow, ScrollList } from "../StyledInvitePanel";
+import { useTheme } from "styled-components";
+
+const FOOTER_HEIGHT = 73;
+const USER_ITEM_HEIGHT = 48;
+
+const Row = memo(({ data, index, style }) => {
+ const {
+ inviteItems,
+ setInviteItems,
+ changeInviteItem,
+ t,
+ setHasErrors,
+ roomType,
+ isOwner,
+ setIsOpenItemAccess,
+ isMobileView,
+ standalone,
+ } = data;
+
+ if (inviteItems === undefined) return;
+
+ const item = inviteItems[index];
+
+ return (
+
+
+
+ );
+});
+
+const ItemsList = ({
+ t,
+ setInviteItems,
+ inviteItems,
+ changeInviteItem,
+ setHasErrors,
+ roomType,
+ isOwner,
+ externalLinksVisible,
+ scrollAllPanelContent,
+ invitePanelBodyRef,
+ isMobileView,
+ standalone,
+}) => {
+ const [bodyHeight, setBodyHeight] = useState(0);
+ const [offsetTop, setOffsetTop] = useState(0);
+ const [isTotalListHeight, setIsTotalListHeight] = useState(false);
+ const [isOpenItemAccess, setIsOpenItemAccess] = useState(false);
+ const bodyRef = useRef();
+ const { height } = useResizeObserver({ ref: bodyRef });
+ const { interfaceDirection } = useTheme();
+
+ const onBodyResize = useCallback(() => {
+ const scrollHeight = bodyRef?.current?.firstChild.scrollHeight;
+ const heightList = height ? height : bodyRef.current.offsetHeight;
+ const totalHeightItems = inviteItems.length * USER_ITEM_HEIGHT;
+ const listAreaHeight = heightList;
+ const heightBody = invitePanelBodyRef?.current?.clientHeight;
+ const fullHeightList = heightBody - bodyRef.current.offsetTop;
+ const heightWitchOpenItemAccess = Math.max(scrollHeight, fullHeightList);
+
+ const calculatedHeight = scrollAllPanelContent
+ ? Math.max(
+ totalHeightItems,
+ listAreaHeight,
+ isOpenItemAccess ? heightWitchOpenItemAccess : 0,
+ )
+ : heightList - FOOTER_HEIGHT;
+
+ const finalHeight = scrollAllPanelContent
+ ? isOpenItemAccess
+ ? calculatedHeight
+ : totalHeightItems
+ : calculatedHeight;
+
+ setBodyHeight(finalHeight);
+ setOffsetTop(bodyRef.current.offsetTop);
+
+ if (scrollAllPanelContent && totalHeightItems && listAreaHeight)
+ setIsTotalListHeight(
+ totalHeightItems >= listAreaHeight && totalHeightItems >= scrollHeight,
+ );
+ }, [
+ height,
+ bodyRef?.current?.offsetHeight,
+ inviteItems.length,
+ scrollAllPanelContent,
+ isOpenItemAccess,
+ ]);
+
+ useEffect(() => {
+ onBodyResize();
+ }, [
+ bodyRef.current,
+ externalLinksVisible,
+ height,
+ inviteItems.length,
+ scrollAllPanelContent,
+ isOpenItemAccess,
+ ]);
+
+ const overflowStyle = scrollAllPanelContent ? "hidden" : "scroll";
+
+ const willChangeStyle =
+ isMobileView && isOpenItemAccess ? "auto" : "transform";
+
+ return (
+
+
+ {Row}
+
+
+ );
+};
+
+export default inject(({ userStore, dialogsStore, settingsStore }) => {
+ const { changeInviteItem } = dialogsStore;
+ const { isOwner } = userStore.user;
+ const { standalone } = settingsStore;
+
+ return {
+ changeInviteItem,
+ isOwner,
+ standalone,
+ };
+})(observer(ItemsList));
diff --git a/packages/client/src/components/panels/TemplateAccessSettingsPanel/utils/index.js b/packages/client/src/components/panels/TemplateAccessSettingsPanel/utils/index.js
new file mode 100644
index 0000000000..c496782ecc
--- /dev/null
+++ b/packages/client/src/components/panels/TemplateAccessSettingsPanel/utils/index.js
@@ -0,0 +1,215 @@
+// (c) Copyright Ascensio System SIA 2009-2024
+//
+// This program is a free software product.
+// You can redistribute it and/or modify it under the terms
+// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
+// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
+// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
+// any third-party rights.
+//
+// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
+// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
+// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+//
+// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
+//
+// The interactive user interfaces in modified source and object code versions of the Program must
+// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
+//
+// Pursuant to Section 7(b) of the License you must retain the original Product logo when
+// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
+// trademark law for use of our trademarks.
+//
+// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
+// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
+// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+
+import {
+ ShareAccessRights,
+ RoomsType,
+ EmployeeType,
+} from "@docspace/shared/enums";
+import { checkIfAccessPaid } from "SRC_DIR/helpers";
+
+export const getAccessOptions = (
+ t,
+ roomType = RoomsType.CustomRoom,
+ withRemove = false,
+ withSeparator = false,
+ isOwner = false,
+ standalone = false,
+) => {
+ let options = [];
+ const accesses = {
+ docSpaceAdmin: {
+ key: "docSpaceAdmin",
+ label: t("Common:DocSpaceAdmin"),
+ description: t("Translations:RoleDocSpaceAdminDescription"),
+ ...(!standalone && { quota: t("Common:Paid") }),
+ color: "#EDC409",
+ access:
+ roomType === -1 ? EmployeeType.Admin : ShareAccessRights.FullAccess,
+ type: "admin",
+ },
+ roomAdmin: {
+ key: "roomAdmin",
+ label: t("Common:RoomAdmin"),
+ description: t("Translations:RoleRoomAdminDescription"),
+ ...(!standalone && { quota: t("Common:Paid") }),
+ color: "#EDC409",
+ access:
+ roomType === -1 ? EmployeeType.User : ShareAccessRights.RoomManager,
+ type: "manager",
+ },
+ collaborator: {
+ key: "collaborator",
+ label: t("Common:PowerUser"),
+ description: t("Translations:RolePowerUserDescription"),
+ ...(!standalone && { quota: t("Common:Paid") }),
+ color: "#EDC409",
+ access:
+ roomType === -1
+ ? EmployeeType.Collaborator
+ : ShareAccessRights.Collaborator,
+ type: "collaborator",
+ },
+ user: {
+ key: "user",
+ label: t("Common:User"),
+ description: t("Translations:RoleUserDescription"),
+ access: EmployeeType.Guest,
+ type: "user",
+ },
+ editor: {
+ key: "editor",
+ label: t("Common:Editor"),
+ description: t("Translations:RoleEditorDescription"),
+ access: ShareAccessRights.Editing,
+ type: "user",
+ },
+ formFiller: {
+ key: "formFiller",
+ label: t("Translations:RoleFormFiller"),
+ description: t("Translations:RoleFormFillerDescription"),
+ access: ShareAccessRights.FormFilling,
+ type: "user",
+ },
+ reviewer: {
+ key: "reviewer",
+ label: t("Translations:RoleReviewer"),
+ description: t("Translations:RoleReviewerDescription"),
+ access: ShareAccessRights.Review,
+ type: "user",
+ },
+ commentator: {
+ key: "commentator",
+ label: t("Translations:RoleCommentator"),
+ description: t("Translations:RoleCommentatorDescription"),
+ access: ShareAccessRights.Comment,
+ type: "user",
+ },
+ viewer: {
+ key: "viewer",
+ label: t("Translations:RoleViewer"),
+ description: t("Translations:RoleViewerDescription"),
+ access: ShareAccessRights.ReadOnly,
+ type: "user",
+ },
+ };
+
+ switch (roomType) {
+ case RoomsType.FillingFormsRoom:
+ options = [
+ accesses.roomAdmin,
+ accesses.collaborator,
+ { key: "s1", isSeparator: withSeparator },
+ accesses.formFiller,
+ accesses.viewer,
+ ];
+ break;
+ case RoomsType.EditingRoom:
+ options = [
+ accesses.roomAdmin,
+ accesses.collaborator,
+ { key: "s1", isSeparator: withSeparator },
+ accesses.editor,
+ accesses.viewer,
+ ];
+ break;
+ case RoomsType.ReviewRoom:
+ options = [
+ accesses.roomAdmin,
+ accesses.collaborator,
+ { key: "s1", isSeparator: withSeparator },
+ accesses.reviewer,
+ accesses.commentator,
+ accesses.viewer,
+ ];
+ break;
+ case RoomsType.ReadOnlyRoom:
+ options = [
+ accesses.roomAdmin,
+ accesses.collaborator,
+ { key: "s1", isSeparator: withSeparator },
+ accesses.viewer,
+ ];
+ break;
+ case RoomsType.CustomRoom:
+ options = [
+ accesses.roomAdmin,
+ accesses.collaborator,
+ { key: "s1", isSeparator: withSeparator },
+ accesses.editor,
+ accesses.formFiller,
+ accesses.reviewer,
+ accesses.commentator,
+ accesses.viewer,
+ ];
+ break;
+ case RoomsType.PublicRoom:
+ options = [accesses.roomAdmin, accesses.collaborator];
+ break;
+
+ case RoomsType.FormRoom:
+ options = [
+ accesses.roomAdmin,
+ accesses.collaborator,
+ { key: "s1", isSeparator: withSeparator },
+ accesses.viewer,
+ accesses.formFiller,
+ ];
+ break;
+ case -1:
+ if (isOwner) options.push(accesses.docSpaceAdmin);
+
+ options = [
+ ...options,
+ accesses.roomAdmin,
+ accesses.collaborator,
+ { key: "s1", isSeparator: withSeparator },
+ accesses.user,
+ ];
+ break;
+ }
+
+ const removeOption = [
+ {
+ key: "s2",
+ isSeparator: true,
+ },
+ {
+ key: "remove",
+ label: t("Common:Remove"),
+ },
+ ];
+
+ return withRemove ? [...options, ...removeOption] : options;
+};
+
+export const getTopFreeRole = (t, roomType) => {
+ const accesses = getAccessOptions(t, roomType);
+ const freeAccesses = accesses.filter(
+ (item) => !checkIfAccessPaid(item.access) && item.key !== "s1",
+ );
+ return freeAccesses[0];
+};
diff --git a/packages/client/src/components/panels/index.js b/packages/client/src/components/panels/index.js
index 773fbaa370..e85d83234c 100644
--- a/packages/client/src/components/panels/index.js
+++ b/packages/client/src/components/panels/index.js
@@ -33,6 +33,7 @@ import UploadPanel from "./UploadPanel";
import HotkeyPanel from "./HotkeysPanel";
import InvitePanel from "./InvitePanel";
import EditLinkPanel from "./EditLinkPanel";
+import TemplateAccessSettingsPanel from "./TemplateAccessSettingsPanel";
export {
AddUsersPanel,
@@ -44,4 +45,5 @@ export {
HotkeyPanel,
InvitePanel,
EditLinkPanel,
+ TemplateAccessSettingsPanel,
};
diff --git a/packages/client/src/store/ContextOptionsStore.js b/packages/client/src/store/ContextOptionsStore.js
index 96098764a5..557f424b8c 100644
--- a/packages/client/src/store/ContextOptionsStore.js
+++ b/packages/client/src/store/ContextOptionsStore.js
@@ -682,6 +682,10 @@ class ContextOptionsStore {
this.filesActionsStore.onCreateRoomFromTemplate({ ...item, isEdit: true });
};
+ onOpenTemplateAccessOptions = () => {
+ this.dialogsStore.setTemplateAccessSettingsVisible(true);
+ };
+
// onLoadLinks = async (t, item) => {
// const promise = new Promise(async (resolve, reject) => {
// let linksArray = [];
@@ -1339,6 +1343,14 @@ class ContextOptionsStore {
onClick: () => this.onEditRoomTemplate(item),
disabled: false,
},
+ {
+ id: "option_access-settings",
+ key: "access-settings",
+ label: t("AccessSettings"),
+ icon: PersonReactSvgUrl,
+ onClick: () => this.onOpenTemplateAccessOptions(),
+ disabled: false,
+ },
{
id: "option_invite-users-to-room",
key: "invite-users-to-room",
diff --git a/packages/client/src/store/DialogsStore.js b/packages/client/src/store/DialogsStore.js
index 69983cb90f..256580d4d1 100644
--- a/packages/client/src/store/DialogsStore.js
+++ b/packages/client/src/store/DialogsStore.js
@@ -116,6 +116,7 @@ class DialogsStore {
shareFolderDialogVisible = false;
cancelUploadDialogVisible = false;
createRoomTemplateDialogVisible = false;
+ templateAccessSettingsVisible = true;
selectFileFormRoomFilterParam = FilesSelectorFilterTypes.DOCX;
@@ -523,6 +524,10 @@ class DialogsStore {
setCreateRoomTemplateDialogVisible = (visible) => {
this.createRoomTemplateDialogVisible = visible;
};
+
+ setTemplateAccessSettingsVisible = (isVisible) => {
+ this.templateAccessSettingsVisible = isVisible;
+ };
}
export default DialogsStore;
diff --git a/packages/shared/components/selector/Selector.styled.ts b/packages/shared/components/selector/Selector.styled.ts
index 2b3fa130c2..0eb77d51eb 100644
--- a/packages/shared/components/selector/Selector.styled.ts
+++ b/packages/shared/components/selector/Selector.styled.ts
@@ -438,6 +438,11 @@ const StyledInfo = styled.div`
.text {
color: ${(props) => props.theme.selector.info.color};
}
+
+ .selector-info-text-wrapper {
+ display: flex;
+ gap: 8px;
+ }
`;
StyledSelector.defaultProps = { theme: Base };
diff --git a/packages/shared/components/selector/Selector.tsx b/packages/shared/components/selector/Selector.tsx
index d433386e66..e5fec8cac2 100644
--- a/packages/shared/components/selector/Selector.tsx
+++ b/packages/shared/components/selector/Selector.tsx
@@ -134,6 +134,7 @@ const Selector = ({
withInfo,
infoText,
+ withInfoBadge,
}: SelectorProps) => {
const [footerVisible, setFooterVisible] = React.useState
(false);
const [isSearch, setIsSearch] = React.useState(false);
@@ -530,6 +531,7 @@ const Selector = ({
? {
withInfo,
infoText,
+ withInfoBadge,
}
: {};
diff --git a/packages/shared/components/selector/Selector.types.ts b/packages/shared/components/selector/Selector.types.ts
index 4c8ec956cc..877affc6f8 100644
--- a/packages/shared/components/selector/Selector.types.ts
+++ b/packages/shared/components/selector/Selector.types.ts
@@ -295,8 +295,12 @@ export type TSelectorFooterCheckbox = TSelectorCheckbox & {
};
export type TSelectorInfo =
- | { withInfo: true; infoText: string }
- | { withInfo?: undefined; infoText?: undefined };
+ | { withInfo: true; infoText: string; withInfoBadge?: boolean }
+ | {
+ withInfo?: undefined;
+ infoText?: undefined;
+ withInfoBadge?: undefined;
+ };
export type SelectorProps = TSelectorHeader &
TSelectorInfo &
@@ -377,6 +381,7 @@ export type BodyProps = TSelectorBreadCrumbs &
withFooterInput?: boolean;
withFooterCheckbox?: boolean;
descriptionText?: string;
+ withInfoBadge?: boolean;
};
export type FooterProps = TSelectorFooterSubmitButton &
diff --git a/packages/shared/components/selector/sub-components/Body.tsx b/packages/shared/components/selector/sub-components/Body.tsx
index 7ebbb2e13f..3c2a6f087c 100644
--- a/packages/shared/components/selector/sub-components/Body.tsx
+++ b/packages/shared/components/selector/sub-components/Body.tsx
@@ -101,6 +101,7 @@ const Body = ({
withInfo,
infoText,
+ withInfoBadge,
}: BodyProps) => {
const [bodyHeight, setBodyHeight] = React.useState(0);
@@ -212,7 +213,11 @@ const Body = ({
) : null}
{withInfo && !isLoading && (
-
+
)}
{isLoading ? (
diff --git a/packages/shared/components/selector/sub-components/Info.tsx b/packages/shared/components/selector/sub-components/Info.tsx
index 419c245ba2..1b649250c1 100644
--- a/packages/shared/components/selector/sub-components/Info.tsx
+++ b/packages/shared/components/selector/sub-components/Info.tsx
@@ -26,17 +26,27 @@
import React from "react";
+import { ReactSVG } from "react-svg";
+import InfoIcon from "PUBLIC_DIR/images/info.outline.react.svg?url";
import { Text } from "../../text";
import { TSelectorInfo } from "../Selector.types";
import { StyledInfo } from "../Selector.styled";
-export const Info = ({ infoText }: TSelectorInfo) => {
+export const Info = ({ infoText, withInfoBadge }: TSelectorInfo) => {
return (
-
- {infoText}
-
+
+ {withInfoBadge && }
+
+ {infoText}
+
+
);
};