diff --git a/packages/client/src/helpers/constants.js b/packages/client/src/helpers/constants.js index 1a952ddef3..c40d85552f 100644 --- a/packages/client/src/helpers/constants.js +++ b/packages/client/src/helpers/constants.js @@ -54,14 +54,7 @@ export const ConfirmType = Object.freeze({ * Enum for result of validation public room keys. * @readonly */ -export const ValidationStatus = Object.freeze({ - Ok: 0, - Invalid: 1, - Expired: 2, - Password: 3, - InvalidPassword: 4, - ExternalAccessDenied: 5, -}); + export const GUID_EMPTY = "00000000-0000-0000-0000-000000000000"; export const ID_NO_GROUP_MANAGER = "4a515a15-d4d6-4b8e-828e-e0586f18f3a3"; diff --git a/packages/client/src/pages/PublicPreview/PublicPreview.tsx b/packages/client/src/pages/PublicPreview/PublicPreview.tsx index 51cad7d915..2e3a84f839 100644 --- a/packages/client/src/pages/PublicPreview/PublicPreview.tsx +++ b/packages/client/src/pages/PublicPreview/PublicPreview.tsx @@ -4,7 +4,7 @@ import { observer, inject } from "mobx-react"; import { useParams, useSearchParams } from "react-router-dom"; import api from "@docspace/shared/api"; -import { UrlActionType } from "@docspace/shared/enums"; +import { UrlActionType, ValidationStatus } from "@docspace/shared/enums"; import { toastr } from "@docspace/shared/components/toast"; import MediaViewer from "@docspace/shared/components/media-viewer/MediaViewer"; import { ViewerLoader } from "@docspace/shared/components/media-viewer/sub-components/ViewerLoader"; @@ -17,8 +17,6 @@ import type { PlaylistType, } from "@docspace/shared/components/media-viewer/MediaViewer.types"; -import { ValidationStatus } from "SRC_DIR/helpers/constants"; - import type { PublicPreviewProps } from "./PublicPreview.types"; import { DEFAULT_EXTS_IMAGE } from "./PublicPreview.constants"; import { isAxiosError, useDeviceType } from "./PublicPreview.helpers"; diff --git a/packages/client/src/pages/PublicRoom/index.js b/packages/client/src/pages/PublicRoom/index.js index a62d372f29..c02d9b02a0 100644 --- a/packages/client/src/pages/PublicRoom/index.js +++ b/packages/client/src/pages/PublicRoom/index.js @@ -29,7 +29,7 @@ import { observer, inject } from "mobx-react"; import { useNavigate, useLocation, useSearchParams } from "react-router-dom"; import Section from "@docspace/shared/components/section"; import { Loader } from "@docspace/shared/components/loader"; -import { ValidationStatus } from "../../helpers/constants"; +import { ValidationStatus } from "@docspace/shared/enums"; import SectionWrapper from "SRC_DIR/components/Section"; import RoomPassword from "./sub-components/RoomPassword"; import RoomErrors from "./sub-components/RoomErrors"; diff --git a/packages/client/src/pages/PublicRoom/sub-components/RoomPassword.js b/packages/client/src/pages/PublicRoom/sub-components/RoomPassword.js index 38d393fc56..b74f86f907 100644 --- a/packages/client/src/pages/PublicRoom/sub-components/RoomPassword.js +++ b/packages/client/src/pages/PublicRoom/sub-components/RoomPassword.js @@ -37,7 +37,7 @@ import { frameCallCommand } from "@docspace/shared/utils/common"; import { toastr } from "@docspace/shared/components/toast"; import { FormWrapper } from "@docspace/shared/components/form-wrapper"; import PortalLogo from "@docspace/shared/components/portal-logo/PortalLogo"; -import { ValidationStatus } from "../../../helpers/constants"; +import { ValidationStatus } from "@docspace/shared/enums"; import PublicRoomIcon from "PUBLIC_DIR/images/icons/32/room/public.svg"; @@ -167,6 +167,7 @@ const RoomPassword = (props) => { isDisabled={isLoading} isDisableTooltip forwardedRef={inputRef} + isAutoFocussed /> diff --git a/packages/client/src/store/PublicRoomStore.js b/packages/client/src/store/PublicRoomStore.js index f8798e1112..f43bc697e1 100644 --- a/packages/client/src/store/PublicRoomStore.js +++ b/packages/client/src/store/PublicRoomStore.js @@ -38,7 +38,8 @@ import { import { CategoryType } from "SRC_DIR/helpers/constants"; import { getCategoryUrl } from "SRC_DIR/helpers/utils"; -import { LinkType, ValidationStatus } from "../helpers/constants"; +import { LinkType } from "../helpers/constants"; +import { ValidationStatus } from "@docspace/shared/enums"; class PublicRoomStore { externalLinks = []; diff --git a/packages/doceditor/src/app/(root)/page.tsx b/packages/doceditor/src/app/(root)/page.tsx index 03fc442ef0..c7652637a4 100644 --- a/packages/doceditor/src/app/(root)/page.tsx +++ b/packages/doceditor/src/app/(root)/page.tsx @@ -29,9 +29,11 @@ import { headers } from "next/headers"; import { getSelectorsByUserAgent } from "react-device-detect"; -import { getData } from "@/utils/actions"; +import { getData, validatePublicRoomKey } from "@/utils/actions"; import { RootPageProps } from "@/types"; import Root from "@/components/Root"; +import FilePassword from "@/components/file-password"; +import { ValidationStatus } from "@docspace/shared/enums"; const initialSearchParams: RootPageProps["searchParams"] = { fileId: undefined, @@ -58,6 +60,17 @@ async function Page({ searchParams }: RootPageProps) { if (isMobile) type = "mobile"; } + if (share) { + const roomData = await validatePublicRoomKey(share, fileId ?? fileid ?? ""); + if (!roomData) return; + + const { status } = roomData.response; + + if (status === ValidationStatus.Password) { + return ; + } + } + const startDate = new Date(); const data = await getData( diff --git a/packages/doceditor/src/components/file-password/FilePassword.styled.ts b/packages/doceditor/src/components/file-password/FilePassword.styled.ts new file mode 100644 index 0000000000..9173ea5248 --- /dev/null +++ b/packages/doceditor/src/components/file-password/FilePassword.styled.ts @@ -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 styled, { css } from "styled-components"; +import { mobile, tablet } from "@docspace/shared/utils"; +import { isIOS, isFirefox } from "react-device-detect"; + +import BackgroundPatternReactSvgUrl from "PUBLIC_DIR/images/background.pattern.react.svg?url"; + +export const StyledPage = styled.div` + display: flex; + flex-direction: column; + align-items: center; + margin: 0 auto; + box-sizing: border-box; + background-image: url("${BackgroundPatternReactSvgUrl}"); + background-repeat: no-repeat; + background-attachment: fixed; + background-size: cover; + + .logo-wrapper { + display: block; + } + + @media ${mobile} { + background-image: none; + height: 0; + + .logo-wrapper { + display: none; + } + } + + height: ${isIOS && !isFirefox ? "calc(var(--vh, 1vh) * 100)" : "100vh"}; + width: 100vw; + + @media ${tablet} { + padding: 0 16px; + } + + @media ${mobile} { + ${(props) => + props.theme.interfaceDirection === "rtl" + ? css` + padding: 0 16px 0 8px; + ` + : css` + padding: 0 8px 0 16px; + `} + } + + .subtitle { + margin-bottom: 32px; + } + + .password-form { + width: 100%; + margin-bottom: 8px; + } + + .subtitle { + margin-bottom: 32px; + } + + .public-room-content { + padding-top: 9%; + justify-content: unset; + min-height: unset; + + .public-room-text { + margin: 8px 0; + white-space: wrap; + } + + .public-room-name { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 32px; + } + + .public-room-icon { + min-width: 32px; + min-height: 32px; + } + } +`; + +export const StyledContent = styled.div` + min-height: 100vh; + flex: 1 0 auto; + flex-direction: column; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + + @media ${mobile} { + justify-content: start; + min-height: 100%; + } + + .bold { + font-weight: 600; + } +`; + +export const StyledBody = styled.div` + display: flex; + flex-direction: column; + align-items: center; + margin: 56px auto; + + @media ${mobile} { + width: 100%; + margin: 0 auto; + } + + .title { + margin-bottom: 32px; + text-align: center; + } + + .subtitle { + margin-bottom: 32px; + } + + .portal-logo { + display: flex; + align-items: center; + justify-content: center; + padding-bottom: 64px; + } + + .password-field-wrapper { + width: 100%; + } + + .password-change-form { + margin-top: 32px; + margin-bottom: 16px; + } + + .phone-input { + margin-bottom: 24px; + } + + .delete-profile-confirm { + margin-bottom: 8px; + } + + .phone-title { + margin-bottom: 8px; + } +`; + +export const StyledSimpleNav = styled.div` + display: none; + height: 48px; + align-items: center; + justify-content: center; + background-color: ${(props) => props.theme?.login?.navBackground}; + + .logo { + height: 24px; + } + + @media ${mobile} { + display: flex; + + .language-combo-box { + position: absolute; + top: 7px; + right: 8px; + } + } +`; diff --git a/packages/doceditor/src/components/file-password/FilePassword.types.ts b/packages/doceditor/src/components/file-password/FilePassword.types.ts new file mode 100644 index 0000000000..f0e229cc99 --- /dev/null +++ b/packages/doceditor/src/components/file-password/FilePassword.types.ts @@ -0,0 +1,34 @@ +// (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 + +export interface FilePasswordProps { + shareKey: string; + title: string; + id: string; + status: string; + roomType: string; + entryTitle: string; +} diff --git a/packages/doceditor/src/components/file-password/index.tsx b/packages/doceditor/src/components/file-password/index.tsx new file mode 100644 index 0000000000..231ae0d68b --- /dev/null +++ b/packages/doceditor/src/components/file-password/index.tsx @@ -0,0 +1,211 @@ +// (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 + +"use client"; + +import React, { useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; + +import { Text } from "@docspace/shared/components/text"; +import { Button, ButtonSize } from "@docspace/shared/components/button"; +import { FieldContainer } from "@docspace/shared/components/field-container"; +import { PasswordInput } from "@docspace/shared/components/password-input"; +import { FormWrapper } from "@docspace/shared/components/form-wrapper"; + +import { + StyledPage, + StyledContent, + StyledBody, + StyledSimpleNav, +} from "./FilePassword.styled"; +import { FilePasswordProps } from "./FilePassword.types"; + +import PublicRoomIcon from "PUBLIC_DIR/images/icons/32/room/public.svg"; +import { InputSize, InputType } from "@docspace/shared/components/text-input"; +import { toastr } from "@docspace/shared/components/toast"; +import { TData } from "@docspace/shared/components/toast/Toast.type"; + +import { getLogoUrl } from "@docspace/shared/utils"; +import { useTheme } from "styled-components"; +import { ValidationStatus, WhiteLabelLogoType } from "@docspace/shared/enums"; +import { validatePublicRoomPassword } from "@docspace/shared/api/rooms"; +import Image from "next/image"; + +const FilesPassword = ({ shareKey, title, entryTitle }: FilePasswordProps) => { + const { t } = useTranslation(["Common"]); + + const theme = useTheme(); + + const [password, setPassword] = useState(""); + const [passwordValid, setPasswordValid] = useState(true); + const [isLoading, setIsLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + + const onChangePassword = (e: React.ChangeEvent) => { + setPassword(e.target.value); + !passwordValid && setPasswordValid(true); + }; + const onKeyPress = (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + onSubmit(); + } + }; + + const onSubmit = async () => { + if (!password.trim()) { + setPasswordValid(false); + setErrorMessage(t("Common:RequiredField")); + } else { + setErrorMessage(""); + } + + if (!passwordValid || !password.trim()) { + setIsLoading(false); + return; + } + + setIsLoading(true); + try { + const res = await validatePublicRoomPassword(shareKey, password); + + if (res?.status === ValidationStatus.Ok) { + return window.location.reload(); + } + + setIsLoading(false); + + if (res?.status === ValidationStatus.InvalidPassword) { + setErrorMessage(t("Common:IncorrectPassword")); + return; + } + } catch (error) { + toastr.error(error as TData); + setIsLoading(false); + } + }; + + const logoUrl = getLogoUrl(WhiteLabelLogoType.LoginPage, !theme.isBase); + + return ( + <> + + mobile-icon + + + + + icon + + +
+ + {t("Common:PasswordRequired")} + + + + }} + /> + +
+ + + {title} + +
+ + + + +
+ +