Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add driver calendar self-editing #72

Merged
merged 12 commits into from
Jan 15, 2018
30 changes: 30 additions & 0 deletions app/adapters/person.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import ApplicationAdapter from './application';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';

export default ApplicationAdapter.extend({
router: service(),

onCalendar: computed('router.currentRouteName', function() {
return this.get('router.currentRouteName') === 'calendar';
}),

headers: computed(function() {
if (this.get('onCalendar')) {
const personToken = localStorage.getItem('person-token');
return {
'Authorization': `Person Bearer ${personToken}`
};
} else {
return {};
}
}).volatile(),

urlForUpdateRecord(id, modelName, snapshot) {
if (this.get('onCalendar')) {
return this._super('me', modelName, snapshot);
} else {
return this._super(...arguments);
}
}
});
16 changes: 15 additions & 1 deletion app/controllers/calendar.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import Controller from '@ember/controller';

import { alias } from '@ember/object/computed';
import { inject as service } from '@ember/service';

export default Controller.extend({
toasts: service(),

month: alias('model.month'),
slots: alias('model.slots'),
person: alias('model.person')
person: alias('model.person'),

actions: {
savePerson() {
this.get('person').save().then(() => {
this.get('toasts').show('Saved your details');
this.set('showPerson', false);
}).catch(() => {
this.get('toasts').show('Couldn’t save your details');
})
}
}
});
10 changes: 10 additions & 0 deletions app/styles/calendar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,13 @@
.admin-calendar {
display: flex;
}

.person-card header {
display: flex;
justify-content: space-between;

.person-session {
display: flex;
align-self: center;
}
}
45 changes: 43 additions & 2 deletions app/templates/calendar.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,48 @@
{{#power-calendar center=month daysComponent='calendar-days' as |calendar|}}
{{#paper-card as |card|}}
{{#paper-card class='person-card' as |card|}}
{{#card.content}}
<div class='person-session'>You are logged in as {{person.email}}</div>
<header>
<div class='person-session'>You are logged in as {{person.email}}</div>
{{#if showPerson}}
{{#paper-button class='submit' primary=person.hasDirtyAttributes raised=person.hasDirtyAttributes onClick='savePerson'}}Save details{{/paper-button}}
{{else}}
{{paper-button class='toggle' label='Edit details' onClick=(action (mut showPerson) (not showPerson))}}
{{/if}}
</header>
{{#if showPerson}}
{{#paper-form onSubmit=save as |form|}}
{{#paper-radio-group
groupValue=(readonly person.medium)
onChange=(action (mut person.medium)) as |group|}}
<div class='layout-row'>
<div class='layout-column flex-50'>
{{form.input class='name' label='Name' value=person.name onChange=(action (mut person.name)) errors=person.validationErrors.name isTouched=person.validationErrors.name.length}}
</div>
<div class='layout-column flex-50'>
{{#paper-switch value=person.active onChange=(action (mut person.active) (not person.active))}}Available for rides{{/paper-switch}}
</div>
</div>
<div class='layout-row'>
<div class='layout-column flex-50'>
<div class='layout-row text-radio mobile'>
{{form.input type='mobile' label='Mobile' value=person.mobile onChange=(action (mut person.mobile)) errors=person.validationErrors.mobile isTouched=person.validationErrors.mobile.length}}
{{#group.radio value='mobile'}}preferred{{/group.radio}}
</div>
<div class='layout-row text-radio landline'>
{{form.input type='mobile' label='Landline' value=person.landline onChange=(action (mut person.landline)) errors=person.validationErrors.landline isTouched=person.validationErrors.landline.length}}
{{#group.radio value='landline'}}preferred{{/group.radio}}
</div>
</div>
<div class='layout-column email flex-50'>
<div class='layout-row text-radio'>
{{form.input type='email' label='Email' disabled=true value=person.email onChange=(action (mut person.email)) errors=person.validationErrors.email isTouched=person.validationErrors.email.length}}
{{#group.radio value='email'}}preferred{{/group.radio}}
</div>
</div>
</div>
{{/paper-radio-group}}
{{/paper-form}}
{{/if}}
{{/card.content}}
{{/paper-card}}

Expand Down
26 changes: 26 additions & 0 deletions mirage/config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import config from 'prison-rideshare-ui/config/environment';
import Mirage from 'ember-cli-mirage';
import { isEmpty } from '@ember/utils';

export default function() {
this.passthrough('/write-coverage');
Expand Down Expand Up @@ -33,6 +34,31 @@ export default function() {
this.post('/people');
this.patch('/people/:id');

this.patch('/people/me', function({ people }, request) {
if (request.requestHeaders.Authorization.startsWith('Person Bearer')) {
const [, , accessToken] = request.requestHeaders.Authorization.split(' ');
const person = people.findBy({accessToken});

if (person) {
const attrs = this.normalizedRequestAttrs();

if (isEmpty(attrs.name)) {
return new Mirage.Response(422, {}, {
errors: [{
'source': {
'pointer': '/data/attributes/name'
},
'detail': 'Name can\'t be blank'
}]
});
} else {
return person.update(this.normalizedRequestAttrs());
}
}
}
return new Mirage.Response(401, {}, {});
});

this.get('/debts');
this.delete('/debts/:id');

Expand Down
86 changes: 86 additions & 0 deletions tests/acceptance/calendar-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ moduleForAcceptance('Acceptance | calendar', {
const person = server.create('person', {
name: 'Jortle Tortle',
email: '[email protected]',
mobile: '5551313',
medium: 'mobile',
active: true,
magicToken: 'MAGIC??TOKEN',
accessToken: 'XXX'
});
Expand Down Expand Up @@ -180,6 +183,89 @@ test('visiting with a magic token that doesn’t resolve to a person shows an er
});
});

test('the person can edit their details', function(assert) {
page.visit({ month: '2017-12', token: 'MAGIC??TOKEN' });

andThen(() => {
assert.ok(page.person.name.isHidden, 'expected the name field to be hidden by default');
});

page.person.toggle.click();

andThen(() => {
assert.ok(page.person.name.isVisible, 'expected the name field to have become visible');
assert.equal(page.person.name.field.value, 'Jortle Tortle');

assert.ok(page.person.activeSwitch.enabled, 'expected the active switch to be on');

assert.ok(page.person.email.field.isDisabled, 'expected the email field to be disabled');
assert.equal(page.person.email.field.value, '[email protected]');

assert.ok(page.person.mobile.desiredMedium, 'expected mobile to be the desired medium');

assert.notOk(page.person.submitButton.isHighlighted, 'expected the submit button to not be highlighted before anything has changed');
});

page.person.name.field.fillIn('Jortleby');
page.person.activeSwitch.click();
page.person.mobile.field.fillIn('1234');
page.person.email.desiredMedium.click();

andThen(() => {
assert.ok(page.person.submitButton.isHighlighted, 'expected the submit button to be highlighted when the record is dirty');
});

page.person.submitButton.click();

andThen(() => {
const [person] = server.db.people;

assert.equal(person.name, 'Jortleby', 'expected the name to have changed on the server');
assert.notOk(person.active, 'expected the person to be inactive on the server');
assert.equal(person.mobile, '1234', 'expected the mobile number to have changed on the server');
assert.equal(person.medium, 'email', 'expected the medium to have changed on the server');

assert.equal(shared.toast.text, 'Saved your details');
assert.ok(page.person.name.isHidden, 'expected the form to be hidden again');
});
});

test('shows detail validation errors', function(assert) {
page.visit({ month: '2017-12', token: 'MAGIC??TOKEN' });

page.person.toggle.click();
page.person.name.field.fillIn('');
page.person.submitButton.click();

andThen(() => {
// FIXME validation-specific error text?
assert.equal(shared.toast.text, 'Couldn’t save your details');
assert.equal(page.person.name.error.text, 'Name can\'t be blank');
});
});

test('handles an error saving details', function(assert) {
page.visit({ month: '2017-12', token: 'MAGIC??TOKEN' });

server.patch('/people/me', function() {
return new Mirage.Response(401, {}, {
errors: [{
status: 401,
title: 'Unauthorized'
}]
});
});

page.person.toggle.click();
page.person.name.field.fillIn('Jartleby');
page.person.submitButton.click();

andThen(() => {
assert.equal(shared.toast.text, 'Couldn’t save your details');
assert.ok(page.person.name.isVisible, 'expected the form to still be visible');
});
});

test('the path controls the month', function(assert) {
page.visit({ month: '2018-01', token: 'MAGIC??TOKEN' });

Expand Down
62 changes: 62 additions & 0 deletions tests/pages/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,68 @@ export default create({
adminVisit: visitable('/admin-calendar/:month'),

personSession: text('.person-session'),

person: {
scope: '.person-card',

toggle: {
scope: '.toggle'
},

name: {
scope: '.name',
field: {
scope: 'input'
},
error: {
scope: '.paper-input-error'
}
},

activeSwitch: {
scope: '.paper-switch',
enabled: hasClass('md-checked'),
click: clickable('.md-thumb')
},

email: {
scope: '.email',
field: {
scope: 'input',
disabledAttribute: attribute('disabled'),
isDisabled: getter(function() {
return this.disabledAttribute === 'disabled';
})
},
desiredMedium: {
scope: 'md-radio-button',
isChecked: hasClass('md-checked')
},
error: {
scope: '.paper-input-error'
}
},

mobile: {
scope: '.mobile',
field: {
scope: 'input'
},
desiredMedium: {
scope: 'md-radio-button',
isChecked: hasClass('md-checked')
},
error: {
scope: '.paper-input-error'
}
},

submitButton: {
scope: 'button.submit',
isHighlighted: hasClass('md-primary')
},
},

month: text('.ember-power-calendar-nav-title'),

nextMonth: {
Expand Down