diff --git a/core/client/app/components/gh-nav-menu.js b/core/client/app/components/gh-nav-menu.js index 351a896533..b3098d167f 100644 --- a/core/client/app/components/gh-nav-menu.js +++ b/core/client/app/components/gh-nav-menu.js @@ -3,8 +3,7 @@ import Ember from 'ember'; const { Component, inject: {service}, - computed, - observer + computed } = Ember; export default Component.extend({ @@ -13,7 +12,6 @@ export default Component.extend({ classNameBindings: ['open'], open: false, - subscribersEnabled: false, navMenuIcon: computed('ghostPaths.subdir', function () { let url = `${this.get('ghostPaths.subdir')}/ghost/img/ghosticon.jpg`; @@ -26,22 +24,6 @@ export default Component.extend({ ghostPaths: service(), feature: service(), - // TODO: the features service should offer some way to propogate raw values - // rather than promises so we don't need to jump through the hoops below - didInsertElement() { - this.updateSubscribersEnabled(); - }, - - updateFeatures: observer('feature.labs.subscribers', function () { - this.updateSubscribersEnabled(); - }), - - updateSubscribersEnabled() { - this.get('feature.subscribers').then((enabled) => { - this.set('subscribersEnabled', enabled); - }); - }, - mouseEnter() { this.sendAction('onMouseEnter'); }, diff --git a/core/client/app/components/modals/delete-subscriber.js b/core/client/app/components/modals/delete-subscriber.js new file mode 100644 index 0000000000..ec8b58c765 --- /dev/null +++ b/core/client/app/components/modals/delete-subscriber.js @@ -0,0 +1,23 @@ +import Ember from 'ember'; +import ModalComponent from 'ghost/components/modals/base'; +import {invokeAction} from 'ember-invoke-action'; + +const {computed} = Ember; +const {alias} = computed; + +export default ModalComponent.extend({ + + submitting: false, + + subscriber: alias('model'), + + actions: { + confirm() { + this.set('submitting', true); + + invokeAction(this, 'confirm').finally(() => { + this.set('submitting', false); + }); + } + } +}); diff --git a/core/client/app/components/modals/new-subscriber.js b/core/client/app/components/modals/new-subscriber.js index 9f74e43214..01e50958d7 100644 --- a/core/client/app/components/modals/new-subscriber.js +++ b/core/client/app/components/modals/new-subscriber.js @@ -16,6 +16,12 @@ export default ModalComponent.extend({ confirmAction().then(() => { this.send('closeModal'); + }).catch((errors) => { + let [error] = errors; + if (error && error.match(/email/)) { + this.get('model.errors').add('email', error); + this.get('model.hasValidated').pushObject('email'); + } }).finally(() => { if (!this.get('isDestroying') && !this.get('isDestroyed')) { this.set('submitting', false); diff --git a/core/client/app/controllers/subscribers.js b/core/client/app/controllers/subscribers.js index ef7129bd06..1c39bcf0f2 100644 --- a/core/client/app/controllers/subscribers.js +++ b/core/client/app/controllers/subscribers.js @@ -20,6 +20,7 @@ export default Ember.Controller.extend(PaginationMixin, { total: 0, table: null, + subscriberToDelete: null, session: service(), @@ -66,6 +67,11 @@ export default Ember.Controller.extend(PaginationMixin, { valuePath: 'status', sorted: order === 'status', ascending: direction === 'asc' + }, { + label: '', + sortable: false, + cellComponent: 'gh-subscribers-table-delete-cell', + align: 'right' }]; }), @@ -84,8 +90,6 @@ export default Ember.Controller.extend(PaginationMixin, { loadFirstPage() { let table = this.get('table'); - console.log('loadFirstPage', this.get('paginationSettings')); - return this._super(...arguments).then((results) => { table.addRows(results); return results; @@ -119,6 +123,24 @@ export default Ember.Controller.extend(PaginationMixin, { this.incrementProperty('total'); }, + deleteSubscriber(subscriber) { + this.set('subscriberToDelete', subscriber); + }, + + confirmDeleteSubscriber() { + let subscriber = this.get('subscriberToDelete'); + + return subscriber.destroyRecord().then(() => { + this.set('subscriberToDelete', null); + this.get('table').removeRow(subscriber); + this.decrementProperty('total'); + }); + }, + + cancelDeleteSubscriber() { + this.set('subscriberToDelete', null); + }, + reset() { this.get('table').setRows([]); this.send('loadFirstPage'); diff --git a/core/client/app/mirage/config.js b/core/client/app/mirage/config.js index dfd835041f..20f28cdb2d 100644 --- a/core/client/app/mirage/config.js +++ b/core/client/app/mirage/config.js @@ -55,16 +55,26 @@ function mockSubscribers(server) { server.post('/subscribers/', function (db, request) { let [attrs] = JSON.parse(request.requestBody).subscribers; - let subscriber; + let [subscriber] = db.subscribers.where({email: attrs.email}); - attrs.created_at = new Date(); - attrs.created_by = 0; + if (subscriber) { + return new Mirage.Response(422, {}, { + errors: [{ + errorType: 'DataImportError', + message: 'duplicate email', + property: 'email' + }] + }); + } else { + attrs.created_at = new Date(); + attrs.created_by = 0; - subscriber = db.subscribers.insert(attrs); + subscriber = db.subscribers.insert(attrs); - return { - subscriber - }; + return { + subscriber + }; + } }); server.put('/subscribers/:id/', function (db, request) { diff --git a/core/client/app/styles/layouts/subscribers.css b/core/client/app/styles/layouts/subscribers.css index 8390fd6159..4cb4efc1ce 100644 --- a/core/client/app/styles/layouts/subscribers.css +++ b/core/client/app/styles/layouts/subscribers.css @@ -22,6 +22,20 @@ margin: 0; } +.subscribers-table table .btn { + visibility: hidden; +} + +.subscribers-table table tr:hover .btn { + visibility: visible; +} + +.subscribers-table tbody td:last-of-type { + padding-top: 0; + padding-bottom: 0; + padding-left: 0; +} + /* Sidebar (right pane) /* ---------------------------------------------------------- */ diff --git a/core/client/app/styles/patterns/buttons.css b/core/client/app/styles/patterns/buttons.css index 14852318b9..dadefc5274 100644 --- a/core/client/app/styles/patterns/buttons.css +++ b/core/client/app/styles/patterns/buttons.css @@ -65,6 +65,13 @@ fieldset[disabled] .btn { vertical-align: middle; } +.btn-hover-green:hover, +.btn-hover-green:active, +.btn-hover-green:focus { + border-color: var(--green); + color: color(var(--green) lightness(-10%)); +} + /* Blue button /* ---------------------------------------------------------- */ diff --git a/core/client/app/styles/patterns/icons.css b/core/client/app/styles/patterns/icons.css index 192043cbfe..4f398af7a0 100755 --- a/core/client/app/styles/patterns/icons.css +++ b/core/client/app/styles/patterns/icons.css @@ -87,9 +87,15 @@ .icon-idea:before { content: "\e00e"; } -.icon-arrow:before { +.icon-arrow:before, +.icon-ascending:before, +.icon-descending:before { content: "\e00f"; } +.icon-ascending:before { + display: inline-block; + transform: rotate(180deg); +} .icon-pen:before { content: "\e010"; } diff --git a/core/client/app/styles/patterns/tables.css b/core/client/app/styles/patterns/tables.css index 4861616603..ea0b95e844 100644 --- a/core/client/app/styles/patterns/tables.css +++ b/core/client/app/styles/patterns/tables.css @@ -63,17 +63,16 @@ table td, /* Ember Light Table /* ---------------------------------------------------------- */ +.ember-light-table th { + white-space: nowrap; +} + .ember-light-table .lt-column .lt-sort-icon { float: none; - margin-left: 0.5em; -} - -.lt-sort-icon.icon-ascending:before { - content: "▲"; - font-size: 0.7em; + margin-left: 0.3rem; } +.lt-sort-icon.icon-ascending:before, .lt-sort-icon.icon-descending:before { - content: "▼"; - font-size: 0.5em; + font-size: 0.6em; } diff --git a/core/client/app/templates/components/gh-nav-menu.hbs b/core/client/app/templates/components/gh-nav-menu.hbs index 9961c61822..a789ef8bd1 100644 --- a/core/client/app/templates/components/gh-nav-menu.hbs +++ b/core/client/app/templates/components/gh-nav-menu.hbs @@ -25,7 +25,7 @@ {{!