changeset 21:03c5651a371e

added banner image, uni-form, Attendees global
author Atul Varma <avarma@mozilla.com>
date Fri, 25 Jun 2010 16:00:15 -0700
parents 3197696debbe
children da287d1b9405
files static-files/banner.png static-files/default.uni-form.css static-files/index.html static-files/index.js static-files/uni-form.css static-files/uni-form.jquery.js summitidp/app.py tests/test_app.py
diffstat 8 files changed, 566 insertions(+), 64 deletions(-) [+]
line wrap: on
line diff
Binary file static-files/banner.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/static-files/default.uni-form.css	Fri Jun 25 16:00:15 2010 -0700
@@ -0,0 +1,153 @@
+/* ------------------------------------------------------------------------------
+   
+   UNI-FORM DEFAULT by DRAGAN BABIC                         (v2) | Wed, 31 Mar 10
+   
+   ------------------------------------------------------------------------------
+   
+   Copyright (c) 2010, Dragan Babic
+   
+   Permission is hereby granted, free of charge, to any person
+   obtaining a copy of this software and associated documentation
+   files (the "Software"), to deal in the Software without
+   restriction, including without limitation the rights to use,
+   copy, modify, merge, publish, distribute, sublicense, and/or sell
+   copies of the Software, and to permit persons to whom the
+   Software is furnished to do so, subject to the following
+   conditions:
+   
+   The above copyright notice and this permission notice shall be
+   included in all copies or substantial portions of the Software.
+   
+   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+   OTHER DEALINGS IN THE SOFTWARE.
+   
+   ------------------------------------------------------------------------------ */
+
+.uniForm{}
+    
+    .uniForm legend{ font-weight: bold; font-size: 100%; margin: 0; padding: 1.5em 0; }
+  
+    .uniForm .ctrlHolder{ padding: 1em; border-bottom: 1px solid #efefef; }
+    .uniForm .ctrlHolder.focused{ background: #fffcdf; }
+    
+    .uniForm .inlineLabels .noLabel{}
+    
+    .uniForm .buttonHolder{ background: #efefef; text-align: right; margin: 1.5em 0 0 0; padding: 1.5em;
+                            /* CSS3 */
+                            border-radius:         4px;
+                            -webkit-border-radius: 4px;
+                            -moz-border-radius:    4px;
+                            -o-border-radius:      4px;
+                            -khtml-border-radius:  4px;
+                          }
+      .uniForm .buttonHolder .primaryAction{ padding: 10px 22px; line-height: 1; background: #254a86; border: 1px solid #163362; font-size: 12px; font-weight: bold; color: #fff;
+                                             /* CSS3 */
+                                             border-radius:         4px;
+                                             -webkit-border-radius: 4px;
+                                             -moz-border-radius:    4px;
+                                             -o-border-radius:      4px;
+                                             -khtml-border-radius:  4px;
+                                             box-shadow: 1px 1px 0 #fff;
+                                             -webkit-box-shadow: 1px 1px 0 #fff;
+                                             -moz-box-shadow: 1px 1px 0 #fff;
+                                             text-shadow: -1px -1px 0 rgba(0,0,0,.25);
+                                           }
+      .uniForm .buttonHolder .primaryAction:active{ position: relative; top: 1px; }
+      .uniForm .secondaryAction            { text-align: left; }
+      .uniForm button.secondaryAction      { background: transparent; border: none; color: #777; margin: 1.25em 0 0 0; padding: 0; }
+      
+        .uniForm .inlineLabels label em,
+        .uniForm .inlineLabels .label em{ font-style: normal; font-weight: bold; }
+        .uniForm label small{ font-size: .75em; color: #777; }
+        
+      .uniForm .textInput,
+      .uniForm textarea    { padding: 4px 2px; border: 1px solid #aaa; background: #fff; }
+      .uniForm textarea    { height: 12em; }
+      .uniForm select      {}
+      .uniForm .fileUpload {}
+      
+      .uniForm ul{}
+        .uniForm li{}
+          .uniForm ul li label{ font-size: .85em; }
+            
+            .uniForm .small {}
+            .uniForm .medium{}
+            .uniForm .large {} /* Large is default and should match the value you set for .textInput, textarea or select */
+            .uniForm .auto  {}
+            .uniForm .small,
+            .uniForm .medium,
+            .uniForm .auto{}
+      
+      /* Get rid of the 'glow' effect in WebKit, optional */
+      .uniForm .ctrlHolder .textInput:focus,
+      .uniForm .ctrlHolder textarea:focus{ outline: none; }
+      
+      .uniForm .formHint                    { font-size: .85em; color: #777; }
+      .uniForm .inlineLabels .formHint      { padding-top: .5em; }
+      .uniForm .ctrlHolder.focused .formHint{ color: #333; }
+      
+/* ----------------------------------------------------------------------------- */
+/* ############################### Messages #################################### */
+/* ----------------------------------------------------------------------------- */
+
+  /* Error message at the top of the form */
+  .uniForm #errorMsg{ background: #ffdfdf; border: 1px solid #f3afb5; margin: 0 0 1.5em 0; padding: 0 1.5em;
+                      /* CSS3 */
+                      border-radius:         4px;
+                      -webkit-border-radius: 4px;
+                      -moz-border-radius:    4px;
+                      -o-border-radius:      4px;
+                      -khtml-border-radius:  4px;
+                    }
+    .uniForm #errorMsg h3{} /* Feel free to use a heading level suitable to your page structure */
+    .uniForm #errorMsg ol{ margin: 0 0 1.5em 0; padding: 0; }
+      .uniForm #errorMsg ol li{ margin: 0 0 3px 1.5em; padding: 7px; background: #f6bec1; position: relative; font-size: .85em;
+                                /* CSS3 */
+                                border-radius:         4px;
+                                -webkit-border-radius: 4px;
+                                -moz-border-radius:    4px;
+                                -o-border-radius:      4px;
+                                -khtml-border-radius:  4px;
+                              }
+  
+  .uniForm .ctrlHolder.error,
+  .uniForm .ctrlHolder.focused.error{ background: #ffdfdf; border: 1px solid #f3afb5;
+                                      /* CSS3 */
+                                      border-radius:         4px;
+                                      -webkit-border-radius: 4px;
+                                      -moz-border-radius:    4px;
+                                      -o-border-radius:      4px;
+                                      -khtml-border-radius:  4px;
+                                    }
+    .uniForm .ctrlHolder.error input.error,
+    .uniForm .ctrlHolder.error select.error,
+    .uniForm .ctrlHolder.error textarea.error{ color: #af4c4c; margin: 0 0 6px 0; padding: 4px; }
+  
+  /* Success messages at the top of the form */
+  .uniForm #okMsg{ background: #c8ffbf; border: 1px solid #a2ef95; margin: 0 0 1.5em 0; padding: 0 1.5em; text-align: center;
+                   /* CSS3 */
+                   border-radius:         4px;
+                   -webkit-border-radius: 4px;
+                   -moz-border-radius:    4px;
+                   -o-border-radius:      4px;
+                   -khtml-border-radius:  4px;
+                 }
+    .uniForm #OKMsg p{ margin: 0; }
+
+/* ----------------------------------------------------------------------------- */
+/* ############################### Columns ##################################### */
+/* ----------------------------------------------------------------------------- */
+
+    .uniForm .col{}
+    .uniForm .col.first{}
+    .uniForm .col.last{}
+    .uniForm .col{ margin-bottom: 1.5em; }
+    /* Use .first and .last classes to control the layout/spacing of your columns */
+    .uniForm .col.first{ width: 49%; float: left; clear: none;                   }
+    .uniForm .col.last { width: 49%; float: right; clear: none; margin-right: 0; }
\ No newline at end of file
--- a/static-files/index.html	Fri Jun 25 20:33:01 2010 +0000
+++ b/static-files/index.html	Fri Jun 25 16:00:15 2010 -0700
@@ -1,59 +1,133 @@
 <html>
 <head>
   <title>Mozilla Summit Identity Pal</title>
+  <link href="uni-form.css" media="all" rel="stylesheet"/>
+  <link href="default.uni-form.css" media="all" rel="stylesheet"/>
   <style>
+  #container {
+    width: 600px;
+    margin: 0 auto;
+    font-family: Helvetica Neue, Arial, sans-serif;
+    font-size: 9pt;
+  }
+
+  a {
+    color: #6C2F70;
+  }
+
   .screen {
     display: none;
   }
 
+  .success {
+    display: none;
+    color: green;
+  }
+
   .error {
     display: none;
-    background: red;
-    color: white;
+    color: crimson;
   }
 
   .button {
+    -moz-border-radius: 4px;
+    background: #e0e0e0;
+    display: inline;
+    padding: 4px;
     cursor: pointer;
-    background: yellow;
     color: black;
   }
 
+  div.button:hover {
+    background: #b0b0b0;
+  }
+
   .login-email {
-    font-family: monospace;
-    color: blue;
+    font-family: Monaco, monospace;
+    font-size: 8pt;
+    color: #6C2F70;
   }
   </style>
 </head>
 <body>
+<div id="container">
 <img id="banner" src="banner.png">
 <div id="login" class="screen">
-  <p>You don't seem to be logged in!</p>
-  <p>Enter your email address and press enter:</p>
-  <form>
-    <input type="text" id="email">
-  </form>
-  <div class="error">
-    <p>Fail! Maybe you entered an email address that isn't
-    registered for the summit, or maybe there's a bug
-    in this app.</p>
-  </div>
+  <p>Welcome to the Mozilla Summit Identity Pal!</p>
+  <p>Here you can enter information about yourself for sharing only
+    with other Summit-goers. It's a great way to find out who 
+    will be at the Summit.</p>
+  <form action="#" class="uniForm">
+  <fieldset>
+    <div class="ctrlHolder">
+      <label for="Email Address">Email Address</label>
+      <input id="email" name="" value="" size="35" class="textInput" type="text">
+      <p class="formHint">This should be the same email address you
+      receive your Summit Newsletter at.</p>
+    </div>
+    <div class="error">
+      Alas, something didn't work. Maybe you entered an email address that isn't
+      registered for the Summit?
+    </div>
+    <div class="buttonHolder"><button type="submit" class="primaryAction">Submit</button></div>
+  </fieldset>
+  </form>          
 </div>
 <div id="wait-for-verify" class="screen">
   <p>An email was sent to you. Check your inbox and click the link in
   the email to log in.</p>
+  <form action="#" class="uniForm start-over">
+  <fieldset>
   <div class="error">
-    <p>Fail! Maybe the email was sent too long ago, or this
-    verification URL was already visited before, or there's
-    a bug in this app.</p>
+    Fail! Maybe the email was sent too long ago, or this
+    verification URL was already visited before?
   </div>
-  <div class="button start-over">Something messed up. Start
-  over!</div>
+  <div class="buttonHolder"><button type="submit"
+    class="primaryAction">Start Over</button></div>
+  </fieldset>
+  </form>
 </div>
 <div id="logged-in" class="screen">
   <p>Hi! You are logged in as <span class="login-email"></span>.</p>
-  <div class="button start-over">Log Out</div>
+  <p>Please fill out some of the fields below. This information will
+  be made available only to other summit attendees and their cool
+  mashups and web apps.</p>
+  <form action="#" class="uniForm personal-info">
+  <fieldset>
+    <div class="ctrlHolder">
+      <label for="">Your Name</label>
+      <input id="name" name="" value="" size="35" class="textInput" type="text">
+      <p class="formHint">What should people call you?</p>
+    </div>
+
+    <div class="ctrlHolder">
+      <label for="">Profile Image URL</label>
+      <input id="profile-image" name="" value="" size="35" class="textInput" type="text">
+      <p class="formHint">If you have a picture of yourself somewhere,
+      paste in its URL here. It's expected to have square dimensions.</p>
+    </div>
+
+    <div class="ctrlHolder">
+      <label for="">Bugzilla Email</label>
+      <input id="bugzilla-email" name="" value="" size="35" class="textInput" type="text">
+      <p class="formHint">If you use Bugzilla, enter your email here.</p>
+    </div>
+
+    <div class="success">
+    Great success! Your information has been updated.
+    </div>
+
+    <div class="error">
+      Fail! Maybe you're not connected to the internet?
+    </div>
+
+    <div class="buttonHolder"><button type="submit" class="primaryAction">Submit</button></div>
+  </fieldset>
+  </form>
+</div>
 </div>
 <script src="jquery-1.4.2.min.js"></script>
+<script src="uni-form.jquery.js"></script>
 <script src="api.js"></script>
 <script src="index.js"></script>
 </body>
--- a/static-files/index.js	Fri Jun 25 20:33:01 2010 +0000
+++ b/static-files/index.js	Fri Jun 25 16:00:15 2010 -0700
@@ -25,42 +25,89 @@
   observers: []
 };
 
-var Api = {
-  postJSON: function Api_postJSON(path, obj, cb) {
-    var options = {
-      url: path,
-      type: "POST",
-      contentType: "application/json",
-      data: JSON.stringify(obj),
-      dataType: "json",
-      success: function(data) {
-        cb(true, data);
-      },
-      error: function(req, textStatus, errorThrown) {
-        var data = null;
-        if (req.getResponseHeader("Content-Type") == "application/json") {
-          try {
-            data = JSON.parse(req.responseText);
-          } catch (e) {}
-        }
-        cb(false, data);
-      }
-    };
-    return jQuery.ajax(options);
-  }
-};
-
 (
-  // This anonymous closure sets up the UI.
+  // Config initialization code.
   function(window) {
     function ensureStateIsValid() {
       if (!('state' in Config.value))
         Config.setValue({state: "login"});
     }
 
+    Config.observers.push(ensureStateIsValid);
+
+    $(window).ready(ensureStateIsValid);
+  }
+)(window);
+
+(
+  // Attendee initialization code.
+  function(window) {
+    var req;
+
+    var Attendees = {
+      refresh: function refresh() {
+        if (req)
+          return;
+        var self = this;
+        if (Config.value.token) {
+          req = jQuery.getJSON(
+            "/profile",
+            {token: Config.value.token},
+            function(data, textStatus) {
+              // TODO: Might need to add a failure callback too?
+              req = null;
+              if (textStatus == "success") {
+                self.value = data.contents;
+                self.observers.forEach(function(cb) { cb(); });
+              } else {
+                // TODO: Raise an error?
+              }
+            });
+        }
+      },
+      value: null,
+      observers: []
+    };
+
+    function refreshAttendees() {
+      if (Config.value.state == "logged-in" && !Attendees.users)
+        Attendees.refresh();
+    }
+
+    Config.observers.push(refreshAttendees);
+    $(window).ready(refreshAttendees);
+
+    window.Attendees = Attendees;
+  }
+)(window);
+
+jQuery.postJSON = function postJSON(path, obj, cb) {
+  var options = {
+    url: path,
+    type: "POST",
+    contentType: "application/json",
+    data: JSON.stringify(obj),
+    dataType: "json",
+    success: function(data) {
+      cb(true, data);
+    },
+    error: function(req, textStatus, errorThrown) {
+      var data = null;
+      if (req.getResponseHeader("Content-Type") == "application/json") {
+        try {
+          data = JSON.parse(req.responseText);
+        } catch (e) {}
+      }
+      cb(false, data);
+    }
+  };
+  return jQuery.ajax(options);
+};
+
+(
+  // This anonymous closure sets up the UI.
+  function(window) {
     function updateUI() {
-      ensureStateIsValid();
-
       $(".screen").hide();
       $("#" + Config.value.state).show();
       switch (Config.value.state) {
@@ -74,6 +121,17 @@
       }
     }
 
+    function fillUserInfo() {
+      var userInfo = Attendees.value[Config.value.userID];
+      if (!userInfo)
+        userInfo = {};
+      $("#name").val(userInfo.name || "");
+      $("#profile-image").val(userInfo.profileImage || "");
+      $("#bugzilla-email").val(userInfo.bugzillaEmail || "");
+    }
+
+    Attendees.observers.push(fillUserInfo);
+
     function bindConfigToUI() {
       var lastChanged = Config.lastChanged;
 
@@ -96,15 +154,17 @@
     }
 
     function initUI() {
-      ensureStateIsValid();
-
-      $(".start-over").click(function() { Config.wipe(); });
+      $(".start-over").submit(
+        function(event) {
+          event.preventDefault();
+          Config.wipe();
+        });
 
       $("#login form").submit(
         function(event) {
           event.preventDefault();
           $("#login .error").hide();
-          Api.postJSON(
+          jQuery.postJSON(
             "/challenge/request",
             {email: $(this).find("#email").val() },
             function(success, data) {
@@ -116,11 +176,36 @@
             });
         });
 
+      $("#logged-in form").submit(
+        function(event) {
+          event.preventDefault();
+          $("#logged-in .success").hide();
+          $("#logged-in .error").hide();
+
+          var contents = {
+            name: $("#name").val(),
+            profileImage: $("#profile-image").val(),
+            bugzillaEmail: $("#bugzilla-email").val()
+          };
+
+          jQuery.postJSON(
+            "/profile",
+            {token: Config.value.token,
+             contents: contents},
+            function(success, data) {
+              if (success) {
+                $("#logged-in .success").slideDown();
+              } else {
+                $("#logged-in .error").slideDown();
+              }
+            });
+        });
+
       var verify = window.location.hash.match(/#verify=(.+)/);
       if (verify && Config.value.state != "logged-in") {
         verify = verify[1];
         Config.setValue({state: "wait-for-verify"});
-        Api.postJSON(
+        jQuery.postJSON(
           "/challenge/respond",
           {token: verify},
           function(success, data) {
@@ -129,6 +214,7 @@
             if (success) {
               Config.setValue({state: "logged-in",
                                token: data.token,
+                               userID: data.user_id,
                                email: data.email});
             } else {
               $("#wait-for-verify .error").slideDown();
@@ -145,6 +231,8 @@
 (
   // Set up the postMessage API.
   function(window) {
+    var attendeeCallbacks = [];
+
     var myOrigin = window.location.protocol + "//" + window.location.host;
     var handlers = {
       getAllUsers: function(options, cb, origin) {
@@ -160,18 +248,20 @@
           return;
         }
 
-        jQuery.getJSON(
-          "/profile",
-          {token: Config.value.token},
-          function(data, textStatus) {
-            if (textStatus == "success")
-              cb({users: data});
-            else
-              cb({error: "an error occurred retrieving user data."});
-          });
+        if (Attendees.value) {
+          cb({users: Attendees.value});
+        } else
+          attendeeCallbacks.push(cb);
       }
     };
 
     var server = new Summit.Server(handlers);
+
+    Attendees.observers.push(
+      function() {
+        var cbs = [];
+        attendeeCallbacks = [];
+        cbs.forEach(function(cb) { cb({users: Attendees.value}); });
+      });
   }
 )(window);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/static-files/uni-form.css	Fri Jun 25 16:00:15 2010 -0700
@@ -0,0 +1,154 @@
+/* ------------------------------------------------------------------------------
+
+   Copyright (c) 2010, Dragan Babic
+   
+   Permission is hereby granted, free of charge, to any person
+   obtaining a copy of this software and associated documentation
+   files (the "Software"), to deal in the Software without
+   restriction, including without limitation the rights to use,
+   copy, modify, merge, publish, distribute, sublicense, and/or sell
+   copies of the Software, and to permit persons to whom the
+   Software is furnished to do so, subject to the following
+   conditions:
+   
+   The above copyright notice and this permission notice shall be
+   included in all copies or substantial portions of the Software.
+   
+   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+   OTHER DEALINGS IN THE SOFTWARE.
+
+   ------------------------------------------------------------------------------ */
+/* #############################   GENERALS   ################################### */
+/* ------------------------------------------------------------------------------ */
+
+.uniForm{ margin: 0; padding: 0; position: relative; z-index: 1; } /* reset stuff */
+  
+  /* Some generals and more resets */
+  .uniForm fieldset{ border: none; margin: 0; padding: 0; }
+    .uniForm fieldset legend{ margin: 0; padding: 0; }
+    
+    /* This are the main units that contain form elements */
+    .uniForm .ctrlHolder,
+    .uniForm .buttonHolder{ margin: 0; padding: 0; clear: both; }
+    
+    /* Clear all floats */ 
+    .uniForm:after,
+      .uniForm .buttonHolder:after, 
+      .uniForm .ctrlHolder:after, 
+        .uniForm .ctrlHolder .multiField:after,
+          .uniForm .inlineLabel:after{ content: "."; display: block; height: 0; line-height: 0; font-size: 0; clear: both; min-height: 0; visibility: hidden; }
+      
+      .uniForm label,
+      .uniForm button{ cursor: pointer; }
+
+/* ------------------------------------------------------------------------------ */
+/* ##########################   DEFAULT LAYOUT   ################################ */
+/* ------------------------------------------------------------------------------ */
+/*       Styles for form controls where labels are above the input elements       */
+/* ------------------------------------------------------------------------------ */
+
+      .uniForm label,
+      .uniForm .label{ display: block; float: none; margin: 0 0 .5em 0; padding: 0; line-height: 100%; width: auto; }
+      
+      /* Float the input elements */
+      .uniForm .textInput,
+      .uniForm .fileUpload,
+      .uniForm .selectInput,
+      .uniForm select,
+      .uniForm textarea{ float: left; width: 53%; margin: 0; }
+      
+      /* Postition the hints */
+      .uniForm .formHint{ float: right; width: 43%; margin: 0; clear: none; }
+      
+      /* Position the elements inside combo boxes (multiple inputs/selects/checkboxes/radio buttons per unit) */
+      .uniForm ul{ float: left; width: 53%; margin: 0; padding: 0; }
+        .uniForm ul li{ margin: 0 0 .5em 0; list-style: none; }
+          .uniForm ul li label{ margin: 0; float: none; display: block; overflow: visible; }
+        /* Alternate layout */
+        .uniForm ul.alternate li{ float: left; width: 30%; margin-right: 3%; }
+          .uniForm ul.alternate li label{ float: none; display: block; width: 98%; }
+            .uniForm ul .textInput,
+            .uniForm ul .selectInput,
+            .uniForm ul select,
+            .uniForm ul.alternate .textInput,
+            .uniForm ul.alternate .selectInput,
+            .uniForm ul.alternate select{ width: 98%; margin-top: .5em; display: block; float: none; }
+            
+        /* Required fields asterisk styling */
+        .uniForm label em,
+        .uniForm .label em{ float: left; width: 1em; margin: 0 0 0 -1em; }
+
+/* ------------------------------------------------------------------------------ */
+/* #########################   ALTERNATE LAYOUT   ############################### */
+/* ------------------------------------------------------------------------------ */
+/*    Styles for form controls where labels are in line with the input elements   */
+/*    Set the class of the parent (preferably to a fieldset) to .inlineLabels     */
+/* ------------------------------------------------------------------------------ */
+
+      .uniForm .inlineLabels label,
+      .uniForm .inlineLabels .label{ float: left; margin: .3em 2% 0 0; padding: 0; line-height: 1; position: relative; width: 32%; }
+      
+      /* Float the input elements */
+      .uniForm .inlineLabels .textInput,
+      .uniForm .inlineLabels .fileUpload,
+      .uniForm .inlineLabels .selectInput,
+      .uniForm .inlineLabels select,
+      .uniForm .inlineLabels textarea{ float: left; width: 64%; }
+
+    /* Postition the hints */
+    .uniForm .inlineLabels .formHint{ clear: both; float: none; width: auto; margin-left: 34%; position: static; }
+    
+    /* Position the elements inside combo boxes (multiple inputs/selects/checkboxes/radio buttons per unit) */
+    .uniForm .inlineLabels ul{ float: left; width: 66%; }
+      .uniForm .inlineLabels ul li{ margin: .5em 0; }
+        .uniForm .inlineLabels ul li label{ float: none; display: block; width: 100%; }
+      /* Alternate layout */
+      .uniForm .inlineLabels ul.alternate li{ margin-right: 3%; margin-top: .25em; }
+          .uniForm .inlineLabels ul li label .textInput,
+          .uniForm .inlineLabels ul li label textarea,
+          .uniForm .inlineLabels ul li label select{ float: none; display: block; width: 98%;  }
+    
+    /* Required fields asterisk styling */
+    .uniForm .inlineLabels label em,
+    .uniForm .inlineLabels .label em{ display: block; float: none; margin: 0; position: absolute; right: 0; }
+
+/* ----------------------------------------------------------------------------- */
+/* ########################### Additional Stuff ################################ */
+/* ----------------------------------------------------------------------------- */
+
+  /* Generals */
+    .uniForm legend{ color: inherit; }
+    
+      .uniForm .secondaryAction{ float: left; }
+      
+      /* .inlineLabel is used for inputs within labels - checkboxes and radio buttons */
+      .uniForm .inlineLabel input,
+      .uniForm .inlineLabels .inlineLabel input,
+      .uniForm .blockLabels .inlineLabel input,
+      /* class .inlineLabel is depreciated */
+      .uniForm label input{ float: none; display: inline; margin: 0; padding: 0; border: none; }
+            
+      .uniForm .buttonHolder .inlineLabel,
+      .uniForm .buttonHolder label{ float: left; margin: .5em 0 0 0; width: auto; max-width: 60%; text-align: left; }
+      
+      /* When you don't want to use a label */
+      .uniForm .inlineLabels .noLabel ul{ margin-left: 34%; /* Match to width of label + gap to field */ }
+      
+      /* Classes for control of the widths of the fields */
+      .uniForm .small { width: 30% !important; }
+      .uniForm .medium{ width: 45% !important; }
+      .uniForm .large {  } /* Large is default and should match the value you set for .textInput, textarea or select */
+      .uniForm .auto  { width: auto !important; }
+      .uniForm .small,
+      .uniForm .medium,
+      .uniForm .auto{ margin-right: 4px; }
+
+/* Columns */
+.uniForm .col{ float: left; }
+.uniForm .col{ width: 50%; }
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/static-files/uni-form.jquery.js	Fri Jun 25 16:00:15 2010 -0700
@@ -0,0 +1,27 @@
+// Author: Ilija Studen for the purposes of Uni–Form
+// Modified by Aris Karageorgos to use the parents function
+
+jQuery.fn.uniform = function(settings) {
+  settings = jQuery.extend({
+    valid_class    : 'valid',
+    invalid_class  : 'invalid',
+    focused_class  : 'focused',
+    holder_class   : 'ctrlHolder',
+    field_selector : 'input, select, textarea'
+  }, settings);
+  
+  return this.each(function() {
+    var form = jQuery(this);
+    // Select form fields and attach them higlighter functionality
+    form.find(settings.field_selector).focus(function() {
+      form.find('.' + settings.focused_class).removeClass(settings.focused_class);
+      $(this).parents().filter('.'+settings.holder_class+':first').addClass(settings.focused_class);
+    }).blur(function() {
+      form.find('.' + settings.focused_class).removeClass(settings.focused_class);
+    });
+  });
+};
+// Auto set on page load...
+$(document).ready(function() {
+  jQuery('form.uniForm').uniform();
+});
\ No newline at end of file
--- a/summitidp/app.py	Fri Jun 25 20:33:01 2010 +0000
+++ b/summitidp/app.py	Fri Jun 25 16:00:15 2010 -0700
@@ -162,12 +162,14 @@
                         )
                 email = chaltok['email']
                 self.challenge_tokens.revoke(body['token'])
+                user_id = self.emails.index(email)
                 token = self.auth_tokens.create(
                     email=email,
-                    user_id=self.emails.index(email)
+                    user_id=user_id
                     )
                 return req.json_response('200 OK',
                                          {'token': token,
+                                          'user_id': user_id,
                                           'email': email})
             return req.json_response('400 Bad Request',
                                      {'error': 'invalid body'})
--- a/tests/test_app.py	Fri Jun 25 20:33:01 2010 +0000
+++ b/tests/test_app.py	Fri Jun 25 16:00:15 2010 -0700
@@ -95,6 +95,7 @@
     resp = post_json(server.respond_to_challenge_path,
                      {'token': token})
     assert resp.json == {'email': 'bob@foo.com',
+                         'user_id': 0,
                          'token': 'my auth token'}
     assert server.auth_tokens.get('my auth token') == {
         'email': 'bob@foo.com',
@@ -116,6 +117,7 @@
     resp = post_json(server.respond_to_challenge_path,
                      {'token': token})
     assert resp.json == {'email': 'bob@foo.com',
+                         'user_id': 0,
                          'token': 'my auth token'}
     post_json(server.respond_to_challenge_path,
               {'token': token},