Bug 1059021: Added sign in/up link to the Loop panel footer. Patch by MattN and nperriault. r=MattN,r=Standard8
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Fri, 29 Aug 2014 14:11:54 +0100
changeset 202397 fe329c0f2d42bf5e67ba877a6f8ab7743a7ccd66
parent 202396 ce756025b8794f840e84533feef8ea98daf3ab4e
child 202398 4a3334d999a69b7f04d5925a1593c1dfd30bb5f2
push id48419
push userryanvm@gmail.com
push dateFri, 29 Aug 2014 19:40:12 +0000
treeherdermozilla-inbound@4fc87f021e00 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN, Standard8
bugs1059021
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1059021: Added sign in/up link to the Loop panel footer. Patch by MattN and nperriault. r=MattN,r=Standard8
browser/components/loop/MozLoopAPI.jsm
browser/components/loop/MozLoopService.jsm
browser/components/loop/content/js/panel.js
browser/components/loop/content/js/panel.jsx
browser/components/loop/content/shared/css/panel.css
browser/components/loop/test/desktop-local/panel_test.js
browser/locales/en-US/chrome/browser/loop/loop.properties
--- a/browser/components/loop/MozLoopAPI.jsm
+++ b/browser/components/loop/MozLoopAPI.jsm
@@ -342,16 +342,24 @@ function injectLoopAPI(targetWindow) {
         return MozLoopService.hawkRequest(path, method, payloadObj).then((response) => {
           callback(null, response.body);
         }, (error) => {
           callback(Cu.cloneInto(error, targetWindow));
         });
       }
     },
 
+    logInToFxA: {
+      enumerable: true,
+      writable: true,
+      value: function() {
+        return MozLoopService.logInToFxA();
+      }
+    },
+
     /**
      * Copies passed string onto the system clipboard.
      *
      * @param {String} str The string to copy
      */
     copyString: {
       enumerable: true,
       writable: true,
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -775,16 +775,20 @@ this.MozLoopService = {
   /**
    * Start the FxA login flow using the OAuth client and params from the Loop server.
    *
    * The caller should be prepared to handle rejections related to network, server or login errors.
    *
    * @return {Promise} that resolves when the FxA login flow is complete.
    */
   logInToFxA: function() {
+    if (gFxAOAuthTokenData) {
+      return Promise.resolve(gFxAOAuthTokenData);
+    }
+
     return MozLoopServiceInternal.promiseFxAOAuthAuthorization().then(response => {
       return MozLoopServiceInternal.promiseFxAOAuthToken(response.code, response.state);
     }).then(tokenData => {
       gFxAOAuthTokenData = tokenData;
       return tokenData;
     },
     error => {
       gFxAOAuthTokenData = null;
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -72,34 +72,32 @@ loop.panel = (function(_, mozL10n) {
         'dnd-menu': true,
         'hide': !this.state.showMenu
       });
       var availabilityText = this.state.doNotDisturb ?
                               __("display_name_dnd_status") :
                               __("display_name_available_status");
 
       return (
-        React.DOM.div({className: "footer"}, 
-          React.DOM.div({className: "do-not-disturb"}, 
-            React.DOM.div({className: "dnd-status", onClick: this.showDropdownMenu}, 
-              React.DOM.span(null, availabilityText), 
-              React.DOM.i({className: availabilityStatus})
+        React.DOM.div({className: "do-not-disturb"}, 
+          React.DOM.p({className: "dnd-status", onClick: this.showDropdownMenu}, 
+            React.DOM.span(null, availabilityText), 
+            React.DOM.i({className: availabilityStatus})
+          ), 
+          React.DOM.ul({className: availabilityDropdown, 
+              onMouseLeave: this.hideDropdownMenu}, 
+            React.DOM.li({onClick: this.changeAvailability("available"), 
+                className: "dnd-menu-item dnd-make-available"}, 
+              React.DOM.i({className: "status status-available"}), 
+              React.DOM.span(null, __("display_name_available_status"))
             ), 
-            React.DOM.ul({className: availabilityDropdown, 
-                onMouseLeave: this.hideDropdownMenu}, 
-              React.DOM.li({onClick: this.changeAvailability("available"), 
-                  className: "dnd-menu-item dnd-make-available"}, 
-                React.DOM.i({className: "status status-available"}), 
-                React.DOM.span(null, __("display_name_available_status"))
-              ), 
-              React.DOM.li({onClick: this.changeAvailability("do-not-disturb"), 
-                  className: "dnd-menu-item dnd-make-unavailable"}, 
-                React.DOM.i({className: "status status-dnd"}), 
-                React.DOM.span(null, __("display_name_dnd_status"))
-              )
+            React.DOM.li({onClick: this.changeAvailability("do-not-disturb"), 
+                className: "dnd-menu-item dnd-make-unavailable"}, 
+              React.DOM.i({className: "status status-dnd"}), 
+              React.DOM.span(null, __("display_name_dnd_status"))
             )
           )
         )
       );
     }
   });
 
   var ToSView = React.createClass({displayName: 'ToSView',
@@ -267,24 +265,33 @@ loop.panel = (function(_, mozL10n) {
   var PanelView = React.createClass({displayName: 'PanelView',
     propTypes: {
       notifier: React.PropTypes.object.isRequired,
       client: React.PropTypes.object.isRequired,
       // Mostly used for UI components showcase and unit tests
       callUrl: React.PropTypes.string
     },
 
+    handleSignUpLinkClick: function() {
+      navigator.mozLoop.logInToFxA();
+    },
+
     render: function() {
       return (
         React.DOM.div(null, 
           CallUrlResult({client: this.props.client, 
                          notifier: this.props.notifier, 
                          callUrl: this.props.callUrl}), 
           ToSView(null), 
-          AvailabilityDropdown(null)
+          React.DOM.div({className: "footer"}, 
+            AvailabilityDropdown(null), 
+            React.DOM.a({className: "signin-link", href: "#", onClick: this.handleSignUpLinkClick}, 
+              __("panel_footer_signin_or_signup_link")
+            )
+          )
         )
       );
     }
   });
 
   var PanelRouter = loop.desktopRouter.DesktopRouter.extend({
     /**
      * DOM document object.
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -72,36 +72,34 @@ loop.panel = (function(_, mozL10n) {
         'dnd-menu': true,
         'hide': !this.state.showMenu
       });
       var availabilityText = this.state.doNotDisturb ?
                               __("display_name_dnd_status") :
                               __("display_name_available_status");
 
       return (
-        <div className="footer">
-          <div className="do-not-disturb">
-            <div className="dnd-status" onClick={this.showDropdownMenu}>
-              <span>{availabilityText}</span>
-              <i className={availabilityStatus}></i>
-            </div>
-            <ul className={availabilityDropdown}
-                onMouseLeave={this.hideDropdownMenu}>
-              <li onClick={this.changeAvailability("available")}
-                  className="dnd-menu-item dnd-make-available">
-                <i className="status status-available"></i>
-                <span>{__("display_name_available_status")}</span>
-              </li>
-              <li onClick={this.changeAvailability("do-not-disturb")}
-                  className="dnd-menu-item dnd-make-unavailable">
-                <i className="status status-dnd"></i>
-                <span>{__("display_name_dnd_status")}</span>
-              </li>
-            </ul>
-          </div>
+        <div className="do-not-disturb">
+          <p className="dnd-status" onClick={this.showDropdownMenu}>
+            <span>{availabilityText}</span>
+            <i className={availabilityStatus}></i>
+          </p>
+          <ul className={availabilityDropdown}
+              onMouseLeave={this.hideDropdownMenu}>
+            <li onClick={this.changeAvailability("available")}
+                className="dnd-menu-item dnd-make-available">
+              <i className="status status-available"></i>
+              <span>{__("display_name_available_status")}</span>
+            </li>
+            <li onClick={this.changeAvailability("do-not-disturb")}
+                className="dnd-menu-item dnd-make-unavailable">
+              <i className="status status-dnd"></i>
+              <span>{__("display_name_dnd_status")}</span>
+            </li>
+          </ul>
         </div>
       );
     }
   });
 
   var ToSView = React.createClass({
     getInitialState: function() {
       return {seenToS: navigator.mozLoop.getLoopCharPref('seenToS')};
@@ -267,24 +265,33 @@ loop.panel = (function(_, mozL10n) {
   var PanelView = React.createClass({
     propTypes: {
       notifier: React.PropTypes.object.isRequired,
       client: React.PropTypes.object.isRequired,
       // Mostly used for UI components showcase and unit tests
       callUrl: React.PropTypes.string
     },
 
+    handleSignUpLinkClick: function() {
+      navigator.mozLoop.logInToFxA();
+    },
+
     render: function() {
       return (
         <div>
           <CallUrlResult client={this.props.client}
                          notifier={this.props.notifier}
                          callUrl={this.props.callUrl} />
           <ToSView />
-          <AvailabilityDropdown />
+          <div className="footer">
+            <AvailabilityDropdown />
+            <a className="signin-link" href="#" onClick={this.handleSignUpLinkClick}>
+              {__("panel_footer_signin_or_signup_link")}
+            </a>
+          </div>
         </div>
       );
     }
   });
 
   var PanelRouter = loop.desktopRouter.DesktopRouter.extend({
     /**
      * DOM document object.
--- a/browser/components/loop/content/shared/css/panel.css
+++ b/browser/components/loop/content/shared/css/panel.css
@@ -133,16 +133,27 @@
 .status-available {
   background: #6cb23e;
 }
 
 .status-dnd {
   border: 1px solid #888;
 }
 
+/* Sign in/up link */
+
+.signin-link {
+  display: none; /* XXX This should be removed as soon bugs 1047144 & 979845 land */
+  line-height: 100%;
+  font-size: .9em;
+  text-decoration: none;
+  color: #888;
+  margin-top: 16px;
+}
+
 /* Terms of Service */
 
 .terms-service {
   padding: 3px 10px 10px;
   background: #FFF;
   text-align: center;
   opacity: .5;
   transition: opacity .3s;
@@ -152,19 +163,22 @@
 
 .terms-service a {
   color: #0095dd;
 }
 
 /* Footer */
 
 .footer {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: nowrap;
+  justify-content: space-between;
+  align-content: stretch;
+  align-items: flex-start;
   font-size: 1em;
   border-top: 1px solid #D1D1D1;
   background: #EAEAEA;
   color: #7F7F7F;
-  display: flex;
-  align-items: center;
+  padding: 14px;
   margin-top: 14px;
-  flex-direction: row;
-  padding: 14px;
 }
 
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -212,22 +212,33 @@ describe("loop.panel", function() {
       };
 
       view = TestUtils.renderIntoDocument(loop.panel.PanelView({
         notifier: notifier,
         client: fakeClient
       }));
     });
 
+    describe("FxA sign in/up link", function() {
+      it("should trigger the FxA sign in/up process when clicking the link",
+        function() {
+          navigator.mozLoop.logInToFxA = sandbox.stub();
+
+          TestUtils.Simulate.click(
+            view.getDOMNode().querySelector(".signin-link"));
+
+          sinon.assert.calledOnce(navigator.mozLoop.logInToFxA);
+        });
+      });
+
     describe("#render", function() {
       it("should render a ToSView", function() {
         TestUtils.findRenderedComponentWithType(view, loop.panel.ToSView);
       });
     });
-
   });
 
   describe("loop.panel.CallUrlResult", function() {
     var fakeClient, callUrlData, view;
 
     beforeEach(function() {
       callUrlData = {
         callUrl: "http://call.invalid/fakeToken",
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -63,8 +63,10 @@ feedback_window_will_close_in=This windo
 
 share_email_subject2=Invitation to chat
 ## LOCALIZATION NOTE (share_email_body2): In this item, don't translate the
 ## part between {{..}} and leave the \r\n\r\n part alone
 share_email_body2=Please click this link to call me:\r\n\r\n{{callUrl}}
 share_button=Email
 copy_url_button=Copy
 copied_url_button=Copied!
+
+panel_footer_signin_or_signup_link=Sign In or Sign Up