Login:Components:TfaActivationForm: add TfaActivationForm

This commit is contained in:
Darya Umrikhina 2024-07-29 12:10:15 +04:00
parent dd38adfb33
commit 8e7bbdc8ad
2 changed files with 496 additions and 0 deletions

View File

@ -0,0 +1,242 @@
// (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 styled from "styled-components";
import {
mobile,
tablet,
getCorrectFourValuesStyle,
} from "@docspace/shared/utils";
import { Box } from "@docspace/shared/components/box";
export const StyledPage = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin: 0 auto;
max-width: 960px;
box-sizing: border-box;
@media ${tablet} {
padding: 0 16px;
}
@media ${mobile} {
width: 100%;
padding: ${({ theme }) =>
getCorrectFourValuesStyle("32px 8px 0 16px", theme.interfaceDirection)};
.language-combo-box {
display: none;
}
}
.subtitle {
margin-bottom: 32px;
}
.password-form {
width: 100%;
margin-bottom: 8px;
}
.subtitle {
margin-bottom: 32px;
}
.language-combo-box {
position: absolute;
right: 28px;
top: 28px;
}
`;
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} {
width: 100%;
justify-content: start;
min-height: 100%;
}
`;
export const StyledHeader = styled.div`
.title {
margin-bottom: 32px;
text-align: center;
}
.subtitle {
margin-bottom: 32px;
}
.portal-logo {
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 40px;
}
@media ${mobile} {
margin-top: 0;
}
`;
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: 40px;
}
.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 StyledForm = styled(Box)`
margin: 56px auto;
display: flex;
flex: 1fr 1fr;
gap: 80px;
flex-direction: row;
justify-content: center;
@media ${tablet} {
display: flex;
flex-direction: column;
align-items: center;
gap: 32px;
}
@media ${mobile} {
margin: 0 auto;
flex-direction: column;
gap: 0px;
${({ theme }) =>
theme.interfaceDirection === "rtl"
? `padding-left: 8px;`
: `padding-right: 8px;`}
}
.app-code-wrapper {
width: 100%;
@media ${tablet} {
flex-direction: column;
}
}
.portal-logo {
padding-bottom: 40px;
@media ${tablet} {
display: flex;
align-items: center;
justify-content: center;
}
}
.set-app-description {
width: 100%;
max-width: 500px;
}
.set-app-title {
margin-bottom: 14px;
}
.set-app-text {
margin-top: 14px;
}
.qrcode-wrapper {
display: flex;
align-items: center;
justify-content: center;
padding: 0px 80px;
border-radius: 6px;
margin-bottom: 32px;
@media ${mobile} {
display: none;
}
}
.app-code-continue-btn {
margin-top: 8px;
}
`;

View File

@ -0,0 +1,254 @@
// (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
/* eslint-disable @next/next/no-img-element */
"use client";
import { useContext, useState } from "react";
import { useTheme } from "styled-components";
import { Trans, useTranslation } from "react-i18next";
import { Link, LinkTarget } from "@docspace/shared/components/link";
import { combineUrl } from "@docspace/shared/utils/combineUrl";
import { Text } from "@docspace/shared/components/text";
import PortalLogo from "@docspace/shared/components/portal-logo/PortalLogo";
import { Box } from "@docspace/shared/components/box";
import { FormWrapper } from "@docspace/shared/components/form-wrapper";
import { FieldContainer } from "@docspace/shared/components/field-container";
import {
InputSize,
InputType,
TextInput,
} from "@docspace/shared/components/text-input";
import { Button, ButtonSize } from "@docspace/shared/components/button";
import { toastr } from "@docspace/shared/components/toast";
import { TColorScheme } from "@docspace/shared/themes";
import { TPasswordHash } from "@docspace/shared/api/settings/types";
import { loginWithTfaCode } from "@docspace/shared/api/user";
import { validateTfaCode } from "@docspace/shared/api/settings";
import { getLogoUrl } from "@docspace/shared/utils";
import { WhiteLabelLogoType } from "@docspace/shared/enums";
import {
TFA_ANDROID_APP_URL,
TFA_IOS_APP_URL,
TFA_WIN_APP_URL,
} from "@/utils/constants";
import { ConfirmRouteContext } from "@/app/(root)/confirm/confirmRoute";
import withLoader from "@/app/(root)/confirm/withLoader";
import { TError, WithLoaderProps } from "@/types";
import {
StyledContent,
StyledForm,
StyledPage,
} from "./TfaActivationForm.styled";
const PROXY_BASE_URL = combineUrl(window.ClientConfig?.proxy?.url, "/profile");
type TfaActivationFormProps = {
secretKey: any;
qrCode: any;
passwordHash: TPasswordHash;
userName?: string;
currentColorScheme?: TColorScheme;
} & WithLoaderProps;
const TfaActivationForm = (props: TfaActivationFormProps) => {
const { secretKey, qrCode, passwordHash, userName, currentColorScheme } =
props;
const { linkData } = useContext(ConfirmRouteContext);
const { t } = useTranslation(["Confirm", "Common"]);
const [code, setCode] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");
const { confirmHeader = null } = linkData;
const onSubmit = async () => {
try {
setIsLoading(true);
if (userName && passwordHash) {
await loginWithTfaCode(userName, passwordHash, code);
} else {
await validateTfaCode(code, confirmHeader);
}
sessionStorage.setItem("openBackupCodesDialog", "true");
window.location.href = PROXY_BASE_URL;
} catch (error) {
const knownError = error as TError;
let errorMessage: string;
if (typeof knownError === "object") {
errorMessage =
knownError?.response?.data?.error?.message ||
knownError?.statusText ||
knownError?.message ||
"";
} else {
errorMessage = knownError;
}
setError(errorMessage);
toastr.error(errorMessage);
} finally {
setIsLoading(false);
}
};
const onKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.code === "Enter" || event.code === "NumpadEnter") onSubmit();
};
const theme = useTheme();
const logoUrl = getLogoUrl(WhiteLabelLogoType.LoginPage, !theme.isBase);
return (
<StyledPage>
<StyledContent>
<StyledForm className="set-app-container">
<Box className="set-app-description" marginProp="0 0 32px 0">
<PortalLogo className="portal-logo" />
<Text isBold fontSize="14px" className="set-app-title">
{t("SetAppTitle")}
</Text>
<Trans
t={t}
i18nKey="SetAppDescription"
ns="Confirm"
productName={t("Common:ProductName")}
>
The two-factor authentication is enabled to provide additional
portal security. Configure your authenticator application to
continue work on the portal. For example you could use Google
Authenticator for
<Link
color={currentColorScheme?.main?.accent}
href={TFA_ANDROID_APP_URL}
target={LinkTarget.blank}
>
Android
</Link>
and{" "}
<Link
color={currentColorScheme?.main?.accent}
href={TFA_IOS_APP_URL}
target={LinkTarget.blank}
>
iOS
</Link>{" "}
or Authenticator for{" "}
<Link
color={currentColorScheme?.main?.accent}
href={TFA_WIN_APP_URL}
target={LinkTarget.blank}
>
Windows Phone
</Link>{" "}
.
</Trans>
<Text className="set-app-text">
<Trans
t={t}
i18nKey="SetAppInstallDescription"
ns="Confirm"
key={secretKey}
>
To connect your apllication scan the QR code or manually enter
your secret key <strong>{{ secretKey }}</strong> then enter
6-digit code from your application in the field below.
</Trans>
</Text>
</Box>
<FormWrapper>
<Box
displayProp="flex"
flexDirection="column"
className="app-code-wrapper"
>
<div className="qrcode-wrapper">
<img src={qrCode} height="180px" width="180px" alt="QR-code" />
</div>
<Box className="app-code-input">
<FieldContainer
labelVisible={false}
hasError={error ? true : false}
errorMessage={error}
>
<TextInput
id="code"
name="code"
type={InputType.email}
size={InputSize.large}
scale
isAutoFocussed
tabIndex={1}
placeholder={t("EnterCodePlaceholder")}
isDisabled={isLoading}
maxLength={6}
onChange={(e) => {
setCode(e.target.value);
setError("");
}}
value={code}
hasError={error ? true : false}
onKeyDown={onKeyPress}
/>
</FieldContainer>
</Box>
<Box className="app-code-continue-btn">
<Button
scale
primary
size={ButtonSize.medium}
tabIndex={3}
label={
isLoading
? t("Common:LoadingProcessing")
: t("SetAppButton")
}
isDisabled={!code.length || isLoading}
isLoading={isLoading}
onClick={onSubmit}
/>
</Box>
</Box>
</FormWrapper>
</StyledForm>
</StyledContent>
</StyledPage>
);
};
export default withLoader(TfaActivationForm);