Implemented first pass of member details screen

This commit is contained in:
Kevin Ansfield 2019-02-22 18:31:45 +07:00
parent ac321fa62a
commit 6ac2569f24
9 changed files with 169 additions and 4 deletions

View File

@ -16,6 +16,7 @@ export default Component.extend({
tagName: '',
member: null,
initialsClass: 'f6 fw3',
backgroundStyle: computed('member.name', function () {
let name = this.member.name;

View File

@ -0,0 +1,42 @@
import ModalComponent from 'ghost-admin/components/modal-base';
import {alias} from '@ember/object/computed';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
export default ModalComponent.extend({
notifications: service(),
member: alias('model.member'),
onSuccess: alias('model.onSuccess'),
actions: {
confirm() {
this.deleteMember.perform();
}
},
_success() {
// clear any previous error messages
this.notifications.closeAlerts('post.delete');
// trigger the success action
if (this.onSuccess) {
this.onSuccess();
}
},
_failure(error) {
this.notifications.showAPIError(error, {key: 'post.delete.failed'});
},
deleteMember: task(function* () {
try {
yield this.member.destroyRecord();
this._success();
} catch (e) {
this._failure(e);
} finally {
this.send('closeModal');
}
}).drop()
});

View File

@ -1,6 +1,22 @@
import Controller from '@ember/controller';
import {alias} from '@ember/object/computed';
import {inject as controller} from '@ember/controller';
import {inject as service} from '@ember/service';
export default Controller.extend({
member: alias('model')
members: controller(),
router: service(),
member: alias('model'),
actions: {
finaliseDeletion() {
// decrememnt the total member count manually so there's no flash
// when transitioning back to the members list
if (this.members.meta) {
this.members.decrementProperty('meta.pagination.total');
}
this.router.transitionTo('members');
}
}
});

View File

@ -1,3 +1,3 @@
<div class="flex items-center justify-center br-100" style={{this.backgroundStyle}} ...attributes>
<span class="block white f6 fw3">{{this.initials}}</span>
<span class="db white {{this.initialsClass}}">{{this.initials}}</span>
</div>

View File

@ -0,0 +1,15 @@
<header class="modal-header">
<h1>Are you sure you want to delete this member?</h1>
</header>
<a class="close" href="" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
<div class="modal-body">
<p>
You're about to delete "<strong>{{or member.name member.email}}</strong>". This is permanent! We warned you, k?
</p>
</div>
<div class="modal-footer">
<button {{action "closeModal"}} class="gh-btn"><span>Cancel</span></button>
{{gh-task-button "Delete" successText="Deleted" task=deleteMember class="gh-btn gh-btn-red gh-btn-icon"}}
</div>

View File

@ -8,6 +8,52 @@
</header>
<section class="view-container">
<div class="pt1">
<h2 class="f7 fw4 midgrey">Overview</h2>
</div>
<div class="flex flex-column mb10 ba br3 b--whitegrey">
<div class="flex flex-row pa5">
<GhMemberAvatar class="w20 h20 mr4" @initialsClass="f-subheadline" @member={{member}} />
<div class="flex flex-column justify-center">
<h3 class="ma0 pa0">{{member.name}}</h3>
<span class="db">
<a class="midlightgrey" href="mailto:{{member.email}}">
{{member.email}}
</a>
</span>
</div>
</div>
<div class="flex flex-row bt b--whitegrey bg-whitegrey-l2">
<div class="flex flex-column flex-grow-1 pa5 br b--whitegrey">
<span class="db ttu f8 midlightgrey">Member since</span>
<span class="db f5">{{moment-format member.createdAt "MMMM Do"}}</span>
<span class="db f8 midlightgrey">({{moment-to-now member.createdAt hideAffix=true}})</span>
</div>
<div class="flex flex-column flex-grow-1 pa5">
<span class="db ttu f8 midlightgrey">Current plan</span>
<span class="db f5">-</span>
<span class="db f8 midlightgrey"></span>
</div>
</div>
</div>
<h2 class="pb1 bb b--whitegrey f7 fw4 midgrey">Danger zone</h2>
<button
type="button"
class="gh-btn gh-btn-red gh-btn-icon"
{{action (toggle "showDeleteMemberModal" this)}}
data-test-button="delete-member"
>
<span>Delete member</span>
</button>
</section>
</section>
</section>
{{#if showDeleteMemberModal}}
{{gh-fullscreen-modal "delete-member"
model=(hash member=member onSuccess=(action "finaliseDeletion"))
close=(action (toggle "showDeleteMemberModal" this))
modifier="action wide"}}
{{/if}}

View File

@ -5,7 +5,14 @@
<section class="view-container h-100">
<div class="flex justify-between items-center pt1">
<h2 class="f7 fw4 midgrey">All members ({{this.meta.pagination.total}})</h2>
<h2 class="f7 fw4 midgrey">
All members
{{#if this.fetchMembers.lastSuccessful}}
({{this.meta.pagination.total}})
{{else}}
(...)
{{/if}}
</h2>
</div>
{{#if this.members}}

View File

@ -2,4 +2,18 @@ import {paginatedResponse} from '../utils';
export default function mockMembers(server) {
server.get('/members/', paginatedResponse('members'));
server.get('/members/:id/', function ({members}, {params}) {
let {id} = params;
let member = members.find(id);
return member || new Response(404, {}, {
errors: [{
errorType: 'NotFoundError',
message: 'Member not found.'
}]
});
});
server.del('/members/:id/');
}

View File

@ -0,0 +1,24 @@
import { expect } from 'chai';
import { describe, it } from 'mocha';
import { setupComponentTest } from 'ember-mocha';
import hbs from 'htmlbars-inline-precompile';
describe('Integration | Component | modal-delete-member', function() {
setupComponentTest('modal-delete-member', {
integration: true
});
it('renders', function() {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
// Template block usage:
// this.render(hbs`
// {{#modal-delete-member}}
// template content
// {{/modal-delete-member}}
// `);
this.render(hbs`{{modal-delete-member}}`);
expect(this.$()).to.have.length(1);
});
});