diff --git a/ghost/admin/app/components/gh-member-avatar.js b/ghost/admin/app/components/gh-member-avatar.js index 38ca3542f1..0f6458a416 100644 --- a/ghost/admin/app/components/gh-member-avatar.js +++ b/ghost/admin/app/components/gh-member-avatar.js @@ -16,6 +16,7 @@ export default Component.extend({ tagName: '', member: null, + initialsClass: 'f6 fw3', backgroundStyle: computed('member.name', function () { let name = this.member.name; diff --git a/ghost/admin/app/components/modal-delete-member.js b/ghost/admin/app/components/modal-delete-member.js new file mode 100644 index 0000000000..0a76e6e2a9 --- /dev/null +++ b/ghost/admin/app/components/modal-delete-member.js @@ -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() +}); diff --git a/ghost/admin/app/controllers/member.js b/ghost/admin/app/controllers/member.js index bf72a2cf5e..dc0b12c5e7 100644 --- a/ghost/admin/app/controllers/member.js +++ b/ghost/admin/app/controllers/member.js @@ -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'); + } + } }); diff --git a/ghost/admin/app/templates/components/gh-member-avatar.hbs b/ghost/admin/app/templates/components/gh-member-avatar.hbs index 3d7004da52..2967bda3b1 100644 --- a/ghost/admin/app/templates/components/gh-member-avatar.hbs +++ b/ghost/admin/app/templates/components/gh-member-avatar.hbs @@ -1,3 +1,3 @@
- {{this.initials}} + {{this.initials}}
\ No newline at end of file diff --git a/ghost/admin/app/templates/components/modal-delete-member.hbs b/ghost/admin/app/templates/components/modal-delete-member.hbs new file mode 100644 index 0000000000..4d0b80a79f --- /dev/null +++ b/ghost/admin/app/templates/components/modal-delete-member.hbs @@ -0,0 +1,15 @@ + +{{svg-jar "close"}} + + + + \ No newline at end of file diff --git a/ghost/admin/app/templates/member.hbs b/ghost/admin/app/templates/member.hbs index 3cfeb61ca0..4f1f1cb2b7 100644 --- a/ghost/admin/app/templates/member.hbs +++ b/ghost/admin/app/templates/member.hbs @@ -8,6 +8,52 @@
+
+

Overview

+
+
+
+ +
+

{{member.name}}

+ + + {{member.email}} + + +
+
+ +
+
+ Member since + {{moment-format member.createdAt "MMMM Do"}} + ({{moment-to-now member.createdAt hideAffix=true}}) +
+
+ Current plan + - + +
+
+
+ +

Danger zone

+
- \ No newline at end of file + + +{{#if showDeleteMemberModal}} + {{gh-fullscreen-modal "delete-member" + model=(hash member=member onSuccess=(action "finaliseDeletion")) + close=(action (toggle "showDeleteMemberModal" this)) + modifier="action wide"}} +{{/if}} \ No newline at end of file diff --git a/ghost/admin/app/templates/members.hbs b/ghost/admin/app/templates/members.hbs index d8a481ebb7..a45ec6f5c5 100644 --- a/ghost/admin/app/templates/members.hbs +++ b/ghost/admin/app/templates/members.hbs @@ -5,7 +5,14 @@
-

All members ({{this.meta.pagination.total}})

+

+ All members + {{#if this.fetchMembers.lastSuccessful}} + ({{this.meta.pagination.total}}) + {{else}} + (...) + {{/if}} +

{{#if this.members}} diff --git a/ghost/admin/mirage/config/members.js b/ghost/admin/mirage/config/members.js index b2e849fb90..949c5a340e 100644 --- a/ghost/admin/mirage/config/members.js +++ b/ghost/admin/mirage/config/members.js @@ -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/'); } diff --git a/ghost/admin/tests/integration/components/modal-delete-member-test.js b/ghost/admin/tests/integration/components/modal-delete-member-test.js new file mode 100644 index 0000000000..6b3926887c --- /dev/null +++ b/ghost/admin/tests/integration/components/modal-delete-member-test.js @@ -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); + }); +});