couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From benk...@apache.org
Subject fauxton commit: updated refs/heads/master to 410d2c7
Date Wed, 22 Apr 2015 16:05:59 GMT
Repository: couchdb-fauxton
Updated Branches:
  refs/heads/master 422cb92c6 -> 410d2c772


Updating Auth module to use React


Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/410d2c77
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/410d2c77
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/410d2c77

Branch: refs/heads/master
Commit: 410d2c7729533a9bb0873a14ab72bc7a590b738e
Parents: 422cb92
Author: Ben Keen <ben.keen@gmail.com>
Authored: Thu Apr 16 16:14:27 2015 -0700
Committer: Ben Keen <ben.keen@gmail.com>
Committed: Wed Apr 22 09:04:41 2015 -0700

----------------------------------------------------------------------
 app/addons/auth/actions.js                      | 117 ++++++++
 app/addons/auth/actiontypes.js                  |  23 ++
 app/addons/auth/components.react.jsx            | 287 +++++++++++++++++++
 app/addons/auth/resources.js                    | 172 -----------
 app/addons/auth/routes.js                       |  46 +--
 app/addons/auth/stores.js                       | 171 +++++++++++
 app/addons/auth/templates/change_password.html  |  29 --
 app/addons/auth/templates/create_admin.html     |  37 ---
 app/addons/auth/templates/login.html            |  26 --
 app/addons/auth/templates/nav_dropdown.html     |  24 --
 .../auth/test/auth.componentsSpec.react.jsx     | 128 +++++++++
 app/addons/auth/test/auth.storesSpec.js         | 148 ++++++++++
 app/core/routeObject.js                         |  11 +-
 app/core/tests/routeObjectSpec.js               |  10 +-
 14 files changed, 914 insertions(+), 315 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/auth/actions.js b/app/addons/auth/actions.js
new file mode 100644
index 0000000..ab3f50e
--- /dev/null
+++ b/app/addons/auth/actions.js
@@ -0,0 +1,117 @@
+// Licensed 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.
+define([
+  'api',
+  'addons/auth/actiontypes'
+],
+function (FauxtonAPI, ActionTypes) {
+
+
+  var errorHandler = function (xhr, type, msg) {
+    msg = xhr;
+    if (arguments.length === 3) {
+      msg = xhr.responseJSON.reason;
+    }
+
+    FauxtonAPI.addNotification({
+      msg: msg,
+      type: 'error'
+    });
+  };
+
+
+  return {
+
+    login: function (username, password, urlBack) {
+      var promise = FauxtonAPI.session.login(username, password);
+
+      promise.then(function () {
+        FauxtonAPI.addNotification({ msg: FauxtonAPI.session.messages.loggedIn });
+        if (urlBack) {
+          return FauxtonAPI.navigate(urlBack);
+        }
+        FauxtonAPI.navigate('/');
+      });
+      promise.fail(errorHandler);
+    },
+
+    changePassword: function (password, passwordConfirm) {
+      var promise = FauxtonAPI.session.changePassword(password, passwordConfirm);
+
+      promise.done(function () {
+        FauxtonAPI.addNotification({ msg: FauxtonAPI.session.messages.changePassword });
+        FauxtonAPI.dispatch({ type: ActionTypes.AUTH_CLEAR_CHANGE_PWD_FIELDS });
+      });
+
+      promise.fail(errorHandler);
+    },
+
+    updateChangePasswordField: function (value) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.AUTH_UPDATE_CHANGE_PWD_FIELD,
+        value: value
+      });
+    },
+
+    updateChangePasswordConfirmField: function (value) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD,
+        value: value
+      });
+    },
+
+    createAdmin: function (username, password, loginAfter) {
+      var promise = FauxtonAPI.session.createAdmin(username, password, loginAfter);
+
+      promise.then(function () {
+        FauxtonAPI.addNotification({ msg: FauxtonAPI.session.messages.adminCreated });
+        if (loginAfter) {
+          FauxtonAPI.navigate('/');
+        } else {
+          FauxtonAPI.dispatch({ type: ActionTypes.AUTH_CLEAR_CREATE_ADMIN_FIELDS });
+        }
+      });
+
+      promise.fail(function (xhr, type, msg) {
+        msg = xhr;
+        if (arguments.length === 3) {
+          console.log("here...", xhr.responseJSON);
+          msg = xhr.responseJSON.reason;
+        }
+        errorHandler(FauxtonAPI.session.messages.adminCreationFailedPrefix + ' ' + msg);
+      });
+    },
+
+    updateCreateAdminUsername: function (value) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD,
+        value: value
+      });
+    },
+
+    updateCreateAdminPassword: function (value) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD,
+        value: value
+      });
+    },
+
+    selectPage: function (page) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.AUTH_SELECT_PAGE,
+        page: page
+      });
+    }
+
+  };
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/auth/actiontypes.js b/app/addons/auth/actiontypes.js
new file mode 100644
index 0000000..29ce9bf
--- /dev/null
+++ b/app/addons/auth/actiontypes.js
@@ -0,0 +1,23 @@
+// Licensed 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.
+
+define([], function () {
+  return {
+    AUTH_CLEAR_CHANGE_PWD_FIELDS: 'AUTH_CLEAR_CHANGE_PWD_FIELDS',
+    AUTH_UPDATE_CHANGE_PWD_FIELD: 'AUTH_UPDATE_CHANGE_PWD_FIELD',
+    AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD: 'AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD',
+    AUTH_CLEAR_CREATE_ADMIN_FIELDS: 'AUTH_CLEAR_CREATE_ADMIN_FIELDS',
+    AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD: 'AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD',
+    AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD: 'AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD',
+    AUTH_SELECT_PAGE: 'AUTH_SELECT_PAGE'
+  };
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/auth/components.react.jsx b/app/addons/auth/components.react.jsx
new file mode 100644
index 0000000..5fd5c4c
--- /dev/null
+++ b/app/addons/auth/components.react.jsx
@@ -0,0 +1,287 @@
+// Licensed 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.
+
+define([
+  'app',
+  'api',
+  'react',
+  'addons/auth/stores',
+  'addons/auth/actions'
+], function (app, FauxtonAPI, React, AuthStores, AuthActions) {
+
+  var changePasswordStore = AuthStores.changePasswordStore;
+  var createAdminStore = AuthStores.createAdminStore;
+  var createAdminSidebarStore = AuthStores.createAdminSidebarStore;
+
+
+  var LoginForm = React.createClass({
+    propTypes: {
+      urlBack: React.PropTypes.string.isRequired
+    },
+
+    getInitialState: function () {
+      return {
+        username: '',
+        password: ''
+      };
+    },
+
+    getDefaultProps: function () {
+      return {
+        urlBack: ''
+      };
+    },
+
+    onInputChange: function (e) {
+      var change = (e.target.name === 'name') ? { username: e.target.value } : { password: e.target.value };
+      this.setState(change);
+    },
+
+    login: function (e) {
+      e.preventDefault();
+      AuthActions.login(this.state.username, this.state.password, this.props.urlBack);
+    },
+
+    componentDidMount: function () {
+      this.refs.username.getDOMNode().focus();
+    },
+
+    render: function () {
+      return (
+        <div className="row-fluid">
+          <div className="span12">
+            <form id="login" onSubmit={this.login}>
+              <p className="help-block">
+                Login with your username and password
+              </p>
+              <input id="username" type="text" name="name" ref="username" placeholder="Username" size="24"
+                onChange={this.onInputChange} value={this.state.username} />
+              <br/>
+              <input id="password" type="password" name="password" placeholder="Password" size="24"
+                onChange={this.onInputChange} value={this.state.password} />
+              <br/>
+              <button id="submit" className="btn" type="submit">Login</button>
+            </form>
+          </div>
+        </div>
+      );
+    }
+  });
+
+
+  var ChangePasswordForm = React.createClass({
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    getStoreState: function () {
+      return {
+        password: changePasswordStore.getChangePassword(),
+        passwordConfirm: changePasswordStore.getChangePasswordConfirm()
+      };
+    },
+
+    onChange: function () {
+      this.setState(this.getStoreState());
+    },
+
+    onChangePassword: function (e) {
+      AuthActions.updateChangePasswordField(e.target.value);
+    },
+
+    onChangePasswordConfirm: function (e) {
+      AuthActions.updateChangePasswordConfirmField(e.target.value);
+    },
+
+    componentDidMount: function () {
+      this.refs.password.getDOMNode().focus();
+      changePasswordStore.on('change', this.onChange, this);
+    },
+
+    componentWillUnmount: function () {
+      changePasswordStore.off('change', this.onChange);
+    },
+
+    changePassword: function (e) {
+      e.preventDefault();
+      AuthActions.changePassword(this.state.password, this.state.passwordConfirm);
+    },
+
+    render: function () {
+      return (
+        <div className="auth-page">
+          <h3>Change Password</h3>
+
+          <form id="change-password" onSubmit={this.changePassword}>
+            <p>
+              Enter your new password.
+            </p>
+
+            <input id="password" type="password" ref="password" name="password" placeholder="Password"
+              size="24" onChange={this.onChangePassword} value={this.state.password} />
+            <br />
+            <input id="password-confirm" type="password" name="password_confirm" placeholder= "Verify Password"
+              size="24" onChange={this.onChangePasswordConfirm} value={this.state.passwordConfirm} />
+
+            <br />
+            <p>
+              <button type="submit" className="btn btn-primary">Change</button>
+            </p>
+          </form>
+        </div>
+      );
+    }
+  });
+
+
+  var CreateAdminForm = React.createClass({
+    propTypes: {
+      loginAfter: React.PropTypes.bool.isRequired
+    },
+
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    getStoreState: function () {
+      return {
+        username: createAdminStore.getUsername(),
+        password: createAdminStore.getPassword()
+      };
+    },
+
+    onChange: function () {
+      this.setState(this.getStoreState());
+    },
+
+    getDefaultProps: function () {
+      return {
+        loginAfter: ''
+      };
+    },
+
+    onChangeUsername: function (e) {
+      AuthActions.updateCreateAdminUsername(e.target.value);
+    },
+
+    onChangePassword: function (e) {
+      AuthActions.updateCreateAdminPassword(e.target.value);
+    },
+
+    componentDidMount: function () {
+      this.refs.username.getDOMNode().focus();
+      createAdminStore.on('change', this.onChange, this);
+    },
+
+    componentWillUnmount: function () {
+      createAdminStore.off('change', this.onChange);
+    },
+
+    createAdmin: function (e) {
+      e.preventDefault();
+      AuthActions.createAdmin(this.state.username, this.state.password, this.props.loginAfter);
+    },
+
+    render: function () {
+      return (
+        <div className="auth-page">
+          <h3>Create Admins</h3>
+
+          <p>
+            Before a server admin is configured, all clients have admin privileges. This is fine when
+            HTTP access is restricted to trusted users. <strong>If end-users will be accessing this
+            CouchDB, you must create an admin account to prevent accidental (or malicious) data
+            loss.</strong>
+          </p>
+          <p>
+            Server admins can create and destroy databases, install and update _design documents, run
+            the test suite, and edit all aspects of CouchDB configuration.
+          </p>
+
+          <form id="create-admin-form" onSubmit={this.createAdmin}>
+            <input id="username" type="text" ref="username" name="name" placeholder="Username" size="24"
+              onChange={this.onChangeUsername} />
+            <br/>
+            <input id="password" type="password" name="password" placeholder= "Password" size="24"
+              onChange={this.onChangePassword} />
+            <p>
+              Non-admin users have read and write access to all databases, which
+              are controlled by validation functions. CouchDB can be configured to block all
+              access to anonymous users.
+            </p>
+            <button type="submit" id="create-admin" className="btn btn-primary">Create Admin</button>
+          </form>
+        </div>
+      );
+    }
+  });
+
+
+  var CreateAdminSidebar = React.createClass({
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    getStoreState: function () {
+      return {
+        selectedPage: createAdminSidebarStore.getSelectedPage()
+      };
+    },
+
+    onChange: function () {
+      this.setState(this.getStoreState());
+    },
+
+    componentDidMount: function () {
+      createAdminSidebarStore.on('change', this.onChange, this);
+    },
+
+    componentWillUnmount: function () {
+      createAdminSidebarStore.off('change', this.onChange);
+    },
+
+    selectPage: function (e) {
+      var newPage = e.target.href.split('#')[1];
+      AuthActions.selectPage(newPage);
+    },
+
+    render: function () {
+      var user = FauxtonAPI.session.user();
+      var userName = _.isNull(user) ? '' : FauxtonAPI.session.user().name;
+
+      return (
+        <div className="sidenav">
+          <header className="row-fluid">
+            <h3>{userName}</h3>
+          </header>
+          <ul className="nav nav-list" onClick={this.selectPage}>
+            <li className={this.state.selectedPage === 'changePassword' ? 'active' : ''} data-page="changePassword">
+              <a href="#changePassword">Change Password</a>
+            </li>
+            <li className={this.state.selectedPage === 'addAdmin' ? 'active' : ''} data-page="addAdmin">
+              <a href="#addAdmin">Create Admins</a>
+            </li>
+          </ul>
+        </div>
+      );
+    }
+  });
+
+  return {
+    LoginForm: LoginForm,
+    ChangePasswordForm: ChangePasswordForm,
+    CreateAdminForm: CreateAdminForm,
+    CreateAdminSidebar: CreateAdminSidebar
+  };
+
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/resources.js
----------------------------------------------------------------------
diff --git a/app/addons/auth/resources.js b/app/addons/auth/resources.js
index 3b6d295..c3deed7 100644
--- a/app/addons/auth/resources.js
+++ b/app/addons/auth/resources.js
@@ -20,17 +20,6 @@ function (app, FauxtonAPI, CouchdbSession) {
 
   var Auth = new FauxtonAPI.addon();
 
-  var errorHandler = function (xhr, type, msg) {
-    msg = xhr;
-    if (arguments.length === 3) {
-      msg = xhr.responseJSON.reason;
-    }
-
-    FauxtonAPI.addNotification({
-      msg: msg,
-      type: 'error'
-    });
-  };
 
   var Admin = Backbone.Model.extend({
     url: function () {
@@ -214,167 +203,6 @@ function (app, FauxtonAPI, CouchdbSession) {
     }
   });
 
-  Auth.CreateAdminView = FauxtonAPI.View.extend({
-    template: 'addons/auth/templates/create_admin',
-    className: "auth-page",
-
-    initialize: function (options) {
-      options = options || {};
-      this.login_after = options.login_after === false ? false : true;
-    },
-
-    events: {
-      "submit #create-admin-form": "createAdmin"
-    },
-
-    createAdmin: function (event) {
-      event.preventDefault();
-
-      var that = this,
-          username = this.$('#username').val(),
-          password = this.$('#password').val();
-
-      var promise = this.model.createAdmin(username, password, this.login_after);
-
-      promise.then(function () {
-        FauxtonAPI.addNotification({
-          msg: FauxtonAPI.session.messages.adminCreated,
-        });
-
-        if (that.login_after) {
-          FauxtonAPI.navigate('/');
-        } else {
-          that.$('#username').val('');
-          that.$('#password').val('');
-        }
-      });
-
-      promise.fail(function (xhr, type, msg) {
-        msg = xhr;
-        if (arguments.length === 3) {
-          msg = xhr.responseJSON.reason;
-        }
-        msg = FauxtonAPI.session.messages.adminCreationFailedPrefix + ' ' + msg;
-        errorHandler(msg);
-      });
-    },
-
-    afterRender: function () {
-      $("#username").focus();
-    }
-  });
-
-  Auth.LoginView = FauxtonAPI.View.extend({
-    template: 'addons/auth/templates/login',
-    className: "row-fluid",
-    initialize: function (options) {
-      this.urlBack = options.urlBack || "";
-    },
-
-    events: {
-      "submit #login": "login"
-    },
-
-    login: function (event) {
-      event.preventDefault();
-
-      var username = this.$('#username').val(),
-          password = this.$('#password').val(),
-          urlBack = this.urlBack,
-          promise = this.model.login(username, password);
-
-      promise.then(function () {
-        FauxtonAPI.addNotification({msg:  FauxtonAPI.session.messages.loggedIn });
-
-        if (urlBack) {
-          return FauxtonAPI.navigate(urlBack);
-        }
-
-        FauxtonAPI.navigate('/');
-      });
-
-      promise.fail(errorHandler);
-    },
-
-    afterRender: function () {
-      $("#username").focus();
-    }
-  });
-
-  Auth.ChangePassword = FauxtonAPI.View.extend({
-    template: 'addons/auth/templates/change_password',
-    className: "auth-page",
-
-    events: {
-      "submit #change-password": "changePassword"
-    },
-
-    changePassword: function (event) {
-      event.preventDefault();
-
-      var that = this,
-          new_password = this.$('#password').val(),
-          password_confirm = this.$('#password-confirm').val();
-
-      var promise = this.model.changePassword(new_password, password_confirm);
-
-      promise.done(function () {
-        FauxtonAPI.addNotification({msg: FauxtonAPI.session.messages.changePassword});
-        that.$('#password').val('');
-        that.$('#password-confirm').val('');
-      });
-
-      promise.fail(errorHandler);
-    },
-
-    afterRender: function () {
-      $("#password").focus();
-    }
-  });
-
-  Auth.NavDropDown = FauxtonAPI.View.extend({
-    template: 'addons/auth/templates/nav_dropdown',
-
-    beforeRender: function () {
-      this.listenTo(this.model, 'change', this.render);
-    },
-
-    setTab: function (selectedTab) {
-      this.selectedTab = selectedTab;
-      this.$('.active').removeClass('active');
-      var $tab = this.$('a[data-select="' + selectedTab + '"]');
-      $tab.parent().addClass('active');
-    },
-
-    afterRender: function () {
-      if (this.selectedTab) {
-        this.setTab(this.selectedTab);
-      }
-    },
-
-    serialize: function () {
-      return {
-        admin_party: this.model.isAdminParty(),
-        user: this.model.user()
-      };
-    }
-  });
-
-  Auth.NoAccessView = FauxtonAPI.View.extend({
-    template: "addons/auth/templates/noAccess",
-    className: "row-fluid",
-
-    initialize: function (options) {
-      this.urlBack = options.urlBack || "";
-    },
-
-    serialize: function () {
-      return {
-        urlBack: this.urlBack,
-        user: FauxtonAPI.session.user()
-      };
-    }
-  });
 
   return Auth;
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/routes.js
----------------------------------------------------------------------
diff --git a/app/addons/auth/routes.js b/app/addons/auth/routes.js
index b6ea57d..2632773 100644
--- a/app/addons/auth/routes.js
+++ b/app/addons/auth/routes.js
@@ -13,11 +13,14 @@
 define([
        "app",
        "api",
-       "addons/auth/resources"
+  'addons/auth/resources',
+  'addons/auth/actions',
+  'addons/auth/components.react'
 ],
 
-function (app, FauxtonAPI, Auth) {
-  var authRouteObject = FauxtonAPI.RouteObject.extend({
+function (app, FauxtonAPI, Auth, AuthActions, Components) {
+
+  var AuthRouteObject = FauxtonAPI.RouteObject.extend({
     layout: 'one_pane',
 
     routes: {
@@ -28,33 +31,31 @@ function (app, FauxtonAPI, Auth) {
     },
 
     login: function () {
-      var urlBack = app.getParams().urlback;
-      this.crumbs = [{name: 'Login', link:"#"}];
-      this.setView('#dashboard-content', new Auth.LoginView({
-        model: FauxtonAPI.session,
-        urlBack: urlBack
-      }));
+      this.crumbs = [{ name: 'Login', link: "#" }];
+
+      this.setComponent('#dashboard-content', Components.LoginForm, { urlBack: app.getParams().urlback });
     },
 
     logout: function () {
-      FauxtonAPI.addNotification({msg: 'You have been logged out.'});
+      FauxtonAPI.addNotification({ msg: 'You have been logged out.' });
       FauxtonAPI.session.logout().then(function () {
         FauxtonAPI.navigate('/');
       });
     },
 
     changePassword: function () {
-      this.crumbs = [{name: 'Change Password', link:"#"}];
-      this.setView('#dashboard-content', new Auth.ChangePassword({model: FauxtonAPI.session}));
+      this.crumbs = [{name: 'Change Password', link: "#" }];
+      this.setComponent('#dashboard-content', Components.ChangePasswordForm);
     },
 
     createAdmin: function () {
       this.crumbs = [{name: 'Create Admin', link:"#"}];
-      this.setView('#dashboard-content', new Auth.CreateAdminView({model: FauxtonAPI.session}));
+      this.setComponent('#dashboard-content', Components.CreateAdminForm, { loginAfter: true });
     }
   });
 
-  var userRouteObject = FauxtonAPI.RouteObject.extend({
+
+  var UserRouteObject = FauxtonAPI.RouteObject.extend({
     layout: 'with_sidebar',
 
     routes: {
@@ -63,32 +64,33 @@ function (app, FauxtonAPI, Auth) {
         roles: ['fx_loggedIn']
       },
       'addAdmin': {
-        roles: ['_admin'],
-        route: 'addAdmin'
+        route: 'addAdmin',
+        roles: ['_admin']
       }
     },
+
     selectedHeader: function () {
       return FauxtonAPI.session.user().name;
     },
 
     initialize: function () {
-      this.navDrop = this.setView('#sidebar-content', new Auth.NavDropDown({model: FauxtonAPI.session}));
+      this.setComponent('#sidebar-content', Components.CreateAdminSidebar);
     },
 
     changePassword: function () {
-      this.navDrop.setTab('change-password');
-      this.setView('#dashboard-content', new Auth.ChangePassword({model: FauxtonAPI.session}));
+      AuthActions.selectPage('changePassword');
+      this.setComponent('#dashboard-content', Components.ChangePasswordForm);
     },
 
     addAdmin: function () {
-      this.navDrop.setTab('add-admin');
-      this.setView('#dashboard-content', new Auth.CreateAdminView({login_after: false, model: FauxtonAPI.session}));
+      AuthActions.selectPage('addAdmin');
+      this.setComponent('#dashboard-content', Components.CreateAdminForm, { loginAfter: false });
     },
 
     crumbs: [{name: 'User Management', link: '#'}]
   });
 
-  Auth.RouteObjects = [authRouteObject, userRouteObject];
+  Auth.RouteObjects = [AuthRouteObject, UserRouteObject];
 
   return Auth;
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/auth/stores.js b/app/addons/auth/stores.js
new file mode 100644
index 0000000..5327574
--- /dev/null
+++ b/app/addons/auth/stores.js
@@ -0,0 +1,171 @@
+// Licensed 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.
+
+define([
+  'app',
+  'api',
+  'addons/auth/actiontypes'
+], function (app, FauxtonAPI, ActionTypes) {
+
+
+  // Not thrilled with this. The sole purpose of these next two stores is because the Create Admin + Change Password
+  // forms need to clear after a successful post. Since those events occur in actions.js, we need a way to tell the
+  // component to update + clear the fields. That's why all this code exists.
+
+  var ChangePasswordStore = FauxtonAPI.Store.extend({
+    initialize: function () {
+      this.reset();
+    },
+
+    reset: function () {
+      this._changePassword = '';
+      this._changePasswordConfirm = '';
+    },
+
+    getChangePassword: function () {
+      return this._changePassword;
+    },
+
+    getChangePasswordConfirm: function () {
+      return this._changePasswordConfirm;
+    },
+
+    setChangePassword: function (val) {
+      this._changePassword = val;
+    },
+
+    setChangePasswordConfirm: function (val) {
+      this._changePasswordConfirm = val;
+    },
+
+    dispatch: function (action) {
+      switch (action.type) {
+
+        case ActionTypes.AUTH_CLEAR_CHANGE_PWD_FIELDS:
+          this.reset();
+          this.triggerChange();
+        break;
+
+        case ActionTypes.AUTH_UPDATE_CHANGE_PWD_FIELD:
+          this.setChangePassword(action.value);
+          this.triggerChange();
+        break;
+
+        case ActionTypes.AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD:
+          this.setChangePasswordConfirm(action.value);
+          this.triggerChange();
+        break;
+
+        default:
+        return;
+      }
+    }
+  });
+
+  var changePasswordStore = new ChangePasswordStore();
+  changePasswordStore.dispatchToken = FauxtonAPI.dispatcher.register(changePasswordStore.dispatch.bind(changePasswordStore));
+
+
+  var CreateAdminStore = FauxtonAPI.Store.extend({
+    initialize: function () {
+      this.reset();
+    },
+
+    reset: function () {
+      this._username = '';
+      this._password = '';
+    },
+
+    getUsername: function () {
+      return this._username;
+    },
+
+    getPassword: function () {
+      return this._password;
+    },
+
+    setUsername: function (val) {
+      this._username = val;
+    },
+
+    setPassword: function (val) {
+      this._password = val;
+    },
+
+    dispatch: function (action) {
+      switch (action.type) {
+        case ActionTypes.AUTH_CLEAR_CREATE_ADMIN_FIELDS:
+          this.reset();
+          this.triggerChange();
+        break;
+
+        case ActionTypes.AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD:
+          this.setUsername(action.value);
+          this.triggerChange();
+        break;
+
+        case ActionTypes.AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD:
+          this.setPassword(action.value);
+          this.triggerChange();
+        break;
+
+        default:
+        return;
+      }
+    }
+  });
+
+  var createAdminStore = new CreateAdminStore();
+  createAdminStore.dispatchToken = FauxtonAPI.dispatcher.register(createAdminStore.dispatch.bind(createAdminStore));
+
+
+  var CreateAdminSidebarStore = FauxtonAPI.Store.extend({
+    initialize: function () {
+      this.reset();
+    },
+
+    reset: function () {
+      this._selectedPage = 'changePassword';
+    },
+
+    getSelectedPage: function () {
+      return this._selectedPage;
+    },
+
+    setSelectedPage: function (val) {
+      this._selectedPage = val;
+    },
+
+    dispatch: function (action) {
+      switch (action.type) {
+        case ActionTypes.AUTH_SELECT_PAGE:
+          this.setSelectedPage(action.page);
+          this.triggerChange();
+        break;
+
+        default:
+        return;
+      }
+    }
+  });
+
+  var createAdminSidebarStore = new CreateAdminSidebarStore();
+  createAdminSidebarStore.dispatchToken = FauxtonAPI.dispatcher.register(createAdminSidebarStore.dispatch.bind(createAdminSidebarStore));
+
+
+  return {
+    changePasswordStore: changePasswordStore,
+    createAdminStore: createAdminStore,
+    createAdminSidebarStore: createAdminSidebarStore
+  };
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/templates/change_password.html
----------------------------------------------------------------------
diff --git a/app/addons/auth/templates/change_password.html b/app/addons/auth/templates/change_password.html
deleted file mode 100644
index 8879866..0000000
--- a/app/addons/auth/templates/change_password.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<%/*
-Licensed 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.
-*/%>
-
-<h3>Change Password</h3>
-
-<form id="change-password">
-  <p>
-    Enter your new password.
-  </p>
-  <input id="password" type="password" name="password" placeholder="Password" size="24">
-  <br/>
-  <input id="password-confirm" type="password" name="password_confirm" placeholder= "Verify Password" size="24">
-  <br/>
-
-  <p>
-    <button type="submit" class="btn btn-primary">Change</button>
-  </p>
-</form>

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/templates/create_admin.html
----------------------------------------------------------------------
diff --git a/app/addons/auth/templates/create_admin.html b/app/addons/auth/templates/create_admin.html
deleted file mode 100644
index 9da8fa9..0000000
--- a/app/addons/auth/templates/create_admin.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<%/*
-Licensed 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.
-*/%>
-
-<h3>Create Admins</h3>
-
-<p>
-  Before a server admin is configured, all clients have admin privileges. This is fine when
-  HTTP access is restricted to trusted users. <strong>If end-users will be accessing this
-  CouchDB, you must create an admin account to prevent accidental (or malicious) data
-  loss.</strong>
-</p>
-<p>
-  Server admins can create and destroy databases, install and update _design documents, run
-  the test suite, and edit all aspects of CouchDB configuration.
-</p>
-
-<form id="create-admin-form">
-  <input id="username" type="text" name="name" placeholder="Username" size="24">
-  <br/>
-  <input id="password" type="password" name="password" placeholder= "Password" size="24">
-  <p>Non-admin users have read and write access to all databases, which
-  are controlled by validation functions. CouchDB can be configured to block all
-  access to anonymous users.
-  </p>
-  <button type="submit" href="#" id="create-admin" class="btn btn-primary">Create Admin</button>
-</form>

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/templates/login.html
----------------------------------------------------------------------
diff --git a/app/addons/auth/templates/login.html b/app/addons/auth/templates/login.html
deleted file mode 100644
index d5bc6d7..0000000
--- a/app/addons/auth/templates/login.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
-Licensed 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.
--->
-<div class="span12">
-  <form id="login">
-    <p class="help-block">
-      Login with your username and password
-    </p>
-    <input id="username" type="text" name="name" placeholder="Username" size="24" />
-    <br/>
-    <input id="password" type="password" name="password" placeholder="Password" size="24" />
-    <br/>
-    <button id="submit" class="btn" type="submit">Login</button>
-  </form>
-</div>
-

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/templates/nav_dropdown.html
----------------------------------------------------------------------
diff --git a/app/addons/auth/templates/nav_dropdown.html b/app/addons/auth/templates/nav_dropdown.html
deleted file mode 100644
index 44acf10..0000000
--- a/app/addons/auth/templates/nav_dropdown.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<%/*
-Licensed 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.
-*/%>
-
-<div class="sidenav">
-<header class="row-fluid">
-  <h3><%- user.name %></h3>
-</header>
-
-<ul class="nav nav-list">
-  <li class="active" ><a data-select="change-password" id="user-change-password" href="#changePassword"> Change Password </a></li>
-  <li ><a data-select="add-admin" href="#addAdmin"> Create Admins </a></li>
-</ul>
-</div>

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/test/auth.componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/auth/test/auth.componentsSpec.react.jsx b/app/addons/auth/test/auth.componentsSpec.react.jsx
new file mode 100644
index 0000000..cdafb83
--- /dev/null
+++ b/app/addons/auth/test/auth.componentsSpec.react.jsx
@@ -0,0 +1,128 @@
+// Licensed 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.
+define([
+  'api',
+  'react',
+  'testUtils',
+  'addons/auth/components.react',
+  'addons/auth/stores',
+  'addons/auth/actions'
+], function (FauxtonAPI, React, testUtils, Components, Stores, Actions) {
+  var assert = testUtils.assert;
+
+  var TestUtils = React.addons.TestUtils;
+  var createAdminSidebarStore = Stores.createAdminSidebarStore;
+
+
+  describe('Auth -- Components', function () {
+
+    describe('LoginForm', function () {
+      var container, loginForm;
+
+      beforeEach(function () {
+        container = document.createElement('div');
+        loginForm = TestUtils.renderIntoDocument(<Components.LoginForm />, container);
+      });
+
+      afterEach(function () {
+        React.unmountComponentAtNode(container);
+      });
+
+      it('should trigger login event when form submitted', function () {
+        var spy = sinon.spy(Actions, 'login');
+        TestUtils.Simulate.submit($(loginForm.getDOMNode()).find('#login')[0]);
+        assert.ok(spy.calledOnce);
+      });
+    });
+
+    describe('ChangePasswordForm', function () {
+      var container, changePasswordForm;
+
+      beforeEach(function () {
+        container = document.createElement('div');
+        changePasswordForm = TestUtils.renderIntoDocument(<Components.ChangePasswordForm />, container);
+      });
+
+      afterEach(function () {
+        React.unmountComponentAtNode(container);
+      });
+
+      it('should call action to update password on field change', function () {
+        var spy = sinon.spy(Actions, 'updateChangePasswordField');
+        TestUtils.Simulate.change($(changePasswordForm.getDOMNode()).find('#password')[0], { target: { value: 'bobsyouruncle' }});
+        assert.ok(spy.calledOnce);
+      });
+
+      it('should call action to update password confirm on field change', function () {
+        var spy = sinon.spy(Actions, 'updateChangePasswordConfirmField');
+        TestUtils.Simulate.change($(changePasswordForm.getDOMNode()).find('#password-confirm')[0], { target: { value: 'hotdiggity' }});
+        assert.ok(spy.calledOnce);
+      });
+
+      it('should call action to submit form', function () {
+        var spy = sinon.spy(Actions, 'changePassword');
+        TestUtils.Simulate.submit($(changePasswordForm.getDOMNode()).find('#change-password')[0]);
+        assert.ok(spy.calledOnce);
+      });
+    });
+
+    describe('CreateAdminForm', function () {
+      var container, createAdminForm;
+
+      beforeEach(function () {
+        container = document.createElement('div');
+        createAdminForm = TestUtils.renderIntoDocument(<Components.CreateAdminForm loginAfter={false} />, container);
+      });
+
+      afterEach(function () {
+        React.unmountComponentAtNode(container);
+      });
+
+      it('should call action to update username on field change', function () {
+        var spy = sinon.spy(Actions, 'updateCreateAdminUsername');
+        TestUtils.Simulate.change($(createAdminForm.getDOMNode()).find('#username')[0], { target: { value: 'catsmeow' }});
+        assert.ok(spy.calledOnce);
+      });
+
+      it('should call action to update password confirm on field change', function () {
+        var spy = sinon.spy(Actions, 'updateCreateAdminPassword');
+        TestUtils.Simulate.change($(createAdminForm.getDOMNode()).find('#password')[0], { target: { value: 'topnotch' }});
+        assert.ok(spy.calledOnce);
+      });
+    });
+
+    describe('CreateAdminSidebar', function () {
+      var container, createAdminSidebar;
+
+      beforeEach(function () {
+        createAdminSidebarStore.reset();
+        container = document.createElement('div');
+        createAdminSidebar = TestUtils.renderIntoDocument(<Components.CreateAdminSidebar />, container);
+      });
+
+      afterEach(function () {
+        React.unmountComponentAtNode(container);
+      });
+
+      it('confirm the default selected nav item is the change pwd page', function () {
+        assert.equal($(createAdminSidebar.getDOMNode()).find('.active').find('a').attr('href'), '#changePassword');
+      });
+
+      it('confirm clicking a sidebar nav item selects it in the DOM', function () {
+        TestUtils.Simulate.click($(createAdminSidebar.getDOMNode()).find('li[data-page="addAdmin"]').find('a')[0]);
+        assert.equal($(createAdminSidebar.getDOMNode()).find('.active').find('a').attr('href'), '#addAdmin');
+      });
+    });
+
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/addons/auth/test/auth.storesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/auth/test/auth.storesSpec.js b/app/addons/auth/test/auth.storesSpec.js
new file mode 100644
index 0000000..fd98147
--- /dev/null
+++ b/app/addons/auth/test/auth.storesSpec.js
@@ -0,0 +1,148 @@
+// Licensed 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.
+
+define([
+  'api',
+  'react',
+  'testUtils',
+  'addons/auth/actiontypes',
+  'addons/auth/stores'
+], function (FauxtonAPI, React, testUtils, ActionTypes, Stores) {
+  var assert = testUtils.assert;
+
+  var changePasswordStore = Stores.changePasswordStore;
+  var createAdminStore = Stores.createAdminStore;
+  var createAdminSidebarStore = Stores.createAdminSidebarStore;
+
+  describe('Auth Stores', function () {
+
+    describe('ChangePasswordStore', function () {
+
+      it('get / set change password updates store', function () {
+        // check empty by default
+        assert.equal(changePasswordStore.getChangePassword(), '');
+
+        var newPassword = 'lets-rick-roll-mocha';
+        FauxtonAPI.dispatch({
+          type: ActionTypes.AUTH_UPDATE_CHANGE_PWD_FIELD,
+          value: newPassword
+        });
+        assert.equal(changePasswordStore.getChangePassword(), newPassword);
+      });
+
+      it('clearing change password clears in store', function () {
+        var newPassword = 'never-gonna-give-you-up';
+        FauxtonAPI.dispatch({
+          type: ActionTypes.AUTH_UPDATE_CHANGE_PWD_FIELD,
+          value: newPassword
+        });
+        assert.equal(changePasswordStore.getChangePassword(), newPassword);
+
+        FauxtonAPI.dispatch({ type: ActionTypes.AUTH_CLEAR_CHANGE_PWD_FIELDS });
+        assert.equal(changePasswordStore.getChangePassword(), '');
+      });
+
+      it('get / set change confirm password updates store', function () {
+        // check empty by default
+        assert.equal(changePasswordStore.getChangePasswordConfirm(), '');
+
+        // check getPassword works
+        var newPassword = 'never-gonna-let-you-down';
+        FauxtonAPI.dispatch({
+          type: ActionTypes.AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD,
+          value: newPassword
+        });
+        assert.equal(changePasswordStore.getChangePasswordConfirm(), newPassword);
+      });
+
+      it('clearing change confirm password clears in store', function () {
+        var newPassword = 'never-gonna-run-around-and-desert-you';
+        FauxtonAPI.dispatch({
+          type: ActionTypes.AUTH_UPDATE_CHANGE_PWD_CONFIRM_FIELD,
+          value: newPassword
+        });
+        assert.equal(changePasswordStore.getChangePasswordConfirm(), newPassword);
+
+        FauxtonAPI.dispatch({ type: ActionTypes.AUTH_CLEAR_CHANGE_PWD_FIELDS });
+        assert.equal(changePasswordStore.getChangePasswordConfirm(), '');
+      });
+    });
+
+
+    describe('CreateAdminStore', function () {
+
+      it('get / set username updates store', function () {
+        assert.equal(createAdminStore.getUsername(), '');
+
+        var newUsername = 'never-gonna-make-you-cry';
+        FauxtonAPI.dispatch({
+          type: ActionTypes.AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD,
+          value: newUsername
+        });
+        assert.equal(createAdminStore.getUsername(), newUsername);
+      });
+
+      it('clearing username clears in store', function () {
+        var newUsername = 'never-gonna-say-goodbye';
+        FauxtonAPI.dispatch({
+          type: ActionTypes.AUTH_UPDATE_CREATE_ADMIN_USERNAME_FIELD,
+          value: newUsername
+        });
+        assert.equal(createAdminStore.getUsername(), newUsername);
+
+        FauxtonAPI.dispatch({ type: ActionTypes.AUTH_CLEAR_CREATE_ADMIN_FIELDS });
+        assert.equal(createAdminStore.getUsername(), '');
+      });
+
+      it('get / set password updates store', function () {
+        // check empty by default
+        assert.equal(createAdminStore.getPassword(), '');
+
+        // check getPassword works
+        var newPassword = 'never-gonna-tell-a-lie-and-hurt-you';
+        FauxtonAPI.dispatch({
+          type: ActionTypes.AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD,
+          value: newPassword
+        });
+        assert.equal(createAdminStore.getPassword(), newPassword);
+      });
+
+      it('clearing change confirm password clears in store', function () {
+        var newPassword = 'mocha-please-consider-yourself-rickrolled';
+        FauxtonAPI.dispatch({
+          type: ActionTypes.AUTH_UPDATE_CREATE_ADMIN_PWD_FIELD,
+          value: newPassword
+        });
+        assert.equal(createAdminStore.getPassword(), newPassword);
+
+        FauxtonAPI.dispatch({ type: ActionTypes.AUTH_CLEAR_CREATE_ADMIN_FIELDS });
+        assert.equal(createAdminStore.getPassword(), '');
+      });
+    });
+
+
+    describe('CreateAdminSidebarStore', function () {
+      var defaultPage = 'changePassword';
+      it('has correct default selected page', function () {
+        assert.equal(createAdminSidebarStore.getSelectedPage(), defaultPage);
+      });
+
+      it('selecting a page updates the selected page in store', function () {
+        var newPage = 'addAdmin';
+        FauxtonAPI.dispatch({ type: ActionTypes.AUTH_SELECT_PAGE, page: newPage });
+        assert.equal(createAdminSidebarStore.getSelectedPage(), newPage);
+      });
+    });
+
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/core/routeObject.js
----------------------------------------------------------------------
diff --git a/app/core/routeObject.js b/app/core/routeObject.js
index 0af072a..818f4eb 100644
--- a/app/core/routeObject.js
+++ b/app/core/routeObject.js
@@ -132,8 +132,8 @@ function (FauxtonAPI, React, Backbone) {
     },
 
     renderReactComponents: function () {
-      _.each(this.reactComponents, function (component, selector) {
-        React.render(React.createElement(component, null), $(selector)[0]);
+      _.each(this.reactComponents, function (componentInfo, selector) {
+        React.render(React.createElement(componentInfo.component, componentInfo.props), $(selector)[0]);
       });
     },
 
@@ -245,10 +245,13 @@ function (FauxtonAPI, React, Backbone) {
       return view;
     },
 
-    setComponent: function (selector, component) {
+    setComponent: function (selector, component, props) {
       this.removeView(selector);
       this.removeComponent(selector);
-      this.reactComponents[selector] = component;
+      this.reactComponents[selector] = {
+        component: component,
+        props: props || null
+      };
     },
 
     removeComponent: function (selector) {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/410d2c77/app/core/tests/routeObjectSpec.js
----------------------------------------------------------------------
diff --git a/app/core/tests/routeObjectSpec.js b/app/core/tests/routeObjectSpec.js
index e267f65..fbac6b6 100644
--- a/app/core/tests/routeObjectSpec.js
+++ b/app/core/tests/routeObjectSpec.js
@@ -178,7 +178,15 @@ define([
           var fakeSelector = '.fake-selector';
 
           testRouteObject.setComponent(fakeSelector, fakeReactComponent);
-          assert.deepEqual(fakeReactComponent, testRouteObject.reactComponents[fakeSelector]);
+          assert.deepEqual(fakeReactComponent, testRouteObject.reactComponents[fakeSelector].component);
+        });
+
+        it('sets props for the selector', function () {
+          var fakeReactComponent = React.createElement('div');
+          var fakeSelector = '.fake-selector';
+
+          testRouteObject.setComponent(fakeSelector, fakeReactComponent, {foo: 'bar baromat'});
+          assert.deepEqual(fakeReactComponent, testRouteObject.reactComponents[fakeSelector].component);
         });
 
       });


Mime
View raw message