Web: Login: add new rooms login page

This commit is contained in:
Viktor Fomin 2022-02-03 01:18:24 +03:00
parent 6e5a1572b4
commit 2de4d2b96c
2 changed files with 599 additions and 13 deletions

View File

@ -1,7 +1,6 @@
import React, { useCallback, useEffect, useState } from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
import Box from "@appserver/components/box";
import Button from "@appserver/components/button";
import Text from "@appserver/components/text";
import TextInput from "@appserver/components/text-input";
@ -12,7 +11,6 @@ import HelpButton from "@appserver/components/help-button";
import PasswordInput from "@appserver/components/password-input";
import FieldContainer from "@appserver/components/field-container";
import SocialButton from "@appserver/components/social-button";
import FacebookButton from "@appserver/components/facebook-button";
import PageLayout from "@appserver/common/components/PageLayout";
import ForgotPasswordModalDialog from "./sub-components/forgot-password-modal-dialog";
import Register from "./sub-components/register-container";
@ -35,7 +33,6 @@ import {
} from "react-i18next";
import toastr from "@appserver/components/toast/toastr";
import MoreLoginModal from "./sub-components/more-login";
import { Trans } from "react-i18next";
import {
ButtonsWrapper,
LoginContainer,
@ -411,7 +408,66 @@ const Form = (props) => {
forwardedRef={inputRef}
/>
</FieldContainer>
<FieldContainer
isVertical={true}
labelVisible={false}
hasError={!passwordValid}
errorMessage={errorText ? "" : t("Common:RequiredField")} //TODO: Add wrong password server error
>
<PasswordInput
simpleView={true}
passwordSettings={settings}
id="password"
inputName="password"
placeholder={t("Common:Password")}
type="password"
hasError={!passwordValid}
inputValue={password}
size="large"
scale={true}
tabIndex={1}
isDisabled={isLoading}
autoComplete="current-password"
onChange={onChangePassword}
onKeyDown={onKeyDown}
/>
</FieldContainer>
<div className="login-forgot-wrapper">
<div className="login-checkbox-wrapper">
<Checkbox
className="login-checkbox"
isChecked={isChecked}
onChange={onChangeCheckbox}
label={<Text fontSize="13px">{t("Remember")}</Text>}
/>
<HelpButton
className="login-tooltip"
helpButtonHeaderContent={t("CookieSettingsTitle")}
tooltipContent={
<Text fontSize="12px">{t("RememberHelper")}</Text>
}
/>
<Link
fontSize="13px"
color="#316DAA"
className="login-link"
type="page"
isHovered={false}
onClick={onClick}
>
{t("ForgotPassword")}
</Link>
</div>
</div>
{isDialogVisible && (
<ForgotPasswordModalDialog
visible={isDialogVisible}
email={identifier}
onDialogClose={onDialogClose}
/>
)}
<Button
id="submit"
className="login-button"
@ -425,16 +481,6 @@ const Form = (props) => {
onClick={onSubmit}
/>
<Text color="#A3A9AE" fontSize="12px">
<Trans t={t} i18nKey="LoginDescription" ns="Login">
We'll email you a magic code to sign in without a password. Or you
can
<Link color="#2DA7DB;" fontSize="12px" href="">
log in manually.
</Link>
</Trans>
</Text>
{confirmedEmail && (
<Text isBold={true} fontSize="16px">
{t("MessageEmailConfirmed")} {t("MessageAuthorize")}

View File

@ -0,0 +1,540 @@
import React, { useCallback, useEffect, useState } from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
import Box from "@appserver/components/box";
import Button from "@appserver/components/button";
import Text from "@appserver/components/text";
import TextInput from "@appserver/components/text-input";
import Link from "@appserver/components/link";
import Checkbox from "@appserver/components/checkbox";
import Toast from "@appserver/components/toast";
import HelpButton from "@appserver/components/help-button";
import PasswordInput from "@appserver/components/password-input";
import FieldContainer from "@appserver/components/field-container";
import SocialButton from "@appserver/components/social-button";
import FacebookButton from "@appserver/components/facebook-button";
import PageLayout from "@appserver/common/components/PageLayout";
import ForgotPasswordModalDialog from "./sub-components/forgot-password-modal-dialog";
import Register from "./sub-components/register-container";
import {
getAuthProviders,
getCapabilities,
} from "@appserver/common/api/settings";
import { checkPwd } from "@appserver/common/desktop";
import {
createPasswordHash,
getProviderTranslation,
} from "@appserver/common/utils";
import { providersData } from "@appserver/common/constants";
import { inject, observer } from "mobx-react";
import i18n from "./i18n";
import {
I18nextProvider,
useTranslation,
withTranslation,
} from "react-i18next";
import toastr from "@appserver/components/toast/toastr";
import MoreLoginModal from "./sub-components/more-login";
import { Trans } from "react-i18next";
import {
ButtonsWrapper,
LoginContainer,
LoginFormWrapper,
} from "./StyledLogin";
const settings = {
minLength: 6,
upperCase: false,
digits: false,
specSymbols: false,
};
const Form = (props) => {
const inputRef = React.useRef(null);
const [identifierValid, setIdentifierValid] = useState(true);
const [identifier, setIdentifier] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [isDisabled, setIsDisabled] = useState(false);
const [passwordValid, setPasswordValid] = useState(true);
const [password, setPassword] = useState("");
const [isChecked, setIsChecked] = useState(false);
const [isDialogVisible, setIsDialogVisible] = useState(false);
const [moreAuthVisible, setMoreAuthVisible] = useState(false);
const [ssoLabel, setSsoLabel] = useState("");
const [ssoUrl, setSsoUrl] = useState("");
const [errorText, setErrorText] = useState("");
const { t } = useTranslation("Login");
const {
login,
hashSettings,
isDesktop,
match,
organizationName,
greetingTitle,
history,
thirdPartyLogin,
providers,
} = props;
const { error, confirmedEmail } = match.params;
const oAuthLogin = async (profile) => {
try {
await thirdPartyLogin(profile);
const redirectPath = localStorage.getItem("redirectPath");
if (redirectPath) {
localStorage.removeItem("redirectPath");
window.location.href = redirectPath;
}
} catch (e) {
toastr.error(
t("Common:ProviderNotConnected"),
t("Common:ProviderLoginError")
);
}
localStorage.removeItem("profile");
localStorage.removeItem("code");
};
const getSso = async () => {
const data = await getCapabilities();
console.log(data);
setSsoLabel(data.ssoLabel);
setSsoUrl(data.ssoUrl);
};
useEffect(() => {
getSso();
const profile = localStorage.getItem("profile");
if (!profile) return;
oAuthLogin(profile);
}, []);
const onKeyDown = (e) => {
//console.log("onKeyDown", e.key);
if (e.key === "Enter") {
onClearErrors(e);
!isDisabled && onSubmit(e);
e.preventDefault();
}
};
const onClearErrors = (e) => {
//console.log("onClearErrors", e);
!identifierValid && setIdentifierValid(true);
!passwordValid && setPasswordValid(true);
errorText && setErrorText("");
};
//const throttledKeyPress = throttle(onKeyPress, 500);
const authCallback = (profile) => {
oAuthLogin(profile);
};
const setProviders = async () => {
const { setProviders } = props;
try {
await getAuthProviders().then((providers) => {
setProviders(providers);
});
} catch (e) {
console.error(e);
}
};
useEffect(async () => {
document.title = `${t("Authorization")} ${organizationName}`; //TODO: implement the setDocumentTitle() utility in ASC.Web.Common
error && setErrorText(error);
confirmedEmail && setIdentifier(confirmedEmail);
focusInput();
window.authCallback = authCallback;
await setProviders();
//window.addEventListener("keyup", throttledKeyPress, false);
/*return () => {
window.removeEventListener("keyup", throttledKeyPress, false);
};*/
}, []);
const focusInput = () => {
if (inputRef) {
inputRef.current.focus();
}
};
const onChangeLogin = (e) => {
//console.log("onChangeLogin", e.target.value);
setIdentifier(e.target.value);
onClearErrors(e);
};
const onChangePassword = (e) => {
//console.log("onChangePassword", e.target.value);
setPassword(e.target.value);
onClearErrors(e);
};
const onChangeCheckbox = () => setIsChecked(!isChecked);
const onClick = () => {
setIsDialogVisible(true);
setIsDisabled(true);
};
const onDialogClose = () => {
setIsDialogVisible(false);
setIsDisabled(false);
setIsLoading(false);
};
const moreAuthOpen = () => {
setMoreAuthVisible(true);
};
const moreAuthClose = () => {
setMoreAuthVisible(false);
};
const onSubmit = () => {
errorText && setErrorText("");
let hasError = false;
const userName = identifier.trim();
if (!userName) {
hasError = true;
setIdentifierValid(false);
}
const pass = password.trim();
if (!pass) {
hasError = true;
setPasswordValid(false);
}
if (hasError) return false;
setIsLoading(true);
const hash = createPasswordHash(pass, hashSettings);
isDesktop && checkPwd();
const session = !isChecked;
login(userName, hash, session)
.then((res) => {
const { url, user, hash } = res;
const redirectPath = localStorage.getItem("redirectPath");
if (redirectPath) {
localStorage.removeItem("redirectPath");
window.location.href = redirectPath;
} else history.push(url, { user, hash });
})
.catch((error) => {
setErrorText(error);
setIdentifierValid(!error);
setPasswordValid(!error);
setIsLoading(false);
focusInput();
});
};
const onSocialButtonClick = useCallback((e) => {
const providerName = e.target.dataset.providername;
const url = e.target.dataset.url;
const { getOAuthToken, getLoginLink } = props;
try {
const tokenGetterWin = isDesktop
? (window.location.href = url)
: window.open(
url,
"login",
"width=800,height=500,status=no,toolbar=no,menubar=no,resizable=yes,scrollbars=no"
);
getOAuthToken(tokenGetterWin).then((code) => {
const token = window.btoa(
JSON.stringify({
auth: providerName,
mode: "popup",
callback: "authCallback",
})
);
tokenGetterWin.location.href = getLoginLink(token, code);
});
} catch (err) {
console.log(err);
}
}, []);
const providerButtons = () => {
const providerButtons =
providers &&
providers.map((item, index) => {
if (!providersData[item.provider]) return;
if (index > 1) return;
const { icon, label, iconOptions, className } = providersData[
item.provider
];
return (
<div className="buttonWrapper" key={`${item.provider}ProviderItem`}>
<SocialButton
iconName={icon}
label={getProviderTranslation(label, t)}
className={`socialButton ${className ? className : ""}`}
$iconOptions={iconOptions}
data-url={item.url}
data-providername={item.provider}
onClick={onSocialButtonClick}
/>
</div>
);
});
return providerButtons;
};
const ssoButton = () => {
return (
<div className="buttonWrapper">
<SocialButton
iconName="/static/images/sso.react.svg"
className="socialButton"
label={ssoLabel || getProviderTranslation("sso", t)}
onClick={() => (window.location.href = ssoUrl)}
/>
</div>
);
};
const oauthDataExists = () => {
let existProviders = 0;
providers && providers.length > 0;
providers.map((item) => {
if (!providersData[item.provider]) return;
existProviders++;
});
return !!existProviders;
};
const ssoExists = () => {
if (ssoUrl) return true;
else return false;
};
//console.log("Login render");
return (
<LoginContainer>
<Text
fontSize="23px"
fontWeight={700}
textAlign="center"
className="greeting-title"
>
{greetingTitle}
</Text>
{ssoExists() && <ButtonsWrapper>{ssoButton()}</ButtonsWrapper>}
{oauthDataExists() && (
<>
<ButtonsWrapper>{providerButtons()}</ButtonsWrapper>
{providers && providers.length > 2 && (
<Link
isHovered
type="action"
fontSize="13px"
fontWeight="600"
color="#3B72A7"
className="more-label"
onClick={moreAuthOpen}
>
{t("ShowMore")}
</Link>
)}
</>
)}
{(oauthDataExists() || ssoExists()) && (
<div className="line">
<Text color="#A3A9AE" className="or-label">
{t("Or")}
</Text>
</div>
)}
<form className="auth-form-container">
<FieldContainer
isVertical={true}
labelVisible={false}
hasError={!identifierValid}
errorMessage={errorText ? errorText : t("Common:RequiredField")} //TODO: Add wrong login server error
>
<TextInput
id="login"
name="login"
type="email"
hasError={!identifierValid}
value={identifier}
placeholder={t("RegistrationEmailWatermark")}
size="large"
scale={true}
isAutoFocussed={true}
tabIndex={1}
isDisabled={isLoading}
autoComplete="username"
onChange={onChangeLogin}
onKeyDown={onKeyDown}
forwardedRef={inputRef}
/>
</FieldContainer>
<Button
id="submit"
className="login-button"
primary
size="large"
scale={true}
label={isLoading ? t("Common:LoadingProcessing") : t("LoginButton")}
tabIndex={1}
isDisabled={isLoading}
isLoading={isLoading}
onClick={onSubmit}
/>
<Text color="#A3A9AE" fontSize="12px">
<Trans t={t} i18nKey="LoginDescription" ns="Login">
We'll email you a magic code to sign in without a password. Or you
can
<Link color="#2DA7DB;" fontSize="12px" href="">
log in manually.
</Link>
</Trans>
</Text>
{confirmedEmail && (
<Text isBold={true} fontSize="16px">
{t("MessageEmailConfirmed")} {t("MessageAuthorize")}
</Text>
)}
</form>
<Toast />
<MoreLoginModal
t={t}
visible={moreAuthVisible}
onClose={moreAuthClose}
providers={providers}
onSocialLoginClick={onSocialButtonClick}
ssoLabel={ssoLabel}
ssoUrl={ssoUrl}
/>
</LoginContainer>
);
};
Form.propTypes = {
login: PropTypes.func.isRequired,
match: PropTypes.object.isRequired,
hashSettings: PropTypes.object,
greetingTitle: PropTypes.string.isRequired,
organizationName: PropTypes.string,
homepage: PropTypes.string,
defaultPage: PropTypes.string,
isDesktop: PropTypes.bool,
};
Form.defaultProps = {
identifier: "",
password: "",
email: "",
};
const LoginForm = (props) => {
const { enabledJoin, isDesktop } = props;
return (
<LoginFormWrapper enabledJoin={enabledJoin} isDesktop={isDesktop}>
<PageLayout>
<PageLayout.SectionBody>
<Form {...props} />
</PageLayout.SectionBody>
</PageLayout>
<Register />
</LoginFormWrapper>
);
};
LoginForm.propTypes = {
isLoaded: PropTypes.bool,
enabledJoin: PropTypes.bool,
isDesktop: PropTypes.bool.isRequired,
};
const Login = inject(({ auth }) => {
const {
settingsStore,
isAuthenticated,
isLoaded,
login,
thirdPartyLogin,
setProviders,
providers,
} = auth;
const {
greetingSettings: greetingTitle,
organizationName,
hashSettings,
enabledJoin,
defaultPage,
isDesktopClient: isDesktop,
getOAuthToken,
getLoginLink,
} = settingsStore;
return {
isAuthenticated,
isLoaded,
organizationName,
greetingTitle,
hashSettings,
enabledJoin,
defaultPage,
isDesktop,
login,
thirdPartyLogin,
getOAuthToken,
getLoginLink,
setProviders,
providers,
};
})(withRouter(observer(withTranslation(["Login", "Common"])(LoginForm))));
export default (props) => (
<I18nextProvider i18n={i18n}>
<Login {...props} />
</I18nextProvider>
);