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"}} -
  • {{#link-to "settings.user"}}Your Profile{{/link-to}}
  • +
  • {{#link-to "settings.users.user" session.user.slug}}Your Profile{{/link-to}}
  • Help / Support
  • 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 @@ +
    + +

    Users

    +
    + New User +
    +
    + +
    +
    + +

    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"}} +
    + Photo of {{unbound name}} + +
    + {{#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 +//});