You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2015/05/28 22:13:19 UTC

[17/26] allura git commit: [#7868] ticket:760 UI for phone verification

[#7868] ticket:760 UI for phone verification


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/9c8a9029
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/9c8a9029
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/9c8a9029

Branch: refs/heads/master
Commit: 9c8a9029702bc694469c7ff273ee07ee527a0650
Parents: cb528b7
Author: Igor Bondarenko <je...@gmail.com>
Authored: Mon May 11 14:16:27 2015 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Thu May 28 20:11:46 2015 +0000

----------------------------------------------------------------------
 Allura/allura/nf/allura/css/allura.css          |   4 +
 .../allura/public/nf/js/phone-verification.js   | 166 +++++++++++++++++++
 .../templates/phone_verification_fragment.html  |  21 ++-
 3 files changed, 190 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/9c8a9029/Allura/allura/nf/allura/css/allura.css
----------------------------------------------------------------------
diff --git a/Allura/allura/nf/allura/css/allura.css b/Allura/allura/nf/allura/css/allura.css
index 7d7042a..6c5c1f4 100644
--- a/Allura/allura/nf/allura/css/allura.css
+++ b/Allura/allura/nf/allura/css/allura.css
@@ -83,3 +83,7 @@ tr.rev div.markdown_content p {
 #phone_verification_overlay iframe {
     width: 400px;
 }
+
+#phone_verification_overlay iframe {
+    height: 250px;
+}

http://git-wip-us.apache.org/repos/asf/allura/blob/9c8a9029/Allura/allura/public/nf/js/phone-verification.js
----------------------------------------------------------------------
diff --git a/Allura/allura/public/nf/js/phone-verification.js b/Allura/allura/public/nf/js/phone-verification.js
new file mode 100644
index 0000000..225d2a9
--- /dev/null
+++ b/Allura/allura/public/nf/js/phone-verification.js
@@ -0,0 +1,166 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+var dom = React.createElement;
+var grid = 'grid-8';
+
+/* top-level form state */
+var state = {
+  'step': 'verify',       // 'verify' (enter number) or 'check' (enter pin)
+  'in_progress': false,   // API call in progress
+  'error': null,          // error, returned from previous API call
+  'number': null,         // phone number, entered  by user on 'verify' step
+  'pin': null,            // PIN, entered  by user on 'check' step
+};
+
+
+function set_state(new_state) {
+  /* Set state and re-render entire UI */
+  for (var key in new_state) {
+    state[key] = new_state[key];
+  }
+  render(state);
+}
+
+function render(state) {
+  /* Mount top-level component into the DOM */
+  React.render(
+    dom(PhoneVerificationForm, {state: state}),
+    document.getElementById('phone-verification-form')
+  );
+}
+
+var FormStepMixin = {
+
+  /* 
+   * Subclasses must implement:
+   *   - getAPIUrl(): return API url this step will submit to
+   *   - getAPIData(): returns data to submit to API url
+   *   - getLabel(): returns label text for the input
+   *   - getKey(): returns key in the global state, which will be updated by the input
+   *   - onSuccess(resp): callback wich will be called after successful call to API
+   */
+
+  render: function() {
+    var input_props = {
+      type: 'text',
+      className: grid,
+      value: this.props.state[this.getKey()],
+      disabled: this.isInputDisabled(),
+      onChange: this.handleChange,
+      onKeyDown: this.onKeyDown
+    };
+    var button_props = {
+      onClick: this.handleClick,
+      disabled: this.isButtonDisabled()
+    };
+    var nbsp = String.fromCharCode(160);
+    return dom('div', null,
+             dom('label', {className: grid}, this.getLabel()),
+             dom('input', input_props),
+             dom('div', {className: grid + ' error-text'}, this.props.state.error || nbsp),
+             dom('div', {className: grid},
+               dom('button', button_props, 'Submit')));
+  },
+  
+  handleClick: function() {
+    if (!this.isButtonDisabled()) {
+      set_state({error: null});
+      this.callAPI();
+    }
+  },
+
+  handleChange: function(event) {
+    var new_state = {};
+    new_state[this.getKey()] = event.target.value;
+    set_state(new_state);
+  },
+
+  onKeyDown: function(event) {
+    if (event.key == 'Enter') {
+      this.handleClick();
+    }
+  },
+
+  isInputDisabled() { return this.props.state.in_progress; },
+
+  isButtonDisabled() {
+    var input = this.props.state[this.getKey()];
+    var has_input = Boolean(input);
+    return this.isInputDisabled() || !has_input;
+  },
+
+  callAPI: function() {
+    var url = this.getAPIUrl();
+    var data = this.getAPIData();
+    var csrf = $.cookie('_session_id');
+    data._session_id = csrf;
+    set_state({in_progress: true});
+    $.post(url, data, function(resp) {
+      if (resp.status == 'ok') {
+        this.onSuccess(resp);
+      } else {
+        set_state({error: resp.error});
+      }
+    }.bind(this)).fail(function() {
+      var error = 'Request to API failed, please try again';
+      set_state({error: error});
+    }).always(function() {
+      set_state({in_progress: false});
+    });
+  }
+};
+
+
+var StepVerify = React.createClass({
+  mixins: [FormStepMixin],
+  
+  getAPIUrl: function() { return 'verify_phone'; },
+  getAPIData: function() { return {'number': this.props.state[this.getKey()]}; },
+  getLabel: function() { return 'Enter phone number'; },
+  getKey: function() { return 'number'; },
+  onSuccess: function() { set_state({step: 'check'}); }
+});
+
+var StepCheck = React.createClass({
+  mixins: [FormStepMixin],
+  
+  getAPIUrl: function() { return 'check_phone_verification'; },
+  getAPIData: function() { return {'pin': this.props.state[this.getKey()]}; },
+  getLabel: function() { return 'Enter PIN'; },
+  getKey: function() { return 'pin'; },
+  onSuccess: function() { window.top.location.reload(); }
+});
+
+var PhoneVerificationForm = React.createClass({
+  /*
+   * Top-level component for phone verification form
+   * Used as controller, to render proper form step.
+   */
+  render: function() {
+    var step = this.props.state.step;
+    if (step == 'verify') {
+        return dom(StepVerify, {state: this.props.state});
+    } else if (step == 'check') {
+        return dom(StepCheck, {state: this.props.state});
+    }
+  }
+});
+
+render(state);  // initial render

http://git-wip-us.apache.org/repos/asf/allura/blob/9c8a9029/Allura/allura/templates/phone_verification_fragment.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/phone_verification_fragment.html b/Allura/allura/templates/phone_verification_fragment.html
index 87b0f1b..faa0009 100644
--- a/Allura/allura/templates/phone_verification_fragment.html
+++ b/Allura/allura/templates/phone_verification_fragment.html
@@ -23,6 +23,8 @@
   {% import g.theme.jinja_macros as theme_macros with context %}
 {% endif %}
 {% do g.register_forge_js('js/react.min.js', location='head_js') %}
+{% do g.register_forge_js('js/jquery-base.js', location='head_js') %}
+{% do g.register_forge_js('js/phone-verification.js', location='body_js_tail') %}
 {% do g.theme.require() %}
 {% do g.resource_manager.register_widgets(c) %}
 {# paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ #}
@@ -42,10 +44,27 @@
         <style type="text/css">
             html { overflow: hidden; }
             body { padding-top: 1em; }
+            .error-text {
+              color: red;
+              margin-top: .5em;
+              margin-bottom: .5em;
+            }
         </style>
     </head>
     <body>
-        TODO: Phone verification UI
+        <div class="grid-9">
+          {% block phone_validation_message %}
+          <p>
+            In order to register a project you need to perform phone validation
+            (just this once).  Please, enter your phone number below. You'll
+            receive an SMS message with a PIN you need to provide on the next
+            step.  Your phone number will not be stored.
+          </p>
+          {% endblock phone_validation_message %}
+          <div id="phone-verification-form">
+            {# Phone verification form will be mounted here (see phone-verification.js) #}
+          </div>
+        </div>
 
         {% for blob in g.resource_manager.emit('body_js_tail') %}
           {{ blob }}