diff --git a/core/client/.jshintrc b/core/client/.jshintrc
index 18c4e9e734..5574b02288 100644
--- a/core/client/.jshintrc
+++ b/core/client/.jshintrc
@@ -30,6 +30,7 @@
"validator": true,
"ic": true,
"_": true,
- "NProgress": true
+ "NProgress": true,
+ "moment": true
}
}
diff --git a/core/client/controllers/settings/users/index.js b/core/client/controllers/settings/users/index.js
new file mode 100644
index 0000000000..f731e21c06
--- /dev/null
+++ b/core/client/controllers/settings/users/index.js
@@ -0,0 +1,18 @@
+/*global alert */
+var UsersIndexController = Ember.ArrayController.extend({
+ activeUsers: function () {
+ return this.content.filterBy('status', 'active');
+ }.property('model'),
+
+ invitedUsers: function () {
+ return this.content.filterBy('status', 'invited');
+ }.property('model'),
+
+ actions: {
+ addUser: function () {
+ alert('@TODO: needs to show the "add user" modal - see issue #3079 on GitHub');
+ }
+ }
+});
+
+export default UsersIndexController;
diff --git a/core/client/controllers/settings/user.js b/core/client/controllers/settings/users/user.js
similarity index 73%
rename from core/client/controllers/settings/user.js
rename to core/client/controllers/settings/users/user.js
index 825e07da3a..4fd9c24f6c 100644
--- a/core/client/controllers/settings/user.js
+++ b/core/client/controllers/settings/users/user.js
@@ -3,7 +3,10 @@ var SettingsUserController = Ember.ObjectController.extend({
user: Ember.computed.alias('model'),
+ email: Ember.computed.readOnly('user.email'),
+
coverDefault: '/shared/img/user-cover.png',
+
cover: function () {
// @TODO: add {{asset}} subdir path
var cover = this.get('user.cover');
@@ -19,10 +22,31 @@ var SettingsUserController = Ember.ObjectController.extend({
image: function () {
// @TODO: add {{asset}} subdir path
- return 'background-image: url(' + this.getWithDefault('user.image', '/shared/img/user-image.png') + ')';
+ return 'background-image: url(' + this.getWithDefault('user.image', '/shared/img/user-image.png') + ')';
}.property('user.image'),
+ imageUrl: function () {
+ // @TODO: add {{asset}} subdir path
+ return this.getWithDefault('user.image', '/shared/img/user-image.png');
+ }.property('user.image'),
+
+ last_login: function () {
+ return moment(this.get('user.last_login')).fromNow();
+ }.property('user.last_login'),
+
+ created_at: function () {
+ return moment(this.get('user.created_at')).fromNow();
+ }.property('user.created_at'),
+
actions: {
+ revoke: function () {
+ alert('@TODO: revoke users invitation');
+ },
+
+ resend: function () {
+ alert('@TODO: resend users invitation');
+ },
+
save: function () {
var user = this.get('user'),
self = this;
diff --git a/core/client/router.js b/core/client/router.js
index 7486b578cf..8861e2838c 100644
--- a/core/client/router.js
+++ b/core/client/router.js
@@ -30,7 +30,9 @@ Router.map(function () {
});
this.resource('settings', function () {
this.route('general');
- this.route('user');
+ this.resource('settings.users', { path: '/users' }, function () {
+ this.route('user', { path: '/:slug' });
+ });
this.route('apps');
});
this.route('debug');
diff --git a/core/client/routes/settings/users.js b/core/client/routes/settings/users.js
new file mode 100644
index 0000000000..02bfbc91cd
--- /dev/null
+++ b/core/client/routes/settings/users.js
@@ -0,0 +1,3 @@
+var UsersRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin);
+
+export default UsersRoute;
diff --git a/core/client/routes/settings/users/index.js b/core/client/routes/settings/users/index.js
new file mode 100644
index 0000000000..445375e51b
--- /dev/null
+++ b/core/client/routes/settings/users/index.js
@@ -0,0 +1,8 @@
+var UsersIndexRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, {
+
+ model: function () {
+ return this.store.find('user');
+ }
+});
+
+export default UsersIndexRoute;
diff --git a/core/client/routes/settings/user.js b/core/client/routes/settings/users/user.js
similarity index 100%
rename from core/client/routes/settings/user.js
rename to core/client/routes/settings/users/user.js
diff --git a/core/client/templates/-navbar.hbs b/core/client/templates/-navbar.hbs
index 9e1aa75dc2..73451979ec 100644
--- a/core/client/templates/-navbar.hbs
+++ b/core/client/templates/-navbar.hbs
@@ -18,7 +18,7 @@
{{session.user.name}}
{{/gh-popover-button}}
{{#gh-popover tagName="ul" classNames="overlay" name="user-menu" closeOnClick="true"}}
-
+
diff --git a/core/client/templates/settings.hbs b/core/client/templates/settings.hbs
index 1b5c12bde5..2821b2f580 100644
--- a/core/client/templates/settings.hbs
+++ b/core/client/templates/settings.hbs
@@ -9,7 +9,7 @@
{{/view}}
{{#view "item-view" tagName="li" class="users"}}
- {{#link-to "settings.user"}}User{{/link-to}}
+ {{#link-to "settings.users"}}Users{{/link-to}}
{{/view}}
{{#if showApps}}
diff --git a/core/client/templates/settings/users/index.hbs b/core/client/templates/settings/users/index.hbs
new file mode 100644
index 0000000000..c5c33345ec
--- /dev/null
+++ b/core/client/templates/settings/users/index.hbs
@@ -0,0 +1,63 @@
+
+
+
+
+
+ Invited users
+
+ {{#each invitedUsers itemController="settings/users/user"}}
+
+
ic
+
+
+ {{email}}
+ Invitation sent: {{created_at}}
+
+
+
+ {{else}}
+
+ No invited users.
+
+ {{/each}}
+
+
+
+
+
+
+ Active users
+
+
+ {{#each activeUsers itemController="settings/users/user"}}
+
+
+
+
+ {{#link-to 'settings.users.user' slug class="ember-view name" }}
+ {{user.name}}
+ {{/link-to}}
+
+ Last seen: {{unbound last_login}}
+
+
+
+
+ {{/each}}
+
+
+
diff --git a/core/client/templates/settings/user.hbs b/core/client/templates/settings/users/user.hbs
similarity index 100%
rename from core/client/templates/settings/user.hbs
rename to core/client/templates/settings/users/user.hbs
diff --git a/core/test/functional/base.js b/core/test/functional/base.js
index 10f845370f..207f5646a4 100644
--- a/core/test/functional/base.js
+++ b/core/test/functional/base.js
@@ -30,6 +30,7 @@ var DEBUG = false, // TOGGLE THIS TO GET MORE SCREENSHOTS
url = 'http://' + host + (noPort ? '/' : ':' + port + '/'),
newUser = {
name: 'Test User',
+ slug: 'test-user',
email: email,
password: password
},
diff --git a/core/test/functional/client/app_test.js b/core/test/functional/client/app_test.js
index 00f99640a1..bd96c6d2a4 100644
--- a/core/test/functional/client/app_test.js
+++ b/core/test/functional/client/app_test.js
@@ -54,7 +54,7 @@ CasperTest.begin('Admin navigation bar is correct', 27, function suite(test) {
test.assertExists('#usermenu li.usermenu-profile a', 'Profile menu item exists');
test.assertSelectorHasText('#usermenu li.usermenu-profile a', 'Your Profile',
'Profile menu item has correct text');
- test.assertEquals(profileHref, '/ghost/settings/user/', 'Profile href is correct');
+ test.assertEquals(profileHref, '/ghost/settings/users/' + newUser.slug + '/', 'Profile href is correct');
test.assertExists('#usermenu li.usermenu-help a', 'Help menu item exists');
test.assertSelectorHasText('#usermenu li.usermenu-help a', 'Help / Support', 'Help menu item has correct text');
diff --git a/core/test/functional/client/settings_test.js b/core/test/functional/client/settings_test.js
index 58d010a62e..8429346a9a 100644
--- a/core/test/functional/client/settings_test.js
+++ b/core/test/functional/client/settings_test.js
@@ -6,7 +6,7 @@
// These classes relate to elements which only appear when a given tab is loaded.
// These are used to check that a switch to a tab is complete, or that we are on the right tab.
var generalTabDetector = '.settings-content form#settings-general',
- userTabDetector = '.settings-content form.user-profile';
+ usersTabDetector = '.settings-content .settings-users';
CasperTest.begin('Settings screen is correct', 17, function suite(test) {
casper.thenOpenAndWaitForPageLoad('settings', function testTitleAndUrl() {
@@ -30,11 +30,11 @@ CasperTest.begin('Settings screen is correct', 17, function suite(test) {
casper.then(function testSwitchingTabs() {
casper.thenClick('.settings-menu .users a');
- casper.waitForSelector(userTabDetector, function then () {
+ casper.waitForSelector(usersTabDetector, function then () {
// assert that the right menu item is active
test.assertExists('.settings-menu .users.active', 'User tab is active');
test.assertDoesntExist('.settings-menu .general.active', 'General tab is not active');
- }, casper.failOnTimeout(test, 'waitForSelector `userTabDetector` timed out'));
+ }, casper.failOnTimeout(test, 'waitForSelector `usersTabDetector` timed out'));
casper.thenClick('.settings-menu .general a');
casper.waitForSelector(generalTabDetector, function then () {
@@ -368,4 +368,4 @@ CasperTest.begin('General settings validation is correct', 7, function suite(tes
// casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
// test.assertSelectorHasText('.notification-error', 'is too long');
// }, casper.failOnTimeout(test, 'Location field length error did not appear', 2000));
-//});
\ No newline at end of file
+//});