|
|
@@ -1,10 +1,7 @@
|
|
|
-import React, {
|
|
|
- useState, useEffect, useCallback, type JSX,
|
|
|
-} from 'react';
|
|
|
-
|
|
|
+import React, { type JSX, useCallback, useEffect, useState } from 'react';
|
|
|
+import { useRouter } from 'next/router';
|
|
|
import { LoadingSpinner } from '@growi/ui/dist/components';
|
|
|
import { useTranslation } from 'next-i18next';
|
|
|
-import { useRouter } from 'next/router';
|
|
|
import ReactCardFlip from 'react-card-flip';
|
|
|
|
|
|
import { apiv3Post } from '~/client/util/apiv3-client';
|
|
|
@@ -17,43 +14,50 @@ import { RegistrationMode } from '~/interfaces/registration-mode';
|
|
|
import { toArrayIfNot } from '~/utils/array-utils';
|
|
|
|
|
|
import { CompleteUserRegistration } from '../CompleteUserRegistration';
|
|
|
-
|
|
|
import { ExternalAuthButton } from './ExternalAuthButton';
|
|
|
|
|
|
import styles from './LoginForm.module.scss';
|
|
|
|
|
|
-
|
|
|
const moduleClass = styles['login-form'];
|
|
|
|
|
|
-
|
|
|
type LoginFormProps = {
|
|
|
- username?: string,
|
|
|
- name?: string,
|
|
|
- email?: string,
|
|
|
- isEmailAuthenticationEnabled: boolean,
|
|
|
- registrationMode: RegistrationMode,
|
|
|
- registrationWhitelist: string[],
|
|
|
- isPasswordResetEnabled: boolean,
|
|
|
- isLocalStrategySetup: boolean,
|
|
|
- isLdapStrategySetup: boolean,
|
|
|
- isLdapSetupFailed: boolean,
|
|
|
- enabledExternalAuthType?: IExternalAuthProviderType[],
|
|
|
- isMailerSetup?: boolean,
|
|
|
- externalAccountLoginError?: IExternalAccountLoginError,
|
|
|
- minPasswordLength: number,
|
|
|
-}
|
|
|
+ username?: string;
|
|
|
+ name?: string;
|
|
|
+ email?: string;
|
|
|
+ isEmailAuthenticationEnabled: boolean;
|
|
|
+ registrationMode: RegistrationMode;
|
|
|
+ registrationWhitelist: string[];
|
|
|
+ isPasswordResetEnabled: boolean;
|
|
|
+ isLocalStrategySetup: boolean;
|
|
|
+ isLdapStrategySetup: boolean;
|
|
|
+ isLdapSetupFailed: boolean;
|
|
|
+ enabledExternalAuthType?: IExternalAuthProviderType[];
|
|
|
+ isMailerSetup?: boolean;
|
|
|
+ externalAccountLoginError?: IExternalAccountLoginError;
|
|
|
+ minPasswordLength: number;
|
|
|
+};
|
|
|
export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
const { t } = useTranslation();
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
const {
|
|
|
- isLocalStrategySetup, isLdapStrategySetup, isLdapSetupFailed, isPasswordResetEnabled,
|
|
|
- isEmailAuthenticationEnabled, registrationMode, registrationWhitelist, isMailerSetup, enabledExternalAuthType, minPasswordLength,
|
|
|
+ isLocalStrategySetup,
|
|
|
+ isLdapStrategySetup,
|
|
|
+ isLdapSetupFailed,
|
|
|
+ isPasswordResetEnabled,
|
|
|
+ isEmailAuthenticationEnabled,
|
|
|
+ registrationMode,
|
|
|
+ registrationWhitelist,
|
|
|
+ isMailerSetup,
|
|
|
+ enabledExternalAuthType,
|
|
|
+ minPasswordLength,
|
|
|
} = props;
|
|
|
|
|
|
- const isLocalOrLdapStrategiesEnabled = isLocalStrategySetup || isLdapStrategySetup;
|
|
|
- const isSomeExternalAuthEnabled = enabledExternalAuthType != null && enabledExternalAuthType.length > 0;
|
|
|
+ const isLocalOrLdapStrategiesEnabled =
|
|
|
+ isLocalStrategySetup || isLdapStrategySetup;
|
|
|
+ const isSomeExternalAuthEnabled =
|
|
|
+ enabledExternalAuthType != null && enabledExternalAuthType.length > 0;
|
|
|
|
|
|
// states
|
|
|
const [isRegistering, setIsRegistering] = useState(false);
|
|
|
@@ -69,11 +73,13 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
const [passwordForRegister, setPasswordForRegister] = useState('');
|
|
|
const [registerErrors, setRegisterErrors] = useState<IErrorV3[]>([]);
|
|
|
// For UserActivation
|
|
|
- const [emailForRegistrationOrder, setEmailForRegistrationOrder] = useState('');
|
|
|
+ const [emailForRegistrationOrder, setEmailForRegistrationOrder] =
|
|
|
+ useState('');
|
|
|
|
|
|
const [isSuccessToRagistration, setIsSuccessToRagistration] = useState(false);
|
|
|
|
|
|
- const isRegistrationEnabled = isLocalStrategySetup && registrationMode !== RegistrationMode.CLOSED;
|
|
|
+ const isRegistrationEnabled =
|
|
|
+ isLocalStrategySetup && registrationMode !== RegistrationMode.CLOSED;
|
|
|
|
|
|
const tWithOpt = useTWithOpt();
|
|
|
|
|
|
@@ -89,34 +95,35 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
setLoginErrors([]);
|
|
|
}, [loginErrors.length]);
|
|
|
|
|
|
- const handleLoginWithLocalSubmit = useCallback(async(e) => {
|
|
|
- e.preventDefault();
|
|
|
- resetLoginErrors();
|
|
|
- setIsLoading(true);
|
|
|
-
|
|
|
- const loginForm = {
|
|
|
- username: usernameForLogin,
|
|
|
- password: passwordForLogin,
|
|
|
- };
|
|
|
+ const handleLoginWithLocalSubmit = useCallback(
|
|
|
+ async (e) => {
|
|
|
+ e.preventDefault();
|
|
|
+ resetLoginErrors();
|
|
|
+ setIsLoading(true);
|
|
|
|
|
|
- try {
|
|
|
- const res = await apiv3Post('/login', { loginForm });
|
|
|
- const { redirectTo } = res.data;
|
|
|
+ const loginForm = {
|
|
|
+ username: usernameForLogin,
|
|
|
+ password: passwordForLogin,
|
|
|
+ };
|
|
|
|
|
|
- if (redirectTo != null) {
|
|
|
- return router.push(redirectTo);
|
|
|
- }
|
|
|
+ try {
|
|
|
+ const res = await apiv3Post('/login', { loginForm });
|
|
|
+ const { redirectTo } = res.data;
|
|
|
|
|
|
- return router.push('/');
|
|
|
- }
|
|
|
- catch (err) {
|
|
|
- const errs = toArrayIfNot(err);
|
|
|
- setLoginErrors(errs);
|
|
|
- setIsLoading(false);
|
|
|
- }
|
|
|
- return;
|
|
|
+ if (redirectTo != null) {
|
|
|
+ return router.push(redirectTo);
|
|
|
+ }
|
|
|
|
|
|
- }, [passwordForLogin, resetLoginErrors, router, usernameForLogin]);
|
|
|
+ return router.push('/');
|
|
|
+ } catch (err) {
|
|
|
+ const errs = toArrayIfNot(err);
|
|
|
+ setLoginErrors(errs);
|
|
|
+ setIsLoading(false);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ },
|
|
|
+ [passwordForLogin, resetLoginErrors, router, usernameForLogin],
|
|
|
+ );
|
|
|
|
|
|
// separate errors based on error code
|
|
|
const separateErrorsBasedOnErrorCode = useCallback((errors: IErrorV3[]) => {
|
|
|
@@ -126,8 +133,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
errors.forEach((err) => {
|
|
|
if (err.code === LoginErrorCode.PROVIDER_DUPLICATED_USERNAME_EXCEPTION) {
|
|
|
loginErrorListForDangerouslySetInnerHTML.push(err);
|
|
|
- }
|
|
|
- else {
|
|
|
+ } else {
|
|
|
loginErrorList.push(err);
|
|
|
}
|
|
|
});
|
|
|
@@ -136,31 +142,48 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
}, []);
|
|
|
|
|
|
// wrap error elements which use dangerouslySetInnerHtml
|
|
|
- const generateDangerouslySetErrors = useCallback((errors: IErrorV3[]): JSX.Element => {
|
|
|
- if (errors == null || errors.length === 0) return <></>;
|
|
|
- return (
|
|
|
- <div className="alert alert-danger">
|
|
|
- {errors.map((err) => {
|
|
|
- // eslint-disable-next-line react/no-danger
|
|
|
- return <small dangerouslySetInnerHTML={{ __html: tWithOpt(err.message, err.args) }}></small>;
|
|
|
- })}
|
|
|
- </div>
|
|
|
- );
|
|
|
- }, [tWithOpt]);
|
|
|
+ const generateDangerouslySetErrors = useCallback(
|
|
|
+ (errors: IErrorV3[]): JSX.Element => {
|
|
|
+ if (errors == null || errors.length === 0) return <></>;
|
|
|
+ return (
|
|
|
+ <div className="alert alert-danger">
|
|
|
+ {errors.map((err, index) => {
|
|
|
+ // eslint-disable-next-line react/no-danger
|
|
|
+ return (
|
|
|
+ <small
|
|
|
+ key={`${err.code}-${index}`}
|
|
|
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: rendered HTML from translations
|
|
|
+ dangerouslySetInnerHTML={{
|
|
|
+ __html: tWithOpt(err.message, err.args),
|
|
|
+ }}
|
|
|
+ ></small>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ },
|
|
|
+ [tWithOpt],
|
|
|
+ );
|
|
|
|
|
|
// wrap error elements which do not use dangerouslySetInnerHtml
|
|
|
- const generateSafelySetErrors = useCallback((errors: (IErrorV3 | IExternalAccountLoginError)[]): JSX.Element => {
|
|
|
- if (errors == null || errors.length === 0) return <></>;
|
|
|
- return (
|
|
|
- <ul className="alert alert-danger">
|
|
|
- {errors.map((err, index) => (
|
|
|
- <small className={index > 0 ? 'mt-1' : ''}>
|
|
|
- {tWithOpt(err.message, err.args)}
|
|
|
- </small>
|
|
|
- ))}
|
|
|
- </ul>
|
|
|
- );
|
|
|
- }, [tWithOpt]);
|
|
|
+ const generateSafelySetErrors = useCallback(
|
|
|
+ (errors: (IErrorV3 | IExternalAccountLoginError)[]): JSX.Element => {
|
|
|
+ if (errors == null || errors.length === 0) return <></>;
|
|
|
+ return (
|
|
|
+ <ul className="alert alert-danger">
|
|
|
+ {errors.map((err, index) => (
|
|
|
+ <small
|
|
|
+ key={`${err.message}-${index}`}
|
|
|
+ className={index > 0 ? 'mt-1' : ''}
|
|
|
+ >
|
|
|
+ {tWithOpt(err.message, err.args)}
|
|
|
+ </small>
|
|
|
+ ))}
|
|
|
+ </ul>
|
|
|
+ );
|
|
|
+ },
|
|
|
+ [tWithOpt],
|
|
|
+ );
|
|
|
|
|
|
const renderLocalOrLdapLoginForm = useCallback(() => {
|
|
|
const { isLdapStrategySetup } = props;
|
|
|
@@ -175,16 +198,30 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
{/* !! - END OF HIDDEN ELEMENT - !! */}
|
|
|
{isLdapSetupFailed && (
|
|
|
<div className="alert alert-warning small">
|
|
|
- <strong><span className="material-symbols-outlined">info</span>{t('login.enabled_ldap_has_configuration_problem')}</strong><br />
|
|
|
+ <strong>
|
|
|
+ <span className="material-symbols-outlined">info</span>
|
|
|
+ {t('login.enabled_ldap_has_configuration_problem')}
|
|
|
+ </strong>
|
|
|
+ <br />
|
|
|
{/* eslint-disable-next-line react/no-danger */}
|
|
|
- <span dangerouslySetInnerHTML={{ __html: t('login.set_env_var_for_logs') }}></span>
|
|
|
+ <span
|
|
|
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: rendered HTML from translations
|
|
|
+ dangerouslySetInnerHTML={{
|
|
|
+ __html: t('login.set_env_var_for_logs'),
|
|
|
+ }}
|
|
|
+ ></span>
|
|
|
</div>
|
|
|
)}
|
|
|
|
|
|
- <form role="form" onSubmit={handleLoginWithLocalSubmit} id="login-form">
|
|
|
+ <form onSubmit={handleLoginWithLocalSubmit} id="login-form">
|
|
|
<div className="input-group">
|
|
|
- <label className="text-white opacity-75 d-flex align-items-center" htmlFor="tiUsernameForLogin">
|
|
|
- <span className="material-symbols-outlined" aria-label="Username or E-mail">person</span>
|
|
|
+ <label
|
|
|
+ className="text-white opacity-75 d-flex align-items-center"
|
|
|
+ htmlFor="tiUsernameForLogin"
|
|
|
+ >
|
|
|
+ <span className="material-symbols-outlined" aria-hidden="true">
|
|
|
+ person
|
|
|
+ </span>
|
|
|
</label>
|
|
|
<input
|
|
|
id="tiUsernameForLogin"
|
|
|
@@ -192,7 +229,9 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
className={`form-control rounded ms-2 ${isLdapStrategySetup ? 'ldap-space' : ''}`}
|
|
|
data-testid="tiUsernameForLogin"
|
|
|
placeholder="Username or E-mail"
|
|
|
- onChange={(e) => { setUsernameForLogin(e.target.value) }}
|
|
|
+ onChange={(e) => {
|
|
|
+ setUsernameForLogin(e.target.value);
|
|
|
+ }}
|
|
|
name="usernameForLogin"
|
|
|
/>
|
|
|
{isLdapStrategySetup && (
|
|
|
@@ -201,12 +240,16 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
<span className="">LDAP</span>
|
|
|
</small>
|
|
|
)}
|
|
|
-
|
|
|
</div>
|
|
|
|
|
|
<div className="input-group">
|
|
|
- <label className="text-white opacity-75 d-flex align-items-center" htmlFor="tiPasswordForLogin">
|
|
|
- <span className="material-symbols-outlined" aria-label="Password">lock</span>
|
|
|
+ <label
|
|
|
+ className="text-white opacity-75 d-flex align-items-center"
|
|
|
+ htmlFor="tiPasswordForLogin"
|
|
|
+ >
|
|
|
+ <span className="material-symbols-outlined" aria-hidden="true">
|
|
|
+ lock
|
|
|
+ </span>
|
|
|
</label>
|
|
|
<input
|
|
|
id="tiPasswordForLogin"
|
|
|
@@ -214,7 +257,9 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
className="form-control rounded ms-2"
|
|
|
data-testid="tiPasswordForLogin"
|
|
|
placeholder="Password"
|
|
|
- onChange={(e) => { setPasswordForLogin(e.target.value) }}
|
|
|
+ onChange={(e) => {
|
|
|
+ setPasswordForLogin(e.target.value);
|
|
|
+ }}
|
|
|
name="passwordForLogin"
|
|
|
/>
|
|
|
</div>
|
|
|
@@ -230,7 +275,12 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
{isLoading ? (
|
|
|
<LoadingSpinner />
|
|
|
) : (
|
|
|
- <span className="material-symbols-outlined" aria-label="Login">login</span>
|
|
|
+ <span
|
|
|
+ className="material-symbols-outlined"
|
|
|
+ aria-hidden="true"
|
|
|
+ >
|
|
|
+ login
|
|
|
+ </span>
|
|
|
)}
|
|
|
</span>
|
|
|
<span className="flex-grow-1">{t('Sign in')}</span>
|
|
|
@@ -239,10 +289,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
</form>
|
|
|
</>
|
|
|
);
|
|
|
- }, [
|
|
|
- props, isLdapSetupFailed, t, handleLoginWithLocalSubmit, isLoading,
|
|
|
- ]);
|
|
|
-
|
|
|
+ }, [props, isLdapSetupFailed, t, handleLoginWithLocalSubmit, isLoading]);
|
|
|
|
|
|
const renderExternalAuthLoginForm = useCallback(() => {
|
|
|
const { enabledExternalAuthType } = props;
|
|
|
@@ -254,7 +301,9 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
return (
|
|
|
<>
|
|
|
<div className="mt-2">
|
|
|
- {enabledExternalAuthType.map(authType => <ExternalAuthButton authType={authType} />)}
|
|
|
+ {enabledExternalAuthType.map((authType) => (
|
|
|
+ <ExternalAuthButton key={authType} authType={authType} />
|
|
|
+ ))}
|
|
|
</div>
|
|
|
</>
|
|
|
);
|
|
|
@@ -265,45 +314,55 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
setRegisterErrors([]);
|
|
|
}, [registerErrors.length]);
|
|
|
|
|
|
- const handleRegisterFormSubmit = useCallback(async(e, requestPath) => {
|
|
|
- e.preventDefault();
|
|
|
- setEmailForRegistrationOrder('');
|
|
|
- setIsSuccessToRagistration(false);
|
|
|
- setIsLoading(true);
|
|
|
-
|
|
|
- const registerForm = {
|
|
|
- username: usernameForRegister,
|
|
|
- name: nameForRegister,
|
|
|
- email: emailForRegister,
|
|
|
- password: passwordForRegister,
|
|
|
- };
|
|
|
- try {
|
|
|
- const res = await apiv3Post(requestPath, { registerForm });
|
|
|
-
|
|
|
- setIsSuccessToRagistration(true);
|
|
|
- resetRegisterErrors();
|
|
|
- setIsLoading(false);
|
|
|
-
|
|
|
- const { redirectTo } = res.data;
|
|
|
-
|
|
|
- if (redirectTo != null) {
|
|
|
- router.push(redirectTo);
|
|
|
- }
|
|
|
+ const handleRegisterFormSubmit = useCallback(
|
|
|
+ async (e, requestPath) => {
|
|
|
+ e.preventDefault();
|
|
|
+ setEmailForRegistrationOrder('');
|
|
|
+ setIsSuccessToRagistration(false);
|
|
|
+ setIsLoading(true);
|
|
|
+
|
|
|
+ const registerForm = {
|
|
|
+ username: usernameForRegister,
|
|
|
+ name: nameForRegister,
|
|
|
+ email: emailForRegister,
|
|
|
+ password: passwordForRegister,
|
|
|
+ };
|
|
|
+ try {
|
|
|
+ const res = await apiv3Post(requestPath, { registerForm });
|
|
|
+
|
|
|
+ setIsSuccessToRagistration(true);
|
|
|
+ resetRegisterErrors();
|
|
|
+ setIsLoading(false);
|
|
|
+
|
|
|
+ const { redirectTo } = res.data;
|
|
|
+
|
|
|
+ if (redirectTo != null) {
|
|
|
+ router.push(redirectTo);
|
|
|
+ }
|
|
|
|
|
|
- if (isEmailAuthenticationEnabled) {
|
|
|
- setEmailForRegistrationOrder(emailForRegister);
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- catch (err) {
|
|
|
- // Execute if error exists
|
|
|
- if (err != null || err.length > 0) {
|
|
|
- setRegisterErrors(err);
|
|
|
+ if (isEmailAuthenticationEnabled) {
|
|
|
+ setEmailForRegistrationOrder(emailForRegister);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ // Execute if error exists
|
|
|
+ if (err != null || err.length > 0) {
|
|
|
+ setRegisterErrors(err);
|
|
|
+ }
|
|
|
+ setIsLoading(false);
|
|
|
}
|
|
|
- setIsLoading(false);
|
|
|
- }
|
|
|
- return;
|
|
|
- }, [usernameForRegister, nameForRegister, emailForRegister, passwordForRegister, resetRegisterErrors, router, isEmailAuthenticationEnabled]);
|
|
|
+ return;
|
|
|
+ },
|
|
|
+ [
|
|
|
+ usernameForRegister,
|
|
|
+ nameForRegister,
|
|
|
+ emailForRegister,
|
|
|
+ passwordForRegister,
|
|
|
+ resetRegisterErrors,
|
|
|
+ router,
|
|
|
+ isEmailAuthenticationEnabled,
|
|
|
+ ],
|
|
|
+ );
|
|
|
|
|
|
const switchForm = useCallback(() => {
|
|
|
setIsRegistering(!isRegistering);
|
|
|
@@ -329,34 +388,37 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
{t('page_register.notice.restricted_defail')}
|
|
|
</p>
|
|
|
)}
|
|
|
- {(!isMailerSetup && isEmailAuthenticationEnabled) && (
|
|
|
+ {!isMailerSetup && isEmailAuthenticationEnabled && (
|
|
|
<p className="alert alert-danger">
|
|
|
<span>{t('commons:alert.please_enable_mailer')}</span>
|
|
|
</p>
|
|
|
)}
|
|
|
|
|
|
- {
|
|
|
- registerErrors != null && registerErrors.length > 0 && (
|
|
|
- <p className="alert alert-danger">
|
|
|
- {registerErrors.map(err => (
|
|
|
- <span>
|
|
|
- {tWithOpt(err.message, err.args)}<br />
|
|
|
- </span>
|
|
|
- ))}
|
|
|
- </p>
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- {
|
|
|
- (isEmailAuthenticationEnabled && isSuccessToRagistration) && (
|
|
|
- <p className="alert alert-success">
|
|
|
- <span>{t('message.successfully_send_email_auth', { email: emailForRegistrationOrder })}</span>
|
|
|
- </p>
|
|
|
- )
|
|
|
- }
|
|
|
+ {registerErrors != null && registerErrors.length > 0 && (
|
|
|
+ <p className="alert alert-danger">
|
|
|
+ {registerErrors.map((err, index) => (
|
|
|
+ <span key={`${err.message}-${index}`}>
|
|
|
+ {tWithOpt(err.message, err.args)}
|
|
|
+ <br />
|
|
|
+ </span>
|
|
|
+ ))}
|
|
|
+ </p>
|
|
|
+ )}
|
|
|
|
|
|
- <form role="form" onSubmit={e => handleRegisterFormSubmit(e, registerAction)} id="register-form">
|
|
|
+ {isEmailAuthenticationEnabled && isSuccessToRagistration && (
|
|
|
+ <p className="alert alert-success">
|
|
|
+ <span>
|
|
|
+ {t('message.successfully_send_email_auth', {
|
|
|
+ email: emailForRegistrationOrder,
|
|
|
+ })}
|
|
|
+ </span>
|
|
|
+ </p>
|
|
|
+ )}
|
|
|
|
|
|
+ <form
|
|
|
+ onSubmit={(e) => handleRegisterFormSubmit(e, registerAction)}
|
|
|
+ id="register-form"
|
|
|
+ >
|
|
|
{!isEmailAuthenticationEnabled && (
|
|
|
<div>
|
|
|
<div className="input-group" id="input-group-username">
|
|
|
@@ -367,7 +429,9 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
<input
|
|
|
type="text"
|
|
|
className="form-control rounded ms-2"
|
|
|
- onChange={(e) => { setUsernameForRegister(e.target.value) }}
|
|
|
+ onChange={(e) => {
|
|
|
+ setUsernameForRegister(e.target.value);
|
|
|
+ }}
|
|
|
placeholder={t('User ID')}
|
|
|
name="username"
|
|
|
defaultValue={props.username}
|
|
|
@@ -385,7 +449,9 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
<input
|
|
|
type="text"
|
|
|
className="form-control rounded ms-2"
|
|
|
- onChange={(e) => { setNameForRegister(e.target.value) }}
|
|
|
+ onChange={(e) => {
|
|
|
+ setNameForRegister(e.target.value);
|
|
|
+ }}
|
|
|
placeholder={t('Name')}
|
|
|
name="name"
|
|
|
defaultValue={props.name}
|
|
|
@@ -404,7 +470,9 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
type="email"
|
|
|
disabled={!isMailerSetup && isEmailAuthenticationEnabled}
|
|
|
className="form-control rounded ms-2"
|
|
|
- onChange={(e) => { setEmailForRegister(e.target.value) }}
|
|
|
+ onChange={(e) => {
|
|
|
+ setEmailForRegister(e.target.value);
|
|
|
+ }}
|
|
|
placeholder={t('Email')}
|
|
|
name="email"
|
|
|
defaultValue={props.email}
|
|
|
@@ -437,7 +505,9 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
<input
|
|
|
type="password"
|
|
|
className="form-control rounded ms-2"
|
|
|
- onChange={(e) => { setPasswordForRegister(e.target.value) }}
|
|
|
+ onChange={(e) => {
|
|
|
+ setPasswordForRegister(e.target.value);
|
|
|
+ }}
|
|
|
placeholder={t('Password')}
|
|
|
name="password"
|
|
|
required
|
|
|
@@ -452,7 +522,9 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
<button
|
|
|
type="submit"
|
|
|
className="btn btn-secondary btn-register d-flex col-7"
|
|
|
- disabled={(!isMailerSetup && isEmailAuthenticationEnabled) || isLoading}
|
|
|
+ disabled={
|
|
|
+ (!isMailerSetup && isEmailAuthenticationEnabled) || isLoading
|
|
|
+ }
|
|
|
>
|
|
|
<span>
|
|
|
{isLoading ? (
|
|
|
@@ -468,45 +540,82 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
|
|
|
<div className="row">
|
|
|
<div className="text-end col-12 mb-5">
|
|
|
- <a
|
|
|
- href="#login"
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
className="btn btn-sm btn-secondary btn-function col-10 col-sm-9 mx-auto py-1 d-flex"
|
|
|
style={{ pointerEvents: isLoading ? 'none' : undefined }}
|
|
|
onClick={switchForm}
|
|
|
>
|
|
|
<span className="material-symbols-outlined fs-5">login</span>
|
|
|
<span className="flex-grow-1">{t('Sign in is here')}</span>
|
|
|
- </a>
|
|
|
+ </button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</React.Fragment>
|
|
|
);
|
|
|
}, [
|
|
|
- t, isEmailAuthenticationEnabled, registrationMode, isMailerSetup, registerErrors, isSuccessToRagistration, emailForRegistrationOrder,
|
|
|
- props.username, props.name, props.email, registrationWhitelist, minPasswordLength, isLoading, switchForm, tWithOpt, handleRegisterFormSubmit,
|
|
|
+ t,
|
|
|
+ isEmailAuthenticationEnabled,
|
|
|
+ registrationMode,
|
|
|
+ isMailerSetup,
|
|
|
+ registerErrors,
|
|
|
+ isSuccessToRagistration,
|
|
|
+ emailForRegistrationOrder,
|
|
|
+ props.username,
|
|
|
+ props.name,
|
|
|
+ props.email,
|
|
|
+ registrationWhitelist,
|
|
|
+ minPasswordLength,
|
|
|
+ isLoading,
|
|
|
+ switchForm,
|
|
|
+ tWithOpt,
|
|
|
+ handleRegisterFormSubmit,
|
|
|
]);
|
|
|
|
|
|
- if (registrationMode === RegistrationMode.RESTRICTED && isSuccessToRagistration && !isEmailAuthenticationEnabled) {
|
|
|
+ if (
|
|
|
+ registrationMode === RegistrationMode.RESTRICTED &&
|
|
|
+ isSuccessToRagistration &&
|
|
|
+ !isEmailAuthenticationEnabled
|
|
|
+ ) {
|
|
|
return <CompleteUserRegistration />;
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
<div className={moduleClass}>
|
|
|
- <div className="nologin-dialog mx-auto rounded-4 rounded-top-0" id="nologin-dialog" data-testid="login-form">
|
|
|
+ <div
|
|
|
+ className="nologin-dialog mx-auto rounded-4 rounded-top-0"
|
|
|
+ id="nologin-dialog"
|
|
|
+ data-testid="login-form"
|
|
|
+ >
|
|
|
<div className="row mx-0">
|
|
|
<div className="col-12 px-md-4 pb-5">
|
|
|
- <ReactCardFlip isFlipped={isRegistering} flipDirection="horizontal" cardZIndex="3">
|
|
|
+ <ReactCardFlip
|
|
|
+ isFlipped={isRegistering}
|
|
|
+ flipDirection="horizontal"
|
|
|
+ cardZIndex="3"
|
|
|
+ >
|
|
|
<div className="front">
|
|
|
{/* Error display section - always shown regardless of login method configuration */}
|
|
|
{(() => {
|
|
|
// separate login errors into two arrays based on error code
|
|
|
- const [loginErrorListForDangerouslySetInnerHTML, loginErrorList] = separateErrorsBasedOnErrorCode(loginErrors);
|
|
|
+ const [
|
|
|
+ loginErrorListForDangerouslySetInnerHTML,
|
|
|
+ loginErrorList,
|
|
|
+ ] = separateErrorsBasedOnErrorCode(loginErrors);
|
|
|
// Generate login error elements using dangerouslySetInnerHTML
|
|
|
- const loginErrorElementWithDangerouslySetInnerHTML = generateDangerouslySetErrors(loginErrorListForDangerouslySetInnerHTML);
|
|
|
+ const loginErrorElementWithDangerouslySetInnerHTML =
|
|
|
+ generateDangerouslySetErrors(
|
|
|
+ loginErrorListForDangerouslySetInnerHTML,
|
|
|
+ );
|
|
|
// Generate login error elements - prioritize loginErrorList, fallback to externalAccountLoginError
|
|
|
- const loginErrorElement = (loginErrorList ?? []).length > 0
|
|
|
- ? generateSafelySetErrors(loginErrorList)
|
|
|
- : generateSafelySetErrors(props.externalAccountLoginError != null ? [props.externalAccountLoginError] : []);
|
|
|
+ const loginErrorElement =
|
|
|
+ (loginErrorList ?? []).length > 0
|
|
|
+ ? generateSafelySetErrors(loginErrorList)
|
|
|
+ : generateSafelySetErrors(
|
|
|
+ props.externalAccountLoginError != null
|
|
|
+ ? [props.externalAccountLoginError]
|
|
|
+ : [],
|
|
|
+ );
|
|
|
|
|
|
return (
|
|
|
<>
|
|
|
@@ -517,11 +626,12 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
})()}
|
|
|
|
|
|
{isLocalOrLdapStrategiesEnabled && renderLocalOrLdapLoginForm()}
|
|
|
- {isLocalOrLdapStrategiesEnabled && isSomeExternalAuthEnabled && (
|
|
|
- <div className="text-center text-line d-flex align-items-center mb-3">
|
|
|
- <p className="text-white mb-0">{t('or')}</p>
|
|
|
- </div>
|
|
|
- )}
|
|
|
+ {isLocalOrLdapStrategiesEnabled &&
|
|
|
+ isSomeExternalAuthEnabled && (
|
|
|
+ <div className="text-center text-line d-flex align-items-center mb-3">
|
|
|
+ <p className="text-white mb-0">{t('or')}</p>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
{isSomeExternalAuthEnabled && renderExternalAuthLoginForm()}
|
|
|
{isLocalOrLdapStrategiesEnabled && isPasswordResetEnabled && (
|
|
|
<div className="mt-4">
|
|
|
@@ -531,22 +641,28 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
|
|
|
>
|
|
|
<span className="material-symbols-outlined">vpn_key</span>
|
|
|
- <span className="flex-grow-1">{t('forgot_password.forgot_password')}</span>
|
|
|
+ <span className="flex-grow-1">
|
|
|
+ {t('forgot_password.forgot_password')}
|
|
|
+ </span>
|
|
|
</a>
|
|
|
</div>
|
|
|
)}
|
|
|
{/* Sign up link */}
|
|
|
{isRegistrationEnabled && (
|
|
|
<div className="mt-2">
|
|
|
- <a
|
|
|
- href="#register"
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
className="btn btn-sm btn-secondary btn-function col-10 col-sm-9 mx-auto py-1 d-flex"
|
|
|
style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
|
|
|
onClick={switchForm}
|
|
|
>
|
|
|
- <span className="material-symbols-outlined">person_add</span>
|
|
|
- <span className="flex-grow-1">{t('Sign up is here')}</span>
|
|
|
- </a>
|
|
|
+ <span className="material-symbols-outlined">
|
|
|
+ person_add
|
|
|
+ </span>
|
|
|
+ <span className="flex-grow-1">
|
|
|
+ {t('Sign up is here')}
|
|
|
+ </span>
|
|
|
+ </button>
|
|
|
</div>
|
|
|
)}
|
|
|
</div>
|
|
|
@@ -558,10 +674,10 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
</div>
|
|
|
</div>
|
|
|
<a href="https://growi.org" className="link-growi-org ps-3">
|
|
|
- <span className="growi">GROWI</span><span className="org">.org</span>
|
|
|
+ <span className="growi">GROWI</span>
|
|
|
+ <span className="org">.org</span>
|
|
|
</a>
|
|
|
</div>
|
|
|
</div>
|
|
|
);
|
|
|
-
|
|
|
};
|