|
|
@@ -1,44 +1,64 @@
|
|
|
-import React from 'react';
|
|
|
+import React, {
|
|
|
+ useState, useEffect, useCallback,
|
|
|
+} from 'react';
|
|
|
|
|
|
import { useTranslation } from 'next-i18next';
|
|
|
-import PropTypes from 'prop-types';
|
|
|
+import { useRouter } from 'next/router';
|
|
|
import ReactCardFlip from 'react-card-flip';
|
|
|
|
|
|
+import { apiv3Post } from '~/client/util/apiv3-client';
|
|
|
import { useCsrfToken } from '~/stores/context';
|
|
|
|
|
|
-class LoginForm extends React.Component {
|
|
|
-
|
|
|
- constructor(props) {
|
|
|
- super(props);
|
|
|
-
|
|
|
- this.state = {
|
|
|
- isRegistering: false,
|
|
|
- };
|
|
|
-
|
|
|
- this.switchForm = this.switchForm.bind(this);
|
|
|
- this.handleLoginWithExternalAuth = this.handleLoginWithExternalAuth.bind(this);
|
|
|
- this.renderLocalOrLdapLoginForm = this.renderLocalOrLdapLoginForm.bind(this);
|
|
|
- this.renderExternalAuthLoginForm = this.renderExternalAuthLoginForm.bind(this);
|
|
|
- this.renderExternalAuthInput = this.renderExternalAuthInput.bind(this);
|
|
|
- this.renderRegisterForm = this.renderRegisterForm.bind(this);
|
|
|
+type LoginFormProps = {
|
|
|
+ username?: string,
|
|
|
+ name?: string,
|
|
|
+ email?: string,
|
|
|
+ isRegistrationEnabled: boolean,
|
|
|
+ isEmailAuthenticationEnabled: boolean,
|
|
|
+ registrationMode?: string,
|
|
|
+ registrationWhiteList: string[],
|
|
|
+ isPasswordResetEnabled: boolean,
|
|
|
+ isLocalStrategySetup: boolean,
|
|
|
+ isLdapStrategySetup: boolean,
|
|
|
+ objOfIsExternalAuthEnableds?: any,
|
|
|
+ isMailerSetup?: boolean
|
|
|
+}
|
|
|
+export const LoginForm = (props: LoginFormProps): JSX.Element => {
|
|
|
+ const { t } = useTranslation();
|
|
|
+ const router = useRouter();
|
|
|
+ const { data: csrfToken } = useCsrfToken();
|
|
|
|
|
|
+ const {
|
|
|
+ isLocalStrategySetup, isLdapStrategySetup, isPasswordResetEnabled, isRegistrationEnabled,
|
|
|
+ isEmailAuthenticationEnabled, registrationMode, registrationWhiteList, isMailerSetup,
|
|
|
+ } = props;
|
|
|
+ const isLocalOrLdapStrategiesEnabled = isLocalStrategySetup || isLdapStrategySetup;
|
|
|
+ // const isSomeExternalAuthEnabled = Object.values(objOfIsExternalAuthEnableds).some(elem => elem);
|
|
|
+ const isSomeExternalAuthEnabled = true;
|
|
|
+
|
|
|
+ // states
|
|
|
+ const [isRegistering, setIsRegistering] = useState(false);
|
|
|
+ const [username, setUsername] = useState('');
|
|
|
+ const [name, setName] = useState('');
|
|
|
+ const [email, setEmail] = useState('');
|
|
|
+ const [password, setPassword] = useState('');
|
|
|
+ const [registerErrors, setRegisterErrors] = useState<Error[]>([]);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
const { hash } = window.location;
|
|
|
if (hash === '#register') {
|
|
|
- this.state.isRegistering = true;
|
|
|
+ setIsRegistering(true);
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- switchForm() {
|
|
|
- this.setState({ isRegistering: !this.state.isRegistering });
|
|
|
- }
|
|
|
+ }, []);
|
|
|
|
|
|
- handleLoginWithExternalAuth(e) {
|
|
|
+ // functions
|
|
|
+ const handleLoginWithExternalAuth = useCallback((e) => {
|
|
|
const auth = e.currentTarget.id;
|
|
|
- window.location.href = `/passport/${auth}`;
|
|
|
- }
|
|
|
|
|
|
- renderLocalOrLdapLoginForm() {
|
|
|
- const { t, csrfToken, isLdapStrategySetup } = this.props;
|
|
|
+ window.location.href = `/passport/${auth}`;
|
|
|
+ }, []);
|
|
|
+ const renderLocalOrLdapLoginForm = useCallback(() => {
|
|
|
+ const { isLdapStrategySetup } = props;
|
|
|
|
|
|
return (
|
|
|
<form role="form" action="/login" method="post">
|
|
|
@@ -79,10 +99,8 @@ class LoginForm extends React.Component {
|
|
|
</div>
|
|
|
</form>
|
|
|
);
|
|
|
- }
|
|
|
-
|
|
|
- renderExternalAuthInput(auth) {
|
|
|
- const { t } = this.props;
|
|
|
+ }, [csrfToken, props, t]);
|
|
|
+ const renderExternalAuthInput = useCallback((auth) => {
|
|
|
const authIconNames = {
|
|
|
google: 'google',
|
|
|
github: 'github',
|
|
|
@@ -95,7 +113,7 @@ class LoginForm extends React.Component {
|
|
|
|
|
|
return (
|
|
|
<div key={auth} className="col-6 my-2">
|
|
|
- <button type="button" className="btn btn-fill rounded-0" id={auth} onClick={this.handleLoginWithExternalAuth}>
|
|
|
+ <button type="button" className="btn btn-fill rounded-0" id={auth} onClick={handleLoginWithExternalAuth}>
|
|
|
<div className="eff"></div>
|
|
|
<span className="btn-label">
|
|
|
<i className={`fa fa-${authIconNames[auth]}`}></i>
|
|
|
@@ -105,10 +123,9 @@ class LoginForm extends React.Component {
|
|
|
<div className="small text-right">by {auth} Account</div>
|
|
|
</div>
|
|
|
);
|
|
|
- }
|
|
|
-
|
|
|
- renderExternalAuthLoginForm() {
|
|
|
- const { isLocalStrategySetup, isLdapStrategySetup, objOfIsExternalAuthEnableds } = this.props;
|
|
|
+ }, [handleLoginWithExternalAuth, t]);
|
|
|
+ const renderExternalAuthLoginForm = useCallback(() => {
|
|
|
+ const { isLocalStrategySetup, isLdapStrategySetup, objOfIsExternalAuthEnableds } = props;
|
|
|
const isExternalAuthCollapsible = isLocalStrategySetup || isLdapStrategySetup;
|
|
|
const collapsibleClass = isExternalAuthCollapsible ? 'collapse collapse-external-auth' : '';
|
|
|
|
|
|
@@ -121,7 +138,7 @@ class LoginForm extends React.Component {
|
|
|
if (!objOfIsExternalAuthEnableds[auth]) {
|
|
|
return;
|
|
|
}
|
|
|
- return this.renderExternalAuthInput(auth);
|
|
|
+ return renderExternalAuthInput(auth);
|
|
|
})}
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -140,22 +157,42 @@ class LoginForm extends React.Component {
|
|
|
</div>
|
|
|
</>
|
|
|
);
|
|
|
- }
|
|
|
+ }, [props, renderExternalAuthInput]);
|
|
|
|
|
|
- renderRegisterForm() {
|
|
|
- const {
|
|
|
- t,
|
|
|
- // appContainer,
|
|
|
- csrfToken,
|
|
|
- isEmailAuthenticationEnabled,
|
|
|
+ const handleRegisterFormSubmit = useCallback(async(e, requestPath) => {
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ const registerForm = {
|
|
|
username,
|
|
|
name,
|
|
|
email,
|
|
|
- registrationMode,
|
|
|
- registrationWhiteList,
|
|
|
- isMailerSetup,
|
|
|
- } = this.props;
|
|
|
+ password,
|
|
|
+ };
|
|
|
+ try {
|
|
|
+ const res = await apiv3Post(requestPath, { registerForm });
|
|
|
+ const { redirectTo } = res.data;
|
|
|
+ router.push(redirectTo);
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ // Execute if error exists
|
|
|
+ if (err != null || err.length > 0) {
|
|
|
+ setRegisterErrors(err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }, [email, name, password, router, username]);
|
|
|
+
|
|
|
+ const resetRegisterErrors = useCallback(() => {
|
|
|
+ if (registerErrors.length === 0) return;
|
|
|
+ setRegisterErrors([]);
|
|
|
+ }, [registerErrors.length]);
|
|
|
+
|
|
|
+ const switchForm = useCallback(() => {
|
|
|
+ setIsRegistering(!isRegistering);
|
|
|
+ resetRegisterErrors();
|
|
|
+ }, [isRegistering, resetRegisterErrors]);
|
|
|
|
|
|
+ const renderRegisterForm = useCallback(() => {
|
|
|
let registerAction = '/register';
|
|
|
|
|
|
let submitText = t('Sign up');
|
|
|
@@ -179,7 +216,21 @@ class LoginForm extends React.Component {
|
|
|
</p>
|
|
|
)}
|
|
|
|
|
|
- <form role="form" action={registerAction} method="post" id="register-form">
|
|
|
+ {
|
|
|
+ registerErrors != null && registerErrors.length > 0 && (
|
|
|
+ <p className="alert alert-danger">
|
|
|
+ {registerErrors.map((err, index) => {
|
|
|
+ return (
|
|
|
+ <span key={index}>
|
|
|
+ {t(`message.${err.message}`)}<br/>
|
|
|
+ </span>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </p>
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ <form role="form" onSubmit={e => handleRegisterFormSubmit(e, registerAction) } id="register-form">
|
|
|
|
|
|
{!isEmailAuthenticationEnabled && (
|
|
|
<div>
|
|
|
@@ -189,12 +240,14 @@ class LoginForm extends React.Component {
|
|
|
<i className="icon-user"></i>
|
|
|
</span>
|
|
|
</div>
|
|
|
+ {/* username */}
|
|
|
<input
|
|
|
type="text"
|
|
|
className="form-control rounded-0"
|
|
|
+ onChange={(e) => { setUsername(e.target.value) }}
|
|
|
placeholder={t('User ID')}
|
|
|
- name="registerForm[username]"
|
|
|
- defaultValue={username}
|
|
|
+ name="username"
|
|
|
+ defaultValue={props.username}
|
|
|
required
|
|
|
/>
|
|
|
</div>
|
|
|
@@ -207,7 +260,14 @@ class LoginForm extends React.Component {
|
|
|
<i className="icon-tag"></i>
|
|
|
</span>
|
|
|
</div>
|
|
|
- <input type="text" className="form-control rounded-0" placeholder={t('Name')} name="registerForm[name]" defaultValue={name} required />
|
|
|
+ {/* name */}
|
|
|
+ <input type="text"
|
|
|
+ className="form-control rounded-0"
|
|
|
+ onChange={(e) => { setName(e.target.value) }}
|
|
|
+ placeholder={t('Name')}
|
|
|
+ name="name"
|
|
|
+ defaultValue={props.name}
|
|
|
+ required />
|
|
|
</div>
|
|
|
</div>
|
|
|
)}
|
|
|
@@ -218,7 +278,15 @@ class LoginForm extends React.Component {
|
|
|
<i className="icon-envelope"></i>
|
|
|
</span>
|
|
|
</div>
|
|
|
- <input type="email" className="form-control rounded-0" placeholder={t('Email')} name="registerForm[email]" defaultValue={email} required />
|
|
|
+ {/* email */}
|
|
|
+ <input type="email"
|
|
|
+ className="form-control rounded-0"
|
|
|
+ onChange={(e) => { setEmail(e.target.value) }}
|
|
|
+ placeholder={t('Email')}
|
|
|
+ name="email"
|
|
|
+ defaultValue={props.email}
|
|
|
+ required
|
|
|
+ />
|
|
|
</div>
|
|
|
|
|
|
{registrationWhiteList.length > 0 && (
|
|
|
@@ -244,14 +312,24 @@ class LoginForm extends React.Component {
|
|
|
<i className="icon-lock"></i>
|
|
|
</span>
|
|
|
</div>
|
|
|
- <input type="password" className="form-control rounded-0" placeholder={t('Password')} name="registerForm[password]" required />
|
|
|
+ {/* Password */}
|
|
|
+ <input type="password"
|
|
|
+ className="form-control rounded-0"
|
|
|
+ onChange={(e) => { setPassword(e.target.value) }}
|
|
|
+ placeholder={t('Password')}
|
|
|
+ name="password"
|
|
|
+ required />
|
|
|
</div>
|
|
|
</div>
|
|
|
)}
|
|
|
|
|
|
+ {/* Sign up button (submit) */}
|
|
|
<div className="input-group justify-content-center my-4">
|
|
|
- <input type="hidden" name="_csrf" value={csrfToken} />
|
|
|
- <button type="submit" className="btn btn-fill rounded-0" id="register" disabled={(!isMailerSetup && isEmailAuthenticationEnabled)}>
|
|
|
+ <button
|
|
|
+ className="btn btn-fill rounded-0"
|
|
|
+ id="register"
|
|
|
+ disabled={(!isMailerSetup && isEmailAuthenticationEnabled)}
|
|
|
+ >
|
|
|
<div className="eff"></div>
|
|
|
<span className="btn-label">
|
|
|
<i className="icon-user-follow"></i>
|
|
|
@@ -265,7 +343,7 @@ class LoginForm extends React.Component {
|
|
|
|
|
|
<div className="row">
|
|
|
<div className="text-right col-12 mt-2 py-2">
|
|
|
- <a href="#login" id="login" className="link-switch" onClick={this.switchForm}>
|
|
|
+ <a href="#login" id="login" className="link-switch" onClick={switchForm}>
|
|
|
<i className="icon-fw icon-login"></i>
|
|
|
{t('Sign in is here')}
|
|
|
</a>
|
|
|
@@ -273,93 +351,45 @@ class LoginForm extends React.Component {
|
|
|
</div>
|
|
|
</React.Fragment>
|
|
|
);
|
|
|
- }
|
|
|
-
|
|
|
- render() {
|
|
|
- const {
|
|
|
- t,
|
|
|
- isLocalStrategySetup,
|
|
|
- isLdapStrategySetup,
|
|
|
- isRegistrationEnabled,
|
|
|
- isPasswordResetEnabled,
|
|
|
- objOfIsExternalAuthEnableds,
|
|
|
- } = this.props;
|
|
|
-
|
|
|
-
|
|
|
- const isLocalOrLdapStrategiesEnabled = isLocalStrategySetup || isLdapStrategySetup;
|
|
|
- // const isSomeExternalAuthEnabled = Object.values(objOfIsExternalAuthEnableds).some(elem => elem);
|
|
|
- const isSomeExternalAuthEnabled = true;
|
|
|
-
|
|
|
- return (
|
|
|
- <div className="noLogin-dialog mx-auto" id="noLogin-dialog">
|
|
|
- <div className="row mx-0">
|
|
|
- <div className="col-12">
|
|
|
- <ReactCardFlip isFlipped={this.state.isRegistering} flipDirection="horizontal" cardZIndex="3">
|
|
|
- <div className="front">
|
|
|
- {isLocalOrLdapStrategiesEnabled && this.renderLocalOrLdapLoginForm()}
|
|
|
- {isSomeExternalAuthEnabled && this.renderExternalAuthLoginForm()}
|
|
|
- {isLocalOrLdapStrategiesEnabled && isPasswordResetEnabled && (
|
|
|
- <div className="text-right mb-2">
|
|
|
- <a href="/forgot-password" className="d-block link-switch">
|
|
|
- <i className="icon-key"></i> {t('forgot_password.forgot_password')}
|
|
|
- </a>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- {isRegistrationEnabled && (
|
|
|
- <div className="text-right mb-2">
|
|
|
- <a href="#register" id="register" className="link-switch" onClick={this.switchForm}>
|
|
|
- <i className="ti ti-check-box"></i> {t('Sign up is here')}
|
|
|
- </a>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- <div className="back">
|
|
|
- {isRegistrationEnabled && this.renderRegisterForm()}
|
|
|
- </div>
|
|
|
- </ReactCardFlip>
|
|
|
- </div>
|
|
|
+ }, [handleRegisterFormSubmit, isEmailAuthenticationEnabled, isMailerSetup,
|
|
|
+ props.email, props.name, props.username,
|
|
|
+ registerErrors, registrationMode, registrationWhiteList, switchForm, t]);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="noLogin-dialog mx-auto" id="noLogin-dialog">
|
|
|
+ <div className="row mx-0">
|
|
|
+ <div className="col-12">
|
|
|
+ <ReactCardFlip isFlipped={isRegistering} flipDirection="horizontal" cardZIndex="3">
|
|
|
+ <div className="front">
|
|
|
+ {isLocalOrLdapStrategiesEnabled && renderLocalOrLdapLoginForm()}
|
|
|
+ {isSomeExternalAuthEnabled && renderExternalAuthLoginForm()}
|
|
|
+ {isLocalOrLdapStrategiesEnabled && isPasswordResetEnabled && (
|
|
|
+ <div className="text-right mb-2">
|
|
|
+ <a href="/forgot-password" className="d-block link-switch">
|
|
|
+ <i className="icon-key"></i> {t('forgot_password.forgot_password')}
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ {/* Sign up link */}
|
|
|
+ {isRegistrationEnabled && (
|
|
|
+ <div className="text-right mb-2">
|
|
|
+ <a href="#register" id="register" className="link-switch" onClick={switchForm}>
|
|
|
+ <i className="ti ti-check-box"></i> {t('Sign up is here')}
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ <div className="back">
|
|
|
+ {/* Register form for /login#register */}
|
|
|
+ {isRegistrationEnabled && renderRegisterForm()}
|
|
|
+ </div>
|
|
|
+ </ReactCardFlip>
|
|
|
</div>
|
|
|
- <a href="https://growi.org" className="link-growi-org pl-3">
|
|
|
- <span className="growi">GROWI</span>.<span className="org">ORG</span>
|
|
|
- </a>
|
|
|
</div>
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
-}
|
|
|
+ <a href="https://growi.org" className="link-growi-org pl-3">
|
|
|
+ <span className="growi">GROWI</span>.<span className="org">ORG</span>
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
|
|
|
-LoginForm.propTypes = {
|
|
|
- // i18next
|
|
|
- t: PropTypes.func.isRequired,
|
|
|
- // appContainer: PropTypes.instanceOf(AppContainer).isRequired,
|
|
|
-
|
|
|
- csrfToken: PropTypes.string,
|
|
|
- isRegistering: PropTypes.bool,
|
|
|
- username: PropTypes.string,
|
|
|
- name: PropTypes.string,
|
|
|
- email: PropTypes.string,
|
|
|
- isRegistrationEnabled: PropTypes.bool,
|
|
|
- registrationMode: PropTypes.string,
|
|
|
- registrationWhiteList: PropTypes.array,
|
|
|
- isPasswordResetEnabled: PropTypes.bool,
|
|
|
- isEmailAuthenticationEnabled: PropTypes.bool,
|
|
|
- isLocalStrategySetup: PropTypes.bool,
|
|
|
- isLdapStrategySetup: PropTypes.bool,
|
|
|
- objOfIsExternalAuthEnableds: PropTypes.object,
|
|
|
- isMailerSetup: PropTypes.bool,
|
|
|
};
|
|
|
-
|
|
|
-const LoginFormWrapperFC = (props) => {
|
|
|
- const { t } = useTranslation();
|
|
|
- const { data: csrfToken } = useCsrfToken();
|
|
|
-
|
|
|
- return <LoginForm t={t} csrfToken={csrfToken} {...props} />;
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * Wrapper component for using unstated
|
|
|
- */
|
|
|
-// const LoginFormWrapper = withUnstatedContainers(LoginFormWrapperFC, [AppContainer]);
|
|
|
-
|
|
|
-// export default LoginForm;
|
|
|
-export default LoginFormWrapperFC;
|