From 8ec905319a0082cd79dbe0b410c737be2073ed37 Mon Sep 17 00:00:00 2001 From: Darya Umrikhina Date: Thu, 11 Jul 2024 12:33:08 +0400 Subject: [PATCH] Components:WizardForm: add Wizard Form --- .../components/WizardForm/Wizard.styled.ts | 216 ++++++++ .../login/src/components/WizardForm/index.tsx | 472 ++++++++++++++++++ 2 files changed, 688 insertions(+) create mode 100644 packages/login/src/components/WizardForm/Wizard.styled.ts create mode 100644 packages/login/src/components/WizardForm/index.tsx diff --git a/packages/login/src/components/WizardForm/Wizard.styled.ts b/packages/login/src/components/WizardForm/Wizard.styled.ts new file mode 100644 index 0000000000..27d564e829 --- /dev/null +++ b/packages/login/src/components/WizardForm/Wizard.styled.ts @@ -0,0 +1,216 @@ +// (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 { tablet, mobile } from "@docspace/shared/utils"; +import { isIOS, isFirefox } from "react-device-detect"; +import BackgroundPatternReactSvgUrl from "PUBLIC_DIR/images/background.pattern.react.svg?url"; + +export const Wrapper = styled.div` + height: ${isIOS && !isFirefox ? "calc(var(--vh, 1vh) * 100)" : "100vh"}; + width: 100vw; + z-index: 0; + display: flex; + flex-direction: column; + box-sizing: border-box; + + @media ${mobile} { + height: auto; + min-height: 100%; + width: 100%; + min-width: 100%; + } + + .bg-cover { + background-image: url("${BackgroundPatternReactSvgUrl}"); + background-repeat: no-repeat; + background-attachment: fixed; + background-size: cover; + position: fixed; + top: 0; + right: 0; + left: 0; + bottom: 0; + z-index: -1; + + @media ${mobile} { + background-image: none; + } + } +`; + +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} { + min-height: 100%; + width: calc(100% - 32px); + justify-content: start; + } +`; + +export const WizardContainer = styled.div` + margin: 56px auto; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + @media ${tablet} { + width: 100%; + max-width: 480px; + } + + @media ${mobile} { + max-width: 100%; + margin: 32px auto; + } + + .portal-logo { + display: flex; + align-items: center; + justify-content: center; + padding-bottom: 40px; + } + + .welcome-text { + padding-bottom: 32px; + + @media ${mobile} { + max-width: 343px; + } + } + + .form-header { + padding-bottom: 24px; + } + + .password-field-wrapper { + width: 100%; + } + + .wizard-field { + width: 100%; + } + + .password-field { + margin: 0px !important; + } + + .license-filed { + width: 100%; + margin-bottom: 20px; + } +`; + +export const StyledLink = styled.div` + width: 100%; + display: flex; + gap: 8px; + align-items: center; + padding-bottom: 16px; + padding-top: 8px; + + .generate-password-link { + color: ${(props) => props.theme.client.wizard.generatePasswordColor}; + } + + .icon-button_svg { + svg > g > path { + fill: ${(props) => props.theme.client.wizard.generatePasswordColor}; + } + } +`; + +export const StyledInfo = styled.div` + width: 100%; + display: grid; + grid-template-columns: 59px 1fr; + align-items: center; + gap: 16px; + + margin-bottom: 4px; + + .machine-name { + padding-bottom: 4px; + padding-top: 4px; + padding-left: 8px; + ${({ theme }) => + theme.interfaceDirection === "rtl" + ? `padding-right: 8px;` + : `padding-left: 8px;`} + line-height: 20px; + } + + .combo-button { + ${({ theme }) => + theme.interfaceDirection === "rtl" + ? `padding-right: 8px;` + : `padding-left: 8px;`} + } + + .wrapper__language-selector { + display: flex; + align-items: center; + gap: 2px; + } + + .combo-button-label { + max-width: 220px; + + @media ${tablet} { + max-width: 300px; + } + + @media ${mobile} { + max-width: 220px; + } + } +`; + +export const StyledAcceptTerms = styled.div` + width: 100%; + display: flex; + align-items: center; + gap: 0.3em; + padding-top: 20px; + padding-bottom: 24px; + + .wizard-checkbox svg { + ${({ theme }) => + theme.interfaceDirection === "rtl" + ? `margin-left: 8px;` + : `margin-right: 8px;`} + } +`; diff --git a/packages/login/src/components/WizardForm/index.tsx b/packages/login/src/components/WizardForm/index.tsx new file mode 100644 index 0000000000..27b1fdcd58 --- /dev/null +++ b/packages/login/src/components/WizardForm/index.tsx @@ -0,0 +1,472 @@ +// (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 { TCulturesOption, TTimeZoneOption } from "@/types"; +import { getSelectZone, getUserTimezone, mapTimezonesToArray } from "@/utils"; +import { + DEFAULT_SELECT_LANGUAGE, + DEFAULT_SELECT_TIMEZONE, + URL_LICENSE, +} from "@/utils/constants"; +import { + TPasswordHash, + TPasswordSettings, + TPortalCultures, + TTimeZone, +} from "@docspace/shared/api/settings/types"; +import { + convertLanguage, + createPasswordHash, + mapCulturesToArray, +} from "@docspace/shared/utils/common"; +import { Text } from "@docspace/shared/components/text"; +import { FieldContainer } from "@docspace/shared/components/field-container"; +import { ChangeEvent, MouseEvent, useEffect, useRef, useState } from "react"; +import { EmailInput, TValidate } from "@docspace/shared/components/email-input"; +import { useTranslation } from "react-i18next"; +import { + COOKIE_EXPIRATION_YEAR, + LANGUAGE, + PRODUCT_NAME, +} from "@docspace/shared/constants"; +import { EmailSettings } from "@docspace/shared/utils"; +import { + PasswordInput, + PasswordInputHandle, +} from "@docspace/shared/components/password-input"; +import { FileInput } from "@docspace/shared/components/file-input"; +import { StyledAcceptTerms, StyledInfo, StyledLink } from "./Wizard.styled"; +import { IconButton } from "@docspace/shared/components/icon-button"; +import { Link, LinkTarget, LinkType } from "@docspace/shared/components/link"; +import RefreshReactSvgUrl from "PUBLIC_DIR/images/refresh.react.svg?url"; +import { setLicense } from "@docspace/shared/api/settings"; +import { ComboBox, ComboBoxSize } from "@docspace/shared/components/combobox"; +import BetaBadge from "@docspace/client/src/components/BetaBadgeWrapper"; +import { isMobile } from "@docspace/shared/utils"; +import { Checkbox } from "@docspace/shared/components/checkbox"; +import { useTheme } from "styled-components"; +import { Button, ButtonSize } from "@docspace/shared/components/button"; +import api from "@docspace/shared/api"; +import { setCookie } from "@docspace/shared/utils/cookie"; +import { InputSize, InputType } from "@docspace/shared/components/text-input"; + +type WizardFormProps = { + passwordSettings?: TPasswordSettings; + machineName?: string; + isRequiredLicense?: boolean; + portalTimeZones?: TTimeZone[]; + portalCultures?: TPortalCultures; + culture?: string; + wizardToken?: string; + passwordHash?: TPasswordHash; +}; + +const emailSettings = new EmailSettings(); +emailSettings.allowDomainPunycode = true; + +function WizardForm(props: WizardFormProps) { + const { + passwordSettings, + machineName, + isRequiredLicense, + portalTimeZones, + portalCultures, + culture, + wizardToken, + passwordHash, + } = props; + + const [selectedTimezone, setSelectedTimezone] = useState( + DEFAULT_SELECT_TIMEZONE, + ); + const [selectedLanguage, setSelectedLanguage] = useState( + DEFAULT_SELECT_LANGUAGE, + ); + const [cultures, setCultures] = useState(); + const [timezones, setTimezones] = useState(); + + const [email, setEmail] = useState(""); + const [hasErrorEmail, setHasErrorEmail] = useState(false); + const [password, setPassword] = useState(""); + const [hasErrorPass, setHasErrorPass] = useState(false); + const [hasErrorLicense, setHasErrorLicense] = useState(false); + const [invalidLicense, setInvalidLicense] = useState(false); + const [licenseUpload, setLicenseUpload] = useState(null); + const [agreeTerms, setAgreeTerms] = useState(false); + const [hasErrorAgree, setHasErrorAgree] = useState(false); + const [isCreated, setIsCreated] = useState(false); + + const { t, i18n } = useTranslation(["Wizard", "Common"]); + const theme = useTheme(); + + const refPassInput = useRef(null); + + //TODO: add property + const userCulture = window.navigator + ? window.navigator.language + : culture || "en"; + + const convertedCulture = convertLanguage(userCulture); + + useEffect(() => { + if (portalTimeZones) { + const userTimezone = getUserTimezone(); + const zones = mapTimezonesToArray(portalTimeZones); + const select = getSelectZone(zones, userTimezone); + setTimezones(zones); + + if (select.length === 0) { + setSelectedTimezone(DEFAULT_SELECT_TIMEZONE); + } else { + setSelectedTimezone(select[0]); + } + } + }, [portalTimeZones]); + + useEffect(() => { + if (portalCultures) { + const cultures = mapCulturesToArray(portalCultures, true, i18n); + const select = cultures.filter((lang) => lang.key === convertedCulture); + setCultures(cultures); + + if (select.length === 0) { + setSelectedLanguage(DEFAULT_SELECT_LANGUAGE); + } else { + setSelectedLanguage(select[0]); + } + } + }, [convertedCulture, i18n, portalCultures]); + + const onEmailChangeHandler = (result: TValidate): undefined => { + setHasErrorEmail(!result.isValid); + }; + + const onChangeEmail = (e: ChangeEvent) => { + setEmail(e.target.value); + }; + + const onChangePassword = (e: ChangeEvent) => { + setPassword(e.target.value); + }; + + const isValidPassHandler = (progressScore: boolean) => { + setHasErrorPass(!progressScore); + }; + + const generatePassword = (e: MouseEvent) => { + if (isCreated) return; + if (refPassInput.current === null) return; + + refPassInput.current.onGeneratePassword(e); + }; + + const onLanguageSelect = (lang: TCulturesOption) => { + setSelectedLanguage(lang); + }; + + const onTimezoneSelect = (timezone: TTimeZoneOption) => { + setSelectedTimezone(timezone); + }; + + const onLicenseFileHandler = (file: File | File[]) => { + if (licenseUpload) setLicenseUpload(null); + setHasErrorLicense(false); + setInvalidLicense(false); + + let fd = new FormData(); + fd.append("files", file as Blob); + + if (!wizardToken) return; + + setLicense(wizardToken, fd) + .then((res) => { + setLicenseUpload(res); + }) + .catch((e) => { + console.error(e); + setHasErrorLicense(true); + setInvalidLicense(true); + }); + }; + + const onAgreeTermsChange = () => { + if (hasErrorAgree && !agreeTerms) setHasErrorAgree(false); + setAgreeTerms(!agreeTerms); + }; + + const validateFields = () => { + let anyError = false; + const emptyEmail = email.trim() === ""; + const emptyPassword = password.trim() === ""; + + if (emptyEmail || emptyPassword) { + emptyEmail && setHasErrorEmail(true); + emptyPassword && setHasErrorPass(true); + anyError = true; + } + + if (!agreeTerms) { + setHasErrorAgree(true); + anyError = true; + } + + if (isRequiredLicense && licenseUpload === null) { + setHasErrorLicense(true); + anyError = true; + } + + if (anyError || hasErrorEmail || hasErrorPass) return false; + + return true; + }; + + const onContinueClick = async () => { + if (!validateFields()) return; + + setIsCreated(true); + + const emailTrim = email.trim(); + const analytics = true; + const hash = createPasswordHash(password, passwordHash); + + try { + await api.settings.setPortalOwner( + emailTrim, + hash, + selectedLanguage.key, + selectedTimezone.key, + wizardToken, + analytics, + ); + + setCookie(LANGUAGE, selectedLanguage.key.toString(), { + "max-age": COOKIE_EXPIRATION_YEAR, + }); + + //setWizardComplete(); + } catch (error) { + console.error(error); + setIsCreated(false); + } + }; + + return ( + <> + + {t("Wizard:Desc", { productName: PRODUCT_NAME })} + + + + + + + + + + + + + {t("GeneratePassword")} + + + + {isRequiredLicense && ( + + + + )} + + + + {t("Common:Domain")} + + + {machineName} + + + + + + {t("Common:Language")} + +
+ + {selectedLanguage?.isBeta && ( + + )} +
+
+ + + + {t("Timezone")} + + + + + + + + {t("LicenseLink")} + + + +