]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1200765 - Make login UX mobile friendly to assist mobile authentication workflow
authorDylan William Hardison <dylan@hardison.net>
Thu, 8 Oct 2015 12:28:46 +0000 (08:28 -0400)
committerDylan William Hardison <dylan@hardison.net>
Thu, 8 Oct 2015 12:29:01 +0000 (08:29 -0400)
.htaccess
Bugzilla/Template.pm
skins/standard/global.css
skins/standard/mobile.css [new file with mode: 0644]
template/en/default/account/auth/login.html.tmpl
template/en/default/global/header.html.tmpl
template/en/default/mfa/totp/verify.html.tmpl

index ab9b053da3194d707bc01578f078d94fceca62c9..b5ec2536fdc636e855c5c20817ab20bfe952386c 100644 (file)
--- a/.htaccess
+++ b/.htaccess
@@ -91,3 +91,4 @@ RewriteRule ^rest/(.*)$ rest.cgi/$1 [NE]
 RewriteRule ^(?:latest|1\.2|1\.3)/(.*)$ extensions/BzAPI/bin/rest.cgi/$1 [NE]
 RewriteRule ^bzapi/(.*)$ extensions/BzAPI/bin/rest.cgi/$1 [NE]
 RewriteRule ^data/assets/ZeroClipboard.swf(.*)$ extensions/BugModal/web/ZeroClipboard/ZeroClipboard.swf$1 [NE]
+RewriteRule ^login$ index.cgi?GoAheadAndLogIn=1 [NE]
index bc0d77084f80e420ad11ec53faa46ff15ec313e3..b03698477f4caad2bc3b7b2bd318bde8e3fd2240 100644 (file)
@@ -1117,6 +1117,11 @@ sub create {
                 return \@optional;
             },
             'default_authorizer' => sub { return Bugzilla::Auth->new() },
+
+            # It is almost always better to do mobile feature detection, client side in js.
+            # However, we need to set the meta[name=viewport] server-side or the behavior is
+            # not as predictable. It is possible other parts of the frontend may use this feature too.
+            'is_mobile_browser' => sub { return Bugzilla->cgi->user_agent =~ /Mobi/ },
         },
     };
     # Use a per-process provider to cache compiled templates in memory across
index 81736f052972cbe7e982f03761528e2237dd5494..3790539e61291e3a01acfd9b3be8a9e8d6fa7ca9 100644 (file)
@@ -724,3 +724,21 @@ input.required, select.required, span.required_explanation {
     background-repeat: no-repeat !important;
     background-position: right 8px center !important;
 }
+
+#login .field-login, #login .field-password {
+    line-height: 32px;
+    display: block;
+    padding-top: 2px;
+    padding-bottom: 2px;
+}
+
+#login .field-login label, #login .field-password label {
+    clear: left;
+    width: 7em;
+    display: inline-block;
+    font-weight: bold;
+}
+
+#login .field-restrict, #login .field-remember {
+    margin-left: 7em;
+}
diff --git a/skins/standard/mobile.css b/skins/standard/mobile.css
new file mode 100644 (file)
index 0000000..61179e9
--- /dev/null
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Source Code Form is "Incompatible With Secondary Licenses", as
+ * defined by the Mozilla Public License, v. 2.0.
+ */
+
+@media
+only screen and (max-device-width : 720px) {
+    #header, #footer {
+        display: none;
+    }
+    .cookie-notify {
+        display: none;
+    }
+
+    #login .field-login label, #login .field-password label {
+        display: block;
+    }
+
+    #login .field-login, #login .field-password {
+        line-height: auto;
+        padding-top: 0px;
+        padding-bottom: 0px;
+    }
+
+    #login .field-restrict, #login .field-remember {
+        margin-left: 0px;
+    }
+    #login .field-submit {
+        padding-top: 4px;
+    }
+
+    h1 {
+        font-size: 1.5em;
+    }
+
+    .verify-totp input[type="text"] {
+        font-size: 28px;
+    }
+
+    .verify-totp input[type="submit"] {
+        font-size: 1em;
+    }
+}
+
+@media
+only screen and (-webkit-min-device-pixel-ratio: 2),
+only screen and (min--moz-device-pixel-ratio: 2),
+only screen and (-o-min-device-pixel-ratio: 2/1),
+only screen and (min-device-pixel-ratio: 2),
+only screen and (min-resolution: 192dpi),
+only screen and (min-resolution: 2dppx) {
+    #privacy_policy {
+        font-size: small;
+    }
+
+    body {
+        font-size: medium;
+    }
+
+    label.checkbox-note {
+        font-size: small;
+    }
+}
index 922a55bd40444f4355168980b507d6c290f00896..33aeaeaeaa80a4e8d0ae8de9c0a1007dc6f97df5 100644 (file)
@@ -30,8 +30,9 @@
 [% PROCESS global/variables.none.tmpl %]
 
 [% PROCESS global/header.html.tmpl
-  title = "Log in to $terms.Bugzilla",
-  onload = "document.forms['login'].Bugzilla_login.focus()"
+  title        = "Log in to $terms.Bugzilla",
+  onload       = "document.forms['login'].Bugzilla_login.focus()"
+  allow_mobile = 1
 %]
 
 [% USE Bugzilla %]
   I need an email address and password to continue.
 </p>
 
-<form name="login" action="[% target FILTER html %]" method="POST"
-[%- IF Bugzilla.cgi.param("data") %] enctype="multipart/form-data"[% END %]>
-  <table>
-    <tr>
-      <th align="right"><label for="Bugzilla_login">Email Address:</label></th>
-      <td>
-        <input size="35" id="Bugzilla_login" name="Bugzilla_login"
-               [%- ' type="email"' UNLESS Param('emailsuffix') %]>
-        [% Param('emailsuffix') FILTER html %]
-      </td>
-    </tr>
-    <tr>
-      <th align="right"><label for="Bugzilla_password">Password:</label></th>
-      <td>
-        <input type="password" size="35" id="Bugzilla_password" name="Bugzilla_password">
-      </td>
-    </tr>
+<div id="login" class="login-form">
+  <form name="login" action="[% target FILTER html %]" method="POST"
+        [%- IF Bugzilla.cgi.param("data") %] enctype="multipart/form-data"[% END %]>
+    <div class="field-login">
+      <label for="Bugzilla_login">Email Address:</label>
+      <input id="Bugzilla_login" name="Bugzilla_login"
+             [%- ' type="email"' UNLESS Param('emailsuffix') %]>
+      [% Param('emailsuffix') FILTER html %]
+    </div>
+
+    <div class="field-password">
+      <label for="Bugzilla_password">Password:</label>
+      <input type="password" id="Bugzilla_password" name="Bugzilla_password">
+    </div>
 
     [% IF Param('rememberlogin') == 'defaulton' || 
-          Param('rememberlogin') == 'defaultoff' %]
-      <tr>
-        <th>&nbsp;</th>
-        <td>
-          <input type="checkbox" id="Bugzilla_remember" name="Bugzilla_remember" value="on"
-                 [%+ "checked" IF Param('rememberlogin') == "defaulton" %]>
-          <label for="Bugzilla_remember">Remember my email address</label>
-        </td>
-      </tr>
+       Param('rememberlogin') == 'defaultoff' %]
+      <div class="field-remember">
+        <input type="checkbox" id="Bugzilla_remember" name="Bugzilla_remember" value="on"
+               [%+ "checked" IF Param('rememberlogin') == "defaulton" %]>
+        <label for="Bugzilla_remember" class="checkbox-note">
+          Remember my email address
+        </label>
+      </div>
     [% END %]
 
-    <tr>
-      <th>&nbsp;</th>
-      <td>
-        <input type="checkbox" id="Bugzilla_restrictlogin" name="Bugzilla_restrictlogin"
-               checked="checked">
-        <label for="Bugzilla_restrictlogin">Restrict this session to this IP address
-        (using this option improves security)</label>
-      </td>
-    </tr>
-  </table>
+    [% PROCESS "global/hidden-fields.html.tmpl"
+       exclude="^Bugzilla_(login|password|restrictlogin)$" %]
 
-  [% PROCESS "global/hidden-fields.html.tmpl"
-     exclude="^Bugzilla_(login|password|restrictlogin)$" %]
+    <div class="field-restrict">
+      <input type="checkbox" id="Bugzilla_restrictlogin" name="Bugzilla_restrictlogin"
+             checked="checked">
+      <label for="Bugzilla_restrictlogin" class="checkbox-note">
+        Restrict this session to this IP address
+        (using this option improves security)</label>
+    </div>
 
-  <input type="hidden" name="Bugzilla_login_token"
-         value="[% get_login_request_token() FILTER html %]">
-  <input type="submit" name="GoAheadAndLogIn" value="Log in" id="log_in">
+    <div class="field-submit">
+      <input type="hidden" name="Bugzilla_login_token"
+             value="[% get_login_request_token() FILTER html %]">
+      <input type="submit" name="GoAheadAndLogIn" value="Log in" id="log_in">
+    </div>
 
-  <p>
-    (Note: you should make sure cookies are enabled for this site. 
-    Otherwise, you will be required to log in frequently.)
-  </p>
-</form>
+    <p class="cookie-notify">
+      (Note: you should make sure cookies are enabled for this site.
+      Otherwise, you will be required to log in frequently.)
+    </p>
+  </form>
+</div>
 
 [% Hook.process('additional_methods') %]
 
       If you have an account, but have forgotten your password,
       enter your email address below and submit a request
       to change your password.<br>
-      <input size="35" name="loginname">
+      <input name="loginname">
       <input type="hidden" id="token" name="token" value="[% issue_hash_token(['reqpw']) FILTER html %]">
       <input type="submit" id="request" value="Reset Password">
     </form>
index 3f70b9453a2dab09fc6a48927ca28b388f350c30..23634ed43b0e158d57f32e4ad3d1b9a75ddb47f5 100644 (file)
@@ -37,6 +37,7 @@
   # atomlink: Atom link URL, May contain HTML
   # generate_api_token: generate a token which can be used to make authenticated webservice calls
   # no_body: if true the body element will not be generated
+  # allow_mobile: allow special CSS and viewport for detected mobile useragents
   #%]
 
 [% IF message %]
 [%# Add our required jQuery plugins %]
 [% jquery.push("cookie", "devbridgeAutocomplete") %]
 
+[% IF allow_mobile && is_mobile_browser %]
+  [% style_urls.push("skins/standard/mobile.css") %]
+[% END %]
+
 [%# We should be able to set the default value of the header variable
   # to the value of the title variable using the DEFAULT directive,
   # but that doesn't work if a caller sets header to the empty string
     [%# Required for the 'Autodiscovery' feature in Firefox 2 and IE 7. %]
     <link rel="search" type="application/opensearchdescription+xml"
                        title="[% terms.BugzillaTitle %]" href="./search_plugin.cgi">
+    [% IF allow_mobile && is_mobile_browser %]
+      <meta name="viewport" content="width=device-width, initial-scale=1">
+    [% END %]
     [% Hook.process("additional_header") %]
   </head>
 
index ad75dc6bc1cbc7388a3f67354f6891407da44f44..7314d909a5be74f14b71338a6971184d11c55ae9 100644 (file)
@@ -8,6 +8,7 @@
 
 [% INCLUDE global/header.html.tmpl
   title = "Account Verification"
+  allow_mobile = 1
 %]
 
 <h1>Account Verification</h1>
   <b>[% reason FILTER html %]</b> requires verification.<br>
   Please enter your verification code from your TOTP application:
 </p>
-
-<form method="POST" action="[% postback.action FILTER none %]">
-  [% FOREACH field IN postback.fields.keys %]
-    <input type="hidden" name="[% field FILTER html %]" value="[% postback.fields.item(field) FILTER html %]">
-  [% END %]
-  <input type="text" name="code" id="code"
-        placeholder="123456" maxlength="9" pattern="\d{6,9}" size="10"
-        autocomplete="off" required autofocus><br>
-  <br>
-  <input type="submit" value="Submit">
-</form>
+<div class="verify-totp">
+  <form method="POST" action="[% postback.action FILTER none %]">
+    [% FOREACH field IN postback.fields.keys %]
+      <input type="hidden" name="[% field FILTER html %]" value="[% postback.fields.item(field) FILTER html %]">
+    [% END %]
+    <input type="text" name="code" id="code"
+           placeholder="123456" maxlength="9" pattern="\d{6,9}" size="10"
+           autocomplete="off" required autofocus><br>
+    <br>
+    <input type="submit" value="Submit">
+  </form>
+</div>
 
 [% INCLUDE global/footer.html.tmpl %]