Merge pull request #51 from thermokarst/refactor-users

Refactor users
This commit is contained in:
Matthew Dillon 2015-11-10 10:26:24 -07:00
commit 9fa701c44e
41 changed files with 599 additions and 309 deletions

View file

@ -1,7 +1,9 @@
import Ember from 'ember'; import Ember from 'ember';
const { Helper: { helper } } = Ember;
export function equalHelper(params) { export function equalHelper(params) {
return params[0] === params[1]; return params[0] === params[1];
} }
export default Ember.HTMLBars.makeBoundHelper(equalHelper); export default helper(equalHelper);

View file

@ -2,7 +2,7 @@ export default function() {
// Don't use mirage for development (for now) // Don't use mirage for development (for now)
this.urlPrefix = 'http://127.0.0.1:8901'; this.urlPrefix = 'http://127.0.0.1:8901';
this.namespace = '/api'; this.namespace = '/api';
this.passthrough(); this.passthrough('http://localhost:4200/**', 'http://127.0.0.1:8901/**');
} }
export function testConfig() { export function testConfig() {
@ -10,7 +10,10 @@ export function testConfig() {
this.namespace = '/api/hymenobacter'; this.namespace = '/api/hymenobacter';
this.timing = 0; this.timing = 0;
this.get('/users');
this.post('/users');
this.get('/users/:id'); this.get('/users/:id');
this.put('/users/:id');
this.get('/species'); this.get('/species');
this.post('/species'); this.post('/species');

View file

@ -16,6 +16,7 @@ export default Mixin.create({
if (model.get('hasDirtyAttributes')) { if (model.get('hasDirtyAttributes')) {
model.save().then((model) => { model.save().then((model) => {
this.get('flashMessages').clearMessages();
this.transitionToRoute(fallbackRoute, model); this.transitionToRoute(fallbackRoute, model);
}, () => { }, () => {
ajaxError(model.get('errors'), this.get('flashMessages')); ajaxError(model.get('errors'), this.get('flashMessages'));

View file

@ -5,10 +5,12 @@ const { Mixin, inject: { service }} = Ember;
export default Mixin.create({ export default Mixin.create({
currentUser: service('session-account'), currentUser: service('session-account'),
metaData: null, metaData: null,
isAdmin: null,
setupMetaDataOnInit: Ember.on('init', function() { setupMetaDataOnInit: Ember.on('init', function() {
this.get('currentUser.account').then((user) => { this.get('currentUser.account').then((user) => {
this.set('metaData', user.get('metaData')); this.set('metaData', user.get('metaData'));
this.set('isAdmin', user.get('isAdmin'));
}); });
}), }),
}); });

View file

@ -6,5 +6,5 @@ const { Controller } = Ember;
export default Controller.extend(SaveModel, { export default Controller.extend(SaveModel, {
// Required for SaveModel mixin // Required for SaveModel mixin
fallbackRouteSave: 'protected.characteristics.show', fallbackRouteSave: 'protected.characteristics.show',
fallbackRouteCancel: 'protected.characteristics.index', fallbackRouteCancel: 'protected.characteristics.show',
}); });

View file

@ -1,33 +1,30 @@
import Ember from 'ember'; import Ember from 'ember';
import ajaxRequest from '../../../../utils/ajax-request'; import ajaxRequest from '../../../../utils/ajax-request';
export default Ember.Controller.extend({ const { Controller, inject: { service } } = Ember;
session: Ember.inject.service('session'),
currentUser: Ember.inject.service('session-account'),
passwordConfirm: null, export default Controller.extend({
session: service(),
currentUser: service('session-account'),
actions: { actions: {
save: function() { save: function(password) {
if (this.get('password') !== this.get('passwordConfirm')) { const url = `${this.get('globals.apiURL')}/api/${this.get('globals.genus')}/users/password`;
this.get('flashMessages').clearMessages(); const id = this.get('currentUser.account.id');
this.get('flashMessages').error("Password fields don't match"); const options = {
return;
}
let url = `${this.get('globals.apiURL')}/api/${this.get('globals.genus')}/users/password`;
let options = {
method: 'POST', method: 'POST',
data: { data: {
id: this.get('currentUser.account.id'), id: id,
password: this.get('password'), password: password,
}, },
}; };
ajaxRequest(url, options, this.get('session')); ajaxRequest(url, options, this.get('session'));
this.transitionToRoute('protected.users.index'); this.transitionToRoute('protected.users.show', id);
this.get('flashMessages').information('Your password has been changed.'); this.get('flashMessages').information('Your password has been changed.');
}, },
cancel: function() {
this.transitionToRoute('protected.users.show', this.get('currentUser.account.id'));
},
}, },
}); });

View file

@ -0,0 +1,52 @@
import Ember from 'ember';
const { Component } = Ember;
export default Component.extend({
password: null,
passwordConfirm: null,
matches: false,
// Actions
"on-save": null,
"on-cancel": null,
updateField: function(property, value) {
this.set(property, value);
this.verifyPassword(this.get('password'), this.get('passwordConfirm'));
},
verifyPassword: function(password, passwordConfirm) {
if (password && passwordConfirm) {
if (password !== passwordConfirm) {
this.get('flashMessages').clearMessages();
this.get('flashMessages').error("Password fields don't match");
this.set('matches', false);
} else {
this.get('flashMessages').clearMessages();
this.set('matches', true);
}
}
},
actions: {
save: function() {
this.verifyPassword(this.get('password'), this.get('passwordConfirm'));
if (this.get('matches')) {
return this.attrs['on-save'](this.get('password'));
}
},
cancel: function() {
return this.attrs['on-cancel']();
},
passwordDidChange: function(value) {
this.updateField('password', value);
},
passwordConfirmDidChange: function(value) {
this.updateField('passwordConfirm', value);
},
},
});

View file

@ -0,0 +1,29 @@
<div class="grid-1">
<div class="span-1">
<fieldset>
<legend>Change password</legend>
<form {{action 'save' on='submit'}}>
<ul>
<li>
<label>New Password</label>
{{one-way-input type="password" class="password" value=password update=(action "passwordDidChange")}}
</li>
<li>
<label>New Password (confirm)</label>
{{one-way-input type="password" class="password-confirm" value=passwordConfirm update=(action "passwordConfirmDidChange")}}
</li>
<li>
<a class="button-red smaller" {{action 'cancel'}}>
Cancel
</a>
{{#if matches}}
<button type="submit" class="button-green smaller submit-password">
Submit
</button>
{{/if}}
</li>
</ul>
</form>
</fieldset>
</div>
</div>

View file

@ -1,16 +1,19 @@
import Ember from 'ember'; import Ember from 'ember';
export default Ember.Route.extend({ const { Route, inject: { service } } = Ember;
currentUser: Ember.inject.service('session-account'),
export default Route.extend({
currentUser: service('session-account'),
beforeModel: function(transition) { beforeModel: function(transition) {
this._super(transition); this._super(transition);
let user_id = transition.params['protected.users.changepassword'].user_id; // Only the logged in user can change their password
const user_id = transition.params['protected.users.changepassword'].user_id;
this.get('currentUser.account').then((user) => { this.get('currentUser.account').then((user) => {
if (user.get('id') !== user_id) { if (user.get('id') !== user_id) {
this.transitionTo('protected.users.index'); this.transitionTo('protected.users.show', user.get('id'));
} }
}); });
} }

View file

@ -1,24 +1,5 @@
<div class="grid-1"> {{
<div class="span-1"> protected/users/changepassword/password-form
<fieldset> on-save=(action "save")
<legend>Change password</legend> on-cancel=(action "cancel")
<form {{action 'save' on='submit'}}> }}
<ul>
<li>
<label>New Password</label>
{{input type="password" value=password}}
</li>
<li>
<label>New Password (confirm)</label>
{{input type="password" value=passwordConfirm}}
</li>
<li>
<button type="submit" class="button-green smaller">
Submit
</button>
</li>
</ul>
</form>
</fieldset>
</div>
</div>

View file

@ -1,40 +1,10 @@
import Ember from 'ember'; import Ember from 'ember';
import ajaxError from '../../../../utils/ajax-error'; import SaveModel from '../../../../mixins/save-model';
export default Ember.Controller.extend({ const { Controller } = Ember;
actions: {
save: function() {
let user = this.get('model');
if (user.get('hasDirtyAttributes')) { export default Controller.extend(SaveModel, {
let attrs = user.changedAttributes(), roleChanged = false; // Required for SaveModel mixin
if (attrs.role) { fallbackRouteSave: 'protected.users.show',
roleChanged = true; fallbackRouteCancel: 'protected.users.show',
}
user.save().then((user) => {
this.get('flashMessages').clearMessages();
if (roleChanged) {
// Need to clear the store so that canEdit and canAdd
// attributes reflect the new role.
this.get('store').unloadAll();
}
this.transitionToRoute('protected.users.show', user);
}, () => {
ajaxError(user.get('errors'), this.get('flashMessages'));
});
} else {
this.transitionToRoute('protected.users.show', user);
}
},
cancel: function() {
let user = this.get('model');
user.get('errors').clear();
user.rollbackAttributes();
this.transitionToRoute('protected.users.show', user);
},
},
}); });

View file

@ -1,8 +1,12 @@
import Ember from 'ember'; import Ember from 'ember';
export default Ember.Route.extend({ const { Route, inject: { service } } = Ember;
currentUser: Ember.inject.service('session-account'),
export default Route.extend({
currentUser: service('session-account'),
// Not using ElevatedAccess Mixin because the rules for viewing user accounts
// is slightly different.
beforeModel: function(transition) { beforeModel: function(transition) {
this._super(transition); this._super(transition);
@ -16,7 +20,7 @@ export default Ember.Route.extend({
}, },
model: function(params) { model: function(params) {
return this.store.findRecord('user', params.user_id, { reload: true }); return this.store.findRecord('user', params.user_id);
}, },
}); });

View file

@ -1,7 +1,6 @@
{{ {{
protected/users/user-form protected/users/user-form
user=model user=model
currentUser=currentUser.account on-save=(action "save")
save="save" on-cancel=(action "cancel")
cancel="cancel"
}} }}

View file

@ -1,7 +1,9 @@
import Ember from 'ember'; import Ember from 'ember';
export default Ember.Route.extend({ const { Route, inject: { service } } = Ember;
currentUser: Ember.inject.service('session-account'),
export default Route.extend({
currentUser: service('session-account'),
beforeModel: function(transition) { beforeModel: function(transition) {
this._super(transition); this._super(transition);

View file

@ -1,33 +1,6 @@
<h2>{{genus-name}} Users</h2> <h2>{{genus-name}} Users</h2>
<h3>Total users: {{model.length}}</h3>
<table class="flakes-table"> {{
<thead> protected/users/index/users-table
<tr> users=model
<th>Name</th> }}
<th>Email</th>
<th>Role</th>
<th>Date Registered</th>
</tr>
</thead>
<tbody>
{{#each model as |row|}}
<tr>
<td>
{{#link-to 'protected.users.show' row}}
{{row.name}}
{{/link-to}}
</td>
<td>
{{row.email}}
</td>
<td>
{{row.fullRole}}
</td>
<td>
{{null-time row.createdAt 'LL'}}
</td>
</tr>
{{/each}}
</tbody>
</table>

View file

@ -0,0 +1,7 @@
import Ember from 'ember';
const { Component } = Ember;
export default Component.extend({
users: null,
});

View file

@ -0,0 +1,33 @@
<h3 id="total-users">Total users: {{users.length}}</h3>
<table class="flakes-table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Date Registered</th>
</tr>
</thead>
<tbody>
{{#each users as |user|}}
<tr>
<td>
{{#link-to 'protected.users.show' user}}
{{user.name}}
{{/link-to}}
</td>
<td>
{{user.email}}
</td>
<td>
{{user.fullRole}}
</td>
<td>
{{null-time user.createdAt 'LL'}}
</td>
</tr>
{{/each}}
</tbody>
</table>

View file

@ -0,0 +1 @@
{{loading-panel}}

View file

@ -1,9 +1,9 @@
import Ember from 'ember'; import Ember from 'ember';
import DeleteModel from '../../../../mixins/delete-model';
export default Ember.Controller.extend({ const { Controller } = Ember;
currentUser: Ember.inject.service('session-account'),
isUser: Ember.computed('model.id', 'currentUser.account.id', function() { export default Controller.extend(DeleteModel, {
return this.get('model.id') === this.get('currentUser.account.id'); // Required for DeleteModel mixin
}), transitionRoute: 'protected.index',
}); });

View file

@ -1,21 +1,25 @@
import Ember from 'ember'; import Ember from 'ember';
export default Ember.Route.extend({ const { Route, inject: { service } } = Ember;
currentUser: Ember.inject.service('session-account'),
export default Route.extend({
currentUser: service('session-account'),
// Not using ElevatedAccess Mixin because the rules for viewing user accounts
// is slightly different.
beforeModel: function(transition) { beforeModel: function(transition) {
this._super(transition); this._super(transition);
this.get('currentUser.account').then((currentUser) => { this.get('currentUser.account').then((user) => {
let user_id = transition.params['protected.users.show'].user_id; const user_id = transition.params['protected.users.show'].user_id;
if (!currentUser.get('isAdmin') && currentUser.get('id') !== user_id) { if (!user.get('isAdmin') && user.get('id') !== user_id) {
this.transitionTo('protected.users.index'); this.transitionTo('protected.users.index');
} }
}); });
}, },
model: function(params) { model: function(params) {
return this.store.findRecord('user', params.user_id, { reload: true }); return this.store.findRecord('user', params.user_id);
}, },
}); });

View file

@ -1,53 +1,5 @@
<div class="span-1"> {{
<fieldset class="flakes-information-box"> protected/users/show/user-card
<legend> user=model
{{model.name}} on-delete=(action 'delete')
</legend> }}
{{! ROW 1 }}
<div class="grid-2 gutter-20">
<dl class="span-1">
<dt>Email</dt>
<dd>
{{model.email}}
</dd>
</dl>
<dl class="span-1">
<dt>Role</dt>
<dd>
{{model.fullRole}}
</dd>
</dl>
</div>
{{! ROW 2 }}
<div class="grid-2 gutter-20">
<dl class="span-1">
<dt>Record Created</dt>
<dd>{{null-time model.createdAt 'LL'}}</dd>
</dl>
<dl class="span-1">
<dt>Record Updated</dt>
<dd>{{null-time model.updatedAt 'LL'}}</dd>
</dl>
</div>
</fieldset>
</div>
<br>
<div class="grid-2 gutter-20">
{{#if isUser}}
<div class="span-1">
{{#link-to 'protected.users.changepassword' model.id class="button-gray smaller"}}
Change Password
{{/link-to}}
</div>
{{/if}}
<div class="span-1">
{{#if model.canEdit}}
{{#link-to 'protected.users.edit' model.id class="button-gray smaller"}}
Edit
{{/link-to}}
{{/if}}
</div>
</div>

View file

@ -0,0 +1,13 @@
import Ember from 'ember';
const { Component, computed, inject: { service } } = Ember;
export default Component.extend({
currentUser: service('session-account'),
user: null,
isUser: computed('user.id', 'currentUser.account.id', function() {
return this.get('user.id') === this.get('currentUser.account.id');
}),
});

View file

@ -0,0 +1,53 @@
<div class="span-1">
<fieldset class="flakes-information-box">
<legend>
{{user.name}}
</legend>
{{! ROW 1 }}
<div class="grid-2 gutter-20">
<dl class="span-1">
<dt>Email</dt>
<dd>
{{user.email}}
</dd>
</dl>
<dl class="span-1">
<dt>Role</dt>
<dd>
{{user.fullRole}}
</dd>
</dl>
</div>
{{! ROW 2 }}
<div class="grid-2 gutter-20">
<dl class="span-1">
<dt>Record Created</dt>
<dd>{{null-time user.createdAt 'LL'}}</dd>
</dl>
<dl class="span-1">
<dt>Record Updated</dt>
<dd>{{null-time user.updatedAt 'LL'}}</dd>
</dl>
</div>
</fieldset>
</div>
<br>
<div class="grid-2 gutter-20">
{{#if isUser}}
<div class="span-1">
{{#link-to 'protected.users.changepassword' user.id class="button-gray smaller"}}
Change Password
{{/link-to}}
</div>
{{/if}}
<div class="span-1">
{{#if user.canEdit}}
{{#link-to 'protected.users.edit' user.id class="button-gray smaller"}}
Edit
{{/link-to}}
{{/if}}
</div>
</div>

View file

@ -1,15 +1,61 @@
import Ember from 'ember'; import Ember from 'ember';
import SetupMetaData from '../../../../mixins/setup-metadata';
export default Ember.Component.extend({ const { Component } = Ember;
export default Component.extend(SetupMetaData, {
// Read-only attributes
user: null,
isDirty: false,
roles: Ember.String.w('A R W'), roles: Ember.String.w('A R W'),
// Actions
"on-save": null,
"on-cancel": null,
"on-update": null,
// Property mapping
propertiesList: ['name', 'email', 'role'],
name: null,
email: null,
role: null,
resetOnInit: Ember.on('init', function() {
this.get('propertiesList').forEach((field) => {
const valueInUser = this.get('user').get(field);
this.set(field, valueInUser);
});
}),
updateField: function(property, value) {
this.set(property, value);
// Manually compare against passed in value
if (this.get('user').get(property) !== value) {
this.set('isDirty', true);
} else {
this.set('isDirty', false);
}
},
actions: { actions: {
save: function() { save: function() {
this.sendAction('save'); return this.attrs['on-save'](this.getProperties(this.get('propertiesList')));
}, },
cancel: function() { cancel: function() {
this.sendAction('cancel'); return this.attrs['on-cancel']();
}, },
}
nameDidChange: function(value) {
this.updateField('name', value);
},
emailDidChange: function(value) {
this.updateField('email', value);
},
roleDidChange: function(value) {
this.updateField('role', value);
},
},
}); });

View file

@ -1,29 +1,29 @@
<form class="grid-form" {{action 'save' on='submit'}}> <form class="grid-form" {{action 'save' on='submit'}}>
<fieldset> <fieldset>
<legend><em>{{user.name}}</em></legend> <legend><em>{{name}}</em></legend>
<div data-row-span="1"> <div data-row-span="1">
<div data-field-span="1"> <div data-field-span="1">
<label>Name</label> <label>Name</label>
{{input value=user.name}} {{one-way-input type="text" class="user-name" value=name update=(action "nameDidChange")}}
</div> </div>
</div> </div>
<div data-row-span="1"> <div data-row-span="1">
<div data-field-span="1"> <div data-field-span="1">
<label>Email</label> <label>Email</label>
{{input value=user.email}} {{one-way-input type="text" class="email" value=email update=(action "emailDidChange")}}
</div> </div>
</div> </div>
<div data-row-span="1"> <div data-row-span="1">
<div data-field-span="1"> <div data-field-span="1">
<label>Role</label> <label>Role</label>
{{#if session.currentUser.isAdmin}} {{#if isAdmin}}
<select onchange={{action (mut user.role) value="target.value"}}> <select onchange={{action "roleDidChange" value="target.value"}}>
{{#each roles as |roleChoice|}} {{#each roles as |roleChoice|}}
<option value={{roleChoice}} selected={{equal user.role roleChoice}}>{{roleChoice}}</option> <option value={{roleChoice}} selected={{equal role roleChoice}}>{{roleChoice}}</option>
{{/each}} {{/each}}
</select> </select>
{{else}} {{else}}
{{user.role}} {{role}} {{!-- Not editable --}}
{{/if}} {{/if}}
</div> </div>
</div> </div>
@ -32,8 +32,8 @@
<a class="button-red smaller" {{action 'cancel'}}> <a class="button-red smaller" {{action 'cancel'}}>
Cancel Cancel
</a> </a>
{{#if user.hasDirtyAttributes}} {{#if isDirty}}
<button type="submit" class="button-green smaller"> <button type="submit" class="button-green smaller save-user">
Save Save
</button> </button>
{{/if}} {{/if}}

View file

@ -1,6 +1,8 @@
import Ember from 'ember'; import Ember from 'ember';
export default Ember.Controller.extend({ const { Controller } = Ember;
queryParams: ['token'],
export default Controller.extend({
queryParams: ['token'],
token: null,
}); });

View file

@ -1,14 +1,16 @@
import Ember from 'ember'; import Ember from 'ember';
import UnauthenticatedRouteMixin from 'ember-simple-auth/mixins/unauthenticated-route-mixin'; import UnauthenticatedRouteMixin from 'ember-simple-auth/mixins/unauthenticated-route-mixin';
export default Ember.Route.extend(UnauthenticatedRouteMixin, { const { Route, get, inject: { service } } = Ember;
session: Ember.inject.service('session'),
currentUser: Ember.inject.service('session-account'), export default Route.extend(UnauthenticatedRouteMixin, {
session: service(),
currentUser: service('session-account'),
beforeModel: function(transition) { beforeModel: function(transition) {
this._super(transition); this._super(transition);
let token = Ember.get(transition, 'queryParams.token'); const token = get(transition, 'queryParams.token');
this.get('session').authenticate('authenticator:jwt-resolved', token).then(() => { this.get('session').authenticate('authenticator:jwt-resolved', token).then(() => {
this.get('currentUser.account').then((account) => { this.get('currentUser.account').then((account) => {
this.transitionTo('protected.users.changepassword', account.get('id')); this.transitionTo('protected.users.changepassword', account.get('id'));

View file

@ -1,30 +1,29 @@
import Ember from 'ember'; import Ember from 'ember';
import ajaxError from '../../../utils/ajax-error'; import ajaxError from '../../../utils/ajax-error';
export default Ember.Controller.extend({ const { Controller } = Ember;
passwordConfirm: null,
export default Controller.extend({
isLoading: false,
actions: { actions: {
save: function() { save: function(properties) {
let user = this.get('user'); const user = this.get('model');
user.setProperties(properties);
// All validation is server-side, except for password verification matching
if (user.get('password') !== this.get('passwordConfirm')) {
this.get('flashMessages').clearMessages();
this.get('flashMessages').error("Password fields don't match");
return;
}
if (user.get('hasDirtyAttributes')) { if (user.get('hasDirtyAttributes')) {
this.set('isLoading', true);
user.save().then(() => { user.save().then(() => {
this.transitionTo('login').then(() => { this.transitionToRoute('login').then(() => {
this.get('flashMessages').information(`You have successfully signed up. this.get('flashMessages').information(`You have successfully signed up.
Please check your email for further instructions.`); Please check your email for further instructions.`);
}); });
}, () => { }, () => {
this.set('isLoading', false);
ajaxError(user.get('errors'), this.get('flashMessages')); ajaxError(user.get('errors'), this.get('flashMessages'));
}); });
} }
}, },
}, },
}); });

View file

@ -0,0 +1,66 @@
import Ember from 'ember';
const { Component } = Ember;
export default Component.extend({
// Read-only attributes
user: null,
isLoading: null,
// Actions
"on-save": null,
"on-cancel": null,
// Property mapping
propertiesList: ['name', 'email', 'password', 'passwordConfirm'],
name: null,
email: null,
password: null,
passwordConfirm: null,
resetOnInit: Ember.on('init', function() {
this.get('propertiesList').forEach((field) => {
const valueInUser = this.get('user').get(field);
this.set(field, valueInUser);
});
}),
updateField: function(property, value) {
this.set(property, value);
// Manually compare against passed in value
if (this.get('user').get(property) !== value) {
this.set('isDirty', true);
} else {
this.set('isDirty', false);
}
},
actions: {
save: function() {
// All validation is server-side, except for password verification matching
if (this.get('password') !== this.get('passwordConfirm')) {
this.get('flashMessages').clearMessages();
this.get('flashMessages').error("Password fields don't match");
return;
}
return this.attrs['on-save'](this.getProperties(this.get('propertiesList')));
},
nameDidChange: function(value) {
this.updateField('name', value);
},
emailDidChange: function(value) {
this.updateField('email', value);
},
passwordDidChange: function(value) {
this.updateField('password', value);
},
passwordConfirmDidChange: function(value) {
this.updateField('passwordConfirm', value);
}
}
});

View file

@ -0,0 +1,36 @@
{{#if isLoading}}
{{loading-panel}}
{{else}}
<div class="grid-1">
<div class="span-1">
<fieldset>
<legend>New User Signup</legend>
<form {{action 'save' on='submit'}}>
<ul>
<li>
<label>Name</label>
{{one-way-input type="text" class="user-name" value=name update=(action "nameDidChange")}}
</li>
<li>
<label>Email</label>
{{one-way-input type="text" class="email" value=email update=(action "emailDidChange")}}
</li>
<li>
<label>Password</label>
{{one-way-input type="password" class="password" value=password update=(action "passwordDidChange")}}
</li>
<li>
<label>Password (confirm)</label>
{{one-way-input type="password" class="password-verify" value=passwordConfirm update=(action "passwordConfirmDidChange")}}
</li>
<li>
<button type="submit" class="button-green smaller save-user">
Submit
</button>
</li>
</ul>
</form>
</fieldset>
</div>
</div>
{{/if}}

View file

@ -1,15 +1,10 @@
import Ember from 'ember'; import Ember from 'ember';
import UnauthenticatedRouteMixin from 'ember-simple-auth/mixins/unauthenticated-route-mixin'; import UnauthenticatedRouteMixin from 'ember-simple-auth/mixins/unauthenticated-route-mixin';
export default Ember.Route.extend(UnauthenticatedRouteMixin, { const { Route } = Ember;
export default Route.extend(UnauthenticatedRouteMixin, {
model: function() { model: function() {
return Ember.RSVP.hash({ return this.store.createRecord('user');
user: this.store.createRecord('user'),
});
}, },
setupController: function(controller, model) {
controller.setProperties(model);
},
}); });

View file

@ -1,32 +1,6 @@
<div class="grid-1"> {{
<div class="span-1"> users/new/new-user-form
<fieldset> user=model
<legend>New User Signup</legend> isLoading=isLoading
<form {{action 'save' on='submit'}}> on-save=(action "save")
<ul> }}
<li>
<label>Name</label>
{{input value=user.name}}
</li>
<li>
<label>Email</label>
{{input value=user.email}}
</li>
<li>
<label>Password</label>
{{input type="password" value=user.password}}
</li>
<li>
<label>Password (confirm)</label>
{{input type="password" value=passwordConfirm}}
</li>
<li>
<button type="submit" class="button-green smaller">
Submit
</button>
</li>
</ul>
</form>
</fieldset>
</div>
</div>

View file

@ -1,31 +1,26 @@
import Ember from 'ember'; import Ember from 'ember';
import ajaxRequest from '../../../../utils/ajax-request'; import ajaxRequest from '../../../../utils/ajax-request';
export default Ember.Route.extend({ const { Route, inject: { service } } = Ember;
session: Ember.inject.service('session'),
apiURL: function() { export default Route.extend({
return this.get('globals.apiURL'); session: service(),
}.property(), globals: service(),
genus: function() {
return this.get('globals.genus');
}.property(),
model: function(params) { model: function(params) {
let url = `${this.get('apiURL')}/api/${this.get('genus')}/users/verify/${params.nonce}`; const url = `${this.get('globals.apiURL')}/api/${this.get('globals.genus')}/users/verify/${params.nonce}`;
return ajaxRequest(url, {}, this.get('session')); return ajaxRequest(url, {}, this.get('session'));
}, },
afterModel: function(model/*, transition*/) { afterModel: function(model/*, transition*/) {
this.get('flashMessages').success(model.msg); this.get('flashMessages').success(model.get('msg'));
this.transitionTo('login'); this.transitionTo('login');
}, },
actions: { actions: {
error: function(error/*, transition*/) { error: function(error/*, transition*/) {
let err = Ember.$.parseJSON(error.responseText); const err = Ember.$.parseJSON(error.responseText);
this.get('flashMessages').error(err.error); this.get('flashMessages').error(err.error);
this.transitionTo('login'); this.transitionTo('login');
} }

View file

@ -1 +0,0 @@
{{outlet}}

View file

@ -1,15 +1,18 @@
import Ember from 'ember'; import Ember from 'ember';
import ajaxRequest from '../../../utils/ajax-request'; import ajaxRequest from '../../../utils/ajax-request';
export default Ember.Controller.extend({ const { Controller, inject: { service } } = Ember;
session: Ember.inject.service('session'),
export default Controller.extend({
session: service(),
globals: service(),
actions: { actions: {
save: function() { submit: function(email) {
let url = `${this.get('globals.apiURL')}/api/${this.get('globals.genus')}/users/lockout`; const url = `${this.get('globals.apiURL')}/api/${this.get('globals.genus')}/users/lockout`;
let options = { const options = {
method: 'POST', method: 'POST',
data: { email: this.get('email') }, data: { email: email },
}; };
ajaxRequest(url, options, this.get('session')); ajaxRequest(url, options, this.get('session'));
this.transitionToRoute('login'); this.transitionToRoute('login');

View file

@ -0,0 +1,18 @@
import Ember from 'ember';
const { Component } = Ember;
export default Component.extend({
email: null,
"on-submit": null,
actions: {
save: function() {
return this.attrs["on-submit"](this.get('email'));
},
emailDidChange: function(value) {
this.set('email', value);
},
}
});

View file

@ -0,0 +1,18 @@
<div class="grid-1">
<div class="span-1">
<fieldset>
<legend>Account Lockout/Password Reset</legend>
<form {{action 'save' on='submit'}}>
<ul>
<li>
<label>Email</label>
{{one-way-input type="text" class="email" value=email update=(action "emailDidChange")}}
</li>
<li>
<button type="submit" class="button-green smaller">Submit</button>
</li>
</ul>
</form>
</fieldset>
</div>
</div>

View file

@ -1,8 +0,0 @@
import Ember from 'ember';
export default Ember.Route.extend({
deactivate: function() {
this.controller.set('email', null);
},
});

View file

@ -1,18 +1,4 @@
<div class="grid-1"> {{
<div class="span-1"> users/requestlockouthelp/lockout-form
<fieldset> on-submit=(action "submit")
<legend>Account Lockout/Password Reset</legend> }}
<form {{action 'save' on='submit'}}>
<ul>
<li>
<label>Email</label>
{{input value=email}}
</li>
<li>
<button type="submit" class="button-green smaller">Submit</button>
</li>
</ul>
</form>
</fieldset>
</div>
</div>

View file

@ -2,19 +2,20 @@ import Ember from 'ember';
import DS from 'ember-data'; import DS from 'ember-data';
import parseBase64 from '../utils/parse-base64'; import parseBase64 from '../utils/parse-base64';
const { service } = Ember.inject; const { Service, computed, isEmpty, inject: { service } } = Ember;
const { PromiseObject } = DS;
export default Ember.Service.extend({ export default Service.extend({
session: service('session'), session: service('session'),
store: service(), store: service(),
account: Ember.computed('session.data.authenticated.access_token', function() { account: computed('session.data.authenticated.access_token', function() {
const token = this.get('session.data.authenticated.access_token'); const token = this.get('session.data.authenticated.access_token');
const claims = parseBase64(token); const claims = parseBase64(token);
const id = claims['sub']; const id = claims['sub'];
if (!Ember.isEmpty(id)) { if (!isEmpty(id)) {
return DS.PromiseObject.create({ return PromiseObject.create({
promise: this.get('store').findRecord('user', id), promise: this.get('store').findRecord('user', id),
}); });
} }

View file

@ -0,0 +1,77 @@
import Ember from 'ember';
import { module, test } from 'qunit';
import startApp from '../helpers/start-app';
import { invalidateSession, authenticateSession } from '../helpers/ember-simple-auth';
module('Acceptance | users', {
beforeEach: function() {
this.application = startApp();
authenticateSession(this.application, {
access_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJiYWN0ZGIiLCJzdWIiOiIxIiwiZXhwIjoxNDQ2NTAyMjI2LCJpYXQiOjE0NDY0OTg2MjZ9.vIjKHAsp2TkCV505EbtCo2xQT-2oQkB-Nv5y0b6E7Mg"
});
server.create('users', { role: 'A', canEdit: true });
},
afterEach: function() {
Ember.run(this.application, 'destroy');
}
});
test('visiting /users', function(assert) {
const users = server.createList('users', 19); // We already created one user in beforeEach
visit('/users');
andThen(function() {
assert.equal(currentURL(), '/users');
assert.equal(find(".flakes-table > tbody > tr").length, users.length + 1);
assert.equal(find("#total-users").text(), "Total users: 20");
});
});
test('visiting /users/:id', function(assert) {
const user = server.create('users');
visit(`/users/${user.id}`);
andThen(function() {
assert.equal(currentURL(), `/users/${user.id}`);
assert.equal(find(".flakes-information-box > legend").text().trim(), user.name);
});
});
test('editing /users/:id/edit', function(assert) {
const user = server.create('users', { 'canEdit': true });
visit(`/users/${user.id}/edit`);
andThen(function() {
assert.equal(currentURL(), `/users/${user.id}/edit`);
fillIn('.user-name', 'Revised User Name');
click('.save-user');
andThen(function() {
assert.equal(currentURL(), `/users/${user.id}`);
assert.equal(find(".flakes-information-box > legend").text().trim(), 'Revised User Name');
});
});
});
test('creating /users/new', function(assert) {
invalidateSession(this.application);
visit(`/users/new`);
andThen(function() {
assert.equal(currentURL(), `/users/new`);
fillIn('.user-name', 'New User Name');
fillIn('.email', 'example@example.com');
fillIn('.password', 'Password1');
fillIn('.password-verify', 'Password1');
click('.save-user');
andThen(function() {
assert.equal(currentURL(), `/login`);
assert.equal(find(".flakes-message").text().trim(), `✖ You have successfully signed up.
Please check your email for further instructions.`);
});
});
});