ソースを参照

Merge pull request #1969 from weseek/support/reactify-login-page-front

Support/reactify login page front
yusuketk 6 年 前
コミット
cabc6b73fb

+ 146 - 2
src/client/js/components/LoginForm.jsx

@@ -1,4 +1,5 @@
 import React from 'react';
 import React from 'react';
+import PropTypes from 'prop-types';
 
 
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
 
 
@@ -7,21 +8,164 @@ class LoginForm extends React.Component {
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
 
 
-    this.state = {
+    this.isRegistrationEnabled = false;
+    this.isLocalStrategySetup = false;
+    this.isLdapStrategySetup = false;
+    this.objOfIsExternalAuthEnableds = {};
+
+    this.renderLocalOrLdapLoginForm = this.renderLocalOrLdapLoginForm.bind(this);
+    this.renderExternalAuthLoginForm = this.renderExternalAuthLoginForm.bind(this);
+    this.renderExternalAuthInput = this.renderExternalAuthInput.bind(this);
+  }
+
+  componentWillMount() {
+    // [TODO][GW-1913] get params from server with axios
+    this.isRegistrationEnabled = true;
+    this.isLocalStrategySetup = true;
+    this.isLdapStrategySetup = true;
+    this.objOfIsExternalAuthEnableds = {
+      google: true,
+      github: true,
+      facebook: true,
+      twitter: true,
+      oidc: true,
+      saml: true,
+      basic: true,
     };
     };
+  }
+
+  renderLocalOrLdapLoginForm() {
+    const { t } = this.props;
 
 
+    return (
+      <form role="form" action="/login" method="post">
+
+        <div className="input-group mb-3">
+          <div className="input-group-prepend">
+            <span className="input-group-text"><i className="icon-user"></i></span>
+          </div>
+          <input type="text" className="form-control" placeholder="Username or E-mail" name="loginForm[username]" />
+          {this.isLdapStrategySetup && (
+            <div className="input-group-append">
+              <small className="input-group-text text-success">
+                <i className="icon-fw icon-check"></i> LDAP
+              </small>
+            </div>
+          )}
+        </div>
+
+        <div className="input-group mb-3">
+          <div className="input-group-prepend">
+            <span className="input-group-text"><i className="icon-lock"></i></span>
+          </div>
+          <input type="password" className="form-control" placeholder="Password" name="loginForm[password]" />
+        </div>
+
+        <div className="input-group justify-content-center d-flex mt-5">
+          <input type="hidden" name="_csrf" value="{{ csrf() }}" />
+          <button type="submit" id="login" className="btn btn-fill login px-0 py-2">
+            <div className="eff"></div>
+            <span className="btn-label p-3"><i className="icon-login"></i></span>
+            <span className="btn-label-text p-3">{ t('Sign in') }</span>
+          </button>
+        </div>
+
+      </form>
+    );
+  }
+
+  renderExternalAuthInput(auth) {
+    const { t } = this.props;
+    return (
+      <div key={auth} className="input-group justify-content-center d-flex mt-5">
+        {/* [TODO][GW-1913] use onClick, and delete form tag */}
+        <form role="form" action={`/passport/${auth}`} className="d-inline-flex flex-column">
+          <input type="hidden" name="_csrf" value="{{ csrf() }}" />
+          <button type="submit" className="btn btn-fill px-0 py-2" id={auth}>
+            <div className="eff"></div>
+            <span className="btn-label p-3"><i className={`fa fa-${auth}`}></i></span>
+            <span className="btn-label-text p-3">{ t('Sign in') }</span>
+          </button>
+          <div className="small text-center">by {auth} Account</div>
+        </form>
+      </div>
+    );
+  }
+
+  renderExternalAuthLoginForm() {
+    const isExternalAuthCollapsible = this.isLocalStrategySetup || this.isLdapStrategySetup;
+    const collapsibleClass = isExternalAuthCollapsible ? 'collapse collapse-external-auth collapse-anchor' : '';
+
+    return (
+      <>
+        <div className="border-bottom"></div>
+        <div id="external-auth" className={`external-auth ${collapsibleClass}`}>
+          <div className="spacer"></div>
+          <div className="d-flex flex-row justify-content-between flex-wrap">
+            {Object.keys(this.objOfIsExternalAuthEnableds).map((auth) => {
+              if (!this.objOfIsExternalAuthEnableds[auth]) {
+                return;
+              }
+              return this.renderExternalAuthInput(auth);
+            })}
+          </div>
+          <div className="spacer"></div>
+        </div>
+        <div className="border-bottom"></div>
+        <div className="text-center">
+          <button
+            type="button"
+            className="collapse-anchor btn btn-xs btn-collapse-external-auth mb-3"
+            data-toggle={isExternalAuthCollapsible ? 'collapse' : ''}
+            data-target="#external-auth"
+            aria-expanded="false"
+            aria-controls="external-auth"
+          >
+            External Auth
+          </button>
+        </div>
+      </>
+    );
   }
   }
 
 
   render() {
   render() {
+    const { t, isRegistering } = this.props;
+
+    const isLocalOrLdapStrategiesEnabled = this.isLocalStrategySetup || this.isLdapStrategySetup;
+    const registerFormClass = isRegistering ? 'to-flip' : '';
+    const isSomeExternalAuthEnabled = Object.values(this.objOfIsExternalAuthEnableds).some(elem => elem);
 
 
     return (
     return (
-      <div></div>
+      <div className={`login-dialog mx-auto flipper ${registerFormClass}`} id="login-dialog">
+        <div className="row mx-0">
+          <div className="col-12">
+            <div className="front">
+              { isLocalOrLdapStrategiesEnabled && this.renderLocalOrLdapLoginForm() }
+              { isSomeExternalAuthEnabled && this.renderExternalAuthLoginForm() }
+            </div>
+            {/* [TODO][GW-1863] render register form here */}
+          </div>
+        </div>
+        {this.isRegistrationEnabled && (
+          <div className="row">
+            <div className="col-12 text-right py-2">
+              <a href="#register" id="register" className="link-switch">
+                <i className="ti-check-box"></i> { t('Sign up is here') }
+              </a>
+            </div>
+          </div>
+        )}
+      </div>
     );
     );
   }
   }
 
 
 }
 }
 
 
 LoginForm.propTypes = {
 LoginForm.propTypes = {
+  // i18next
+  t: PropTypes.func.isRequired,
+  isRegistering: PropTypes.bool,
+  csrf: PropTypes.string,
 };
 };
 
 
 export default withTranslation()(LoginForm);
 export default withTranslation()(LoginForm);

+ 7 - 1
src/client/js/nologin.jsx

@@ -27,9 +27,15 @@ if (installerFormElem) {
 // render loginForm
 // render loginForm
 const loginFormElem = document.getElementById('login-form');
 const loginFormElem = document.getElementById('login-form');
 if (loginFormElem) {
 if (loginFormElem) {
+  const isRegistering = loginFormElem.dataset.isRegistering === 'true';
+  // [TODO][GW-1913] An AppContainer gets csrf data
+  const csrf = loginFormElem.dataset.csrf;
   ReactDOM.render(
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
     <I18nextProvider i18n={i18n}>
-      <LoginForm />
+      <LoginForm
+        isRegistering={isRegistering}
+        csrf={csrf}
+      />
     </I18nextProvider>,
     </I18nextProvider>,
     loginFormElem,
     loginFormElem,
   );
   );

+ 3 - 10
src/client/styles/scss/_login.scss

@@ -278,16 +278,9 @@
 
 
 .login-page {
 .login-page {
   // layout
   // layout
-  .main .row {
-    @media (min-width: 350px) {
-      .col-sm-offset-4 {
-        margin-left: calc(50% - 160px);
-      }
-
-      .col-sm-4 {
-        width: 320px;
-      }
-    }
+  .main .row .login-header,
+  .login-dialog {
+    width: 320px;
   }
   }
 
 
   .link-growi-org {
   .link-growi-org {

+ 27 - 199
src/server/views/login.html

@@ -18,7 +18,9 @@
 {% block sidebar %}
 {% block sidebar %}
 {% endblock %}
 {% endblock %}
 
 
-
+{% block html_additional_headers %}
+  <script src="{{ webpack_asset('js/nologin.js') }}" defer></script>
+{% endblock %}
 
 
 {% block layout_main %}
 {% block layout_main %}
 
 
@@ -102,206 +104,15 @@
           </div>
           </div>
         </div>
         </div>
       </div>
       </div>
-    </div>
-  </div>
-
-  <div class="row mb-5">
-    <div class="col-md-12">
-
-    {% set isLocalOrLdapStrategiesEnabled = passportService.isLocalStrategySetup || passportService.isLdapStrategySetup %}
-    {% set isExternalAuthCollapsible = isLocalOrLdapStrategiesEnabled %}
-    {% set isRegistrationEnabled = passportService.isLocalStrategySetup && getConfig('crowi', 'security:registrationMode') != 'Closed' %}
-      <div id="login-form"></div>
-      <!-- [TODO][GW-1772][GW-1863] remove the old login-form -->
-      <div class="login-dialog mx-auto flipper {% if req.query.register or req.body.registerForm or isRegistering %}to-flip{% endif %}" id="login-dialog">
 
 
-        <div class="col-12">
-          <div class="front">
-
-            {% if isLocalOrLdapStrategiesEnabled %}
-            <form role="form" action="/login" method="post">
-
-              <div class="input-group mb-3">
-                <div class="input-group-prepend">
-                  <span class="input-group-text"><i class="icon-user"></i></span>
-                </div>
-                <input type="text" class="form-control" placeholder="Username or E-mail" name="loginForm[username]">
-                {% if passportService.isLdapStrategySetup %}
-                <div class="input-group-append">
-                  <small class="input-group-text text-success">
-                    <i class="icon-fw icon-check"></i> LDAP
-                  </small>
-                </div>
-                {% endif %}
-              </div>
-
-              <div class="input-group">
-                <div class="input-group-prepend">
-                  <span class="input-group-text"><i class="icon-lock"></i></span>
-                </div>
-                <input type="password" class="form-control" placeholder="Password" name="loginForm[password]">
-              </div>
-
-              <div class="input-group justify-content-center d-flex mt-5">
-                <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                <button type="submit" class="btn btn-fill login px-0 py-2">
-                  <div class="eff"></div>
-                  <span class="btn-label p-3"><i class="icon-login"></i></span>
-                  <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
-                </button>
-              </div>
-
-            </form>
-            {% endif %}
-
-            {% if (
-              getConfig('crowi', 'security:passport-google:isEnabled') ||
-              getConfig('crowi', 'security:passport-github:isEnabled') ||
-              getConfig('crowi', 'security:passport-facebook:isEnabled') ||
-              getConfig('crowi', 'security:passport-twitter:isEnabled')||
-              getConfig('crowi', 'security:passport-oidc:isEnabled') ||
-              getConfig('crowi', 'security:passport-saml:isEnabled') ||
-              getConfig('crowi', 'security:passport-basic:isEnabled')
-            ) %}
-            <div class="border-bottom"></div>
-            <div id="external-auth" class="external-auth {% if isExternalAuthCollapsible %}collapse collapse-external-auth collapse-anchor{% endif %}">
-              <div class="spacer"></div>
-              <div class="d-flex flex-row justify-content-between flex-wrap">
-                {% if getConfig('crowi', 'security:passport-google:isEnabled') %}
-                <div class="input-group justify-content-center d-flex mt-5">
-                  <form role="form" action="/passport/google" class="d-inline-flex flex-column">
-                    <button type="submit" class="btn btn-fill px-0 py-2" id="google">
-                      <div class="eff"></div>
-                      <span class="btn-label p-3"><i class="fa fa-google"></i></span>
-                      <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
-                    </button>
-                    <div class="small text-center">by Google Account</div>
-                  </form>
-                </div>
-                {% endif %}
-                {% if getConfig('crowi', 'security:passport-github:isEnabled') %}
-                <div class="input-group justify-content-center d-flex mt-5">
-                  <form role="form" action="/passport/github" class="d-inline-flex flex-column">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                    <button type="submit" class="btn btn-fill px-0 py-2" id="github">
-                      <div class="eff"></div>
-                      <span class="btn-label p-3"><i class="fa fa-github"></i></span>
-                      <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
-                    </button>
-                    <div class="small text-center">by GitHub Account</div>
-                  </form>
-                </div>
-                {% endif %}
-                {% if getConfig('crowi', 'security:passport-facebook:isEnabled') %}
-                <div class="input-group justify-content-center d-flex mt-5">
-                  <form role="form" action="/passport/facebook" class="d-inline-flex flex-column">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                    <button type="submit" class="btn btn-fill px-0 py-2" id="facebook">
-                      <div class="eff"></div>
-                      <span class="btn-label p-3"><i class="fa fa-facebook"></i></span>
-                      <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
-                    </button>
-                    <div class="small text-center">by Facebook Account</div>
-                  </form>
-                </div>
-                {% endif %}
-                {% if getConfig('crowi', 'security:passport-twitter:isEnabled') %}
-                <div class="input-group justify-content-center d-flex mt-5">
-                  <form role="form" action="/passport/twitter" class="d-inline-flex flex-column">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                    <button type="submit" class="btn btn-fill px-0 py-2" id="twitter">
-                      <div class="eff"></div>
-                      <span class="btn-label p-3"><i class="fa fa-twitter"></i></span>
-                      <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
-                    </button>
-                    <div class="small text-center">by Twitter Account</div>
-                  </form>
-                </div>
-                {% endif %}
-                {% if getConfig('crowi', 'security:passport-oidc:isEnabled') %}
-                <div class="input-group justify-content-center d-flex mt-5">
-                  <form role="form" action="/passport/oidc" class="d-inline-flex flex-column">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                    <button type="submit" class="btn btn-fill px-0 py-2" id="oidc">
-                      <div class="eff"></div>
-                      <span class="btn-label p-3"><i class="fa fa-openid"></i></span>
-                      <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
-                    </button>
-                    <div class="small text-center">{{ getConfig('crowi', 'security:passport-oidc:providerName') || "OpenID Connect" }}</div>
-                  </form>
-                </div>
-                {% endif %}
-                {% if getConfig('crowi', 'security:passport-saml:isEnabled') %}
-                <div class="input-group justify-content-center d-flex mt-5">
-                  <form role="form" action="/passport/saml" class="d-inline-flex flex-column">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                    <button type="submit" class="btn btn-fill px-0 py-2" id="saml">
-                      <div class="eff"></div>
-                      <span class="btn-label p-3"><i class="fa fa-key"></i></span>
-                      <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
-                    </button>
-                    <div class="small text-center">with SAML</div>
-                  </form>
-                </div>
-                {% endif %}
-                {% if getConfig('crowi', 'security:passport-basic:isEnabled') %}
-                <div class="input-group justify-content-center d-flex mt-5">
-                  <form role="form" action="/passport/basic" class="d-inline-flex flex-column">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                    <button type="submit" class="btn btn-fill px-0 py-2" id="basic">
-                      <div class="eff"></div>
-                      <span class="btn-label p-3"><i class="fa fa-lock"></i></span>
-                      <span class="btn-label-text p-3">{{ t('Sign in') }}</span>
-                    </button>
-                    <div class="small text-center">with Basic Auth</div>
-                  </form>
-                </div>
-              {% endif %}
-              </div>{# ./d-flex flex-row flex-wrap #}
-              <div class="spacer"></div>
-            </div>
-            <div class="border-bottom"></div>
-            <div class="text-center">
-              <button class="collapse-anchor btn btn-xs btn-collapse-external-auth mb-3"
-                  data-toggle="{% if isExternalAuthCollapsible %}collapse{% endif %}" data-target="#external-auth" aria-expanded="false" aria-controls="external-auth">
-                External Auth
-              </button>
-            </div>
-            {% else %}
-            <div class="border-bottom mb-3"></div>
-            {% endif %}
-
-            {% if isExternalAuthCollapsible %}
-            <script>
-              const isMobile = /iphone|ipad|android/.test(window.navigator.userAgent.toLowerCase());
-
-              if (!isMobile) {
-                $(".collapse-anchor").hover(
-                  function() {
-                    $('.collapse-external-auth').collapse('show');
-                  },
-                  function() {
-                    $('.collapse-external-auth').collapse('hide');
-                  }
-                );
-              }
-            </script>
-            {% endif %}
-
-            <div class="row">
-              <div class="col-12 text-right py-2">
-                {% if isRegistrationEnabled %}
-                <a href="#register" id="register" class="link-switch">
-                  <i class="ti-check-box"></i> {{ t('Sign up is here') }}
-                </a>
-                {% else %}
-                &nbsp;
-                {% endif %}
-              </div>
-            </div>
-
-          </div>
+      <!-- [TODO][GW-1913] An AppContainer gets csrf data -->
+      <div id="login-form"
+        data-is-registering="{{ req.query.register or req.body.registerForm or isRegistering }}"
+        data-csrf="{{ csrf() }}"
+      ></div>
 
 
+      <div>
+        <div>
           {% if isRegistrationEnabled %}
           {% if isRegistrationEnabled %}
           <div class="back">
           <div class="back">
             {% if getConfig('crowi', 'security:registrationMode') == 'Restricted' %}
             {% if getConfig('crowi', 'security:registrationMode') == 'Restricted' %}
@@ -391,6 +202,23 @@
 
 
 
 
 {% block body_end %}
 {% block body_end %}
+<!-- [TODO][GW-1865] -->
+{% if isExternalAuthCollapsible %}
+<script>
+  const isMobile = /iphone|ipad|android/.test(window.navigator.userAgent.toLowerCase());
+
+  if (!isMobile) {
+    $(".collapse-anchor").hover(
+      function() {
+        $('.collapse-external-auth').collapse('show');
+      },
+      function() {
+        $('.collapse-external-auth').collapse('hide');
+      }
+    );
+  }
+</script>
+{% endif %}
 <script>
 <script>
   // login
   // login
   $('#register').on('click', function() {
   $('#register').on('click', function() {