diff --git a/ghost/admin/app/components/gh-feature-flag.js b/ghost/admin/app/components/gh-feature-flag.js index 820bd4a872..7aac678593 100644 --- a/ghost/admin/app/components/gh-feature-flag.js +++ b/ghost/admin/app/components/gh-feature-flag.js @@ -20,9 +20,6 @@ const FeatureFlagComponent = Component.extend({ return this._flagValue; }, set(key, value) { - if (this.flag === 'members' && value === true) { - this.set(`feature.subscribers`, false); - } return this.set(`feature.${this.flag}`, value); } }), diff --git a/ghost/admin/app/components/gh-member-avatar.js b/ghost/admin/app/components/gh-member-avatar.js index 78014eed14..0de0543ab2 100644 --- a/ghost/admin/app/components/gh-member-avatar.js +++ b/ghost/admin/app/components/gh-member-avatar.js @@ -17,7 +17,7 @@ export default Component.extend({ member: null, initialsClass: computed('sizeClass', function () { - return this.sizeClass || 'f5 fw4 lh-zero'; + return this.sizeClass || 'gh-member-list-avatar'; }), backgroundStyle: computed('member.{name,email}', function () { diff --git a/ghost/admin/app/components/gh-member-settings-form.js b/ghost/admin/app/components/gh-member-settings-form.js index fb6e69dcca..6b27fcc71b 100644 --- a/ghost/admin/app/components/gh-member-settings-form.js +++ b/ghost/admin/app/components/gh-member-settings-form.js @@ -29,6 +29,7 @@ export default Component.extend({ status: subscription.status, startDate: subscription.start_date ? moment(subscription.start_date).format('MMM DD YYYY') : '-', plan: subscription.plan, + dollarAmount: parseInt(subscription.plan.amount) ? (subscription.plan.amount / 100) : 0, validUntil: subscription.current_period_end ? moment(subscription.current_period_end).format('MMM DD YYYY') : '-' }; }).reverse(); diff --git a/ghost/admin/app/components/gh-members-list-item.js b/ghost/admin/app/components/gh-members-list-item.js index d72f5f4ad9..ce6a831d7f 100644 --- a/ghost/admin/app/components/gh-members-list-item.js +++ b/ghost/admin/app/components/gh-members-list-item.js @@ -10,16 +10,17 @@ export default Component.extend({ router: service(), tagName: 'li', - classNames: ['gh-flex-list-row'], + classNames: ['gh-list-row'], active: false, id: alias('member.id'), email: alias('member.email'), name: alias('member.name'), - subscribedAt: computed('member.createdAt', function () { - let memberSince = moment(this.member.createdAt).from(moment()); - let createdDate = moment(this.member.createdAt).format('MMM DD, YYYY'); - return `${createdDate} (${memberSince})`; + memberSince: computed('member.createdAt', function () { + return moment(this.member.createdAt).from(moment()); + }), + createdDate: computed('member.createdAt', function () { + return moment(this.member.createdAt).format('MMM DD, YYYY'); }) }); diff --git a/ghost/admin/app/components/gh-search-input.js b/ghost/admin/app/components/gh-search-input.js index ea18512ac9..3f595029f0 100644 --- a/ghost/admin/app/components/gh-search-input.js +++ b/ghost/admin/app/components/gh-search-input.js @@ -161,7 +161,7 @@ export default Component.extend({ _loadPosts() { let store = this.store; let postsUrl = `${store.adapterFor('post').urlForQuery({}, 'post')}/`; - let postsQuery = {fields: 'id,title,page', limit: 'all', status: 'all'}; + let postsQuery = {fields: 'id,title,page', limit: 'all'}; let content = this.content; return this.ajax.request(postsUrl, {data: postsQuery}).then((posts) => { @@ -178,7 +178,7 @@ export default Component.extend({ _loadPages() { let store = this.store; let pagesUrl = `${store.adapterFor('page').urlForQuery({}, 'page')}/`; - let pagesQuery = {fields: 'id,title,page', limit: 'all', status: 'all'}; + let pagesQuery = {fields: 'id,title,page', limit: 'all'}; let content = this.content; return this.ajax.request(pagesUrl, {data: pagesQuery}).then((pages) => { diff --git a/ghost/admin/app/components/modal-delete-subscriber.js b/ghost/admin/app/components/modal-delete-subscriber.js deleted file mode 100644 index 25e623d703..0000000000 --- a/ghost/admin/app/components/modal-delete-subscriber.js +++ /dev/null @@ -1,20 +0,0 @@ -import ModalComponent from 'ghost-admin/components/modal-base'; -import {alias} from '@ember/object/computed'; -import {task} from 'ember-concurrency'; - -export default ModalComponent.extend({ - // Allowed actions - confirm: () => {}, - - subscriber: alias('model'), - - actions: { - confirm() { - this.deleteSubscriber.perform(); - } - }, - - deleteSubscriber: task(function* () { - yield this.confirm(); - }).drop() -}); diff --git a/ghost/admin/app/components/modal-import-subscribers.js b/ghost/admin/app/components/modal-import-subscribers.js deleted file mode 100644 index b0e905bc5c..0000000000 --- a/ghost/admin/app/components/modal-import-subscribers.js +++ /dev/null @@ -1,43 +0,0 @@ -import ModalComponent from 'ghost-admin/components/modal-base'; -import ghostPaths from 'ghost-admin/utils/ghost-paths'; -import {computed} from '@ember/object'; - -export default ModalComponent.extend({ - labelText: 'Select or drag-and-drop a CSV File', - - response: null, - closeDisabled: false, - - // Allowed actions - confirm: () => {}, - - uploadUrl: computed(function () { - return `${ghostPaths().apiRoot}/subscribers/csv/`; - }), - - actions: { - uploadStarted() { - this.set('closeDisabled', true); - }, - - uploadFinished() { - this.set('closeDisabled', false); - }, - - uploadSuccess(response) { - this.set('response', response.meta.stats); - // invoke the passed in confirm action - this.confirm(); - }, - - confirm() { - // noop - we don't want the enter key doing anything - }, - - closeModal() { - if (!this.closeDisabled) { - this._super(...arguments); - } - } - } -}); diff --git a/ghost/admin/app/components/modal-new-subscriber.js b/ghost/admin/app/components/modal-new-subscriber.js deleted file mode 100644 index 03b8fdd02f..0000000000 --- a/ghost/admin/app/components/modal-new-subscriber.js +++ /dev/null @@ -1,47 +0,0 @@ -import ModalComponent from 'ghost-admin/components/modal-base'; -import {alias} from '@ember/object/computed'; -import {A as emberA} from '@ember/array'; -import {isInvalidError} from 'ember-ajax/errors'; -import {task} from 'ember-concurrency'; - -export default ModalComponent.extend({ - - subscriber: alias('model'), - - actions: { - updateEmail(newEmail) { - this.set('subscriber.email', newEmail); - this.set('subscriber.hasValidated', emberA()); - this.get('subscriber.errors').clear(); - }, - - confirm() { - this.addSubscriber.perform(); - } - }, - - addSubscriber: task(function* () { - try { - yield this.confirm(); - this.send('closeModal'); - } catch (error) { - // TODO: server-side validation errors should be serialized - // properly so that errors are added to model.errors automatically - if (error && isInvalidError(error)) { - let [firstError] = error.payload.errors; - let {context} = firstError; - - if (context && context.match(/email/i)) { - this.get('subscriber.errors').add('email', context); - this.get('subscriber.hasValidated').pushObject('email'); - return; - } - } - - // route action so it should bubble up to the global error handler - if (error) { - throw error; - } - } - }).drop() -}); diff --git a/ghost/admin/app/controllers/member.js b/ghost/admin/app/controllers/member.js index 599faae44b..e96a080ac9 100644 --- a/ghost/admin/app/controllers/member.js +++ b/ghost/admin/app/controllers/member.js @@ -31,8 +31,8 @@ export default Controller.extend({ 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'); + if (this.members.memberCount) { + this.members.decrementProperty('memberCount'); } this.router.transitionTo('members'); }, diff --git a/ghost/admin/app/controllers/members.js b/ghost/admin/app/controllers/members.js index a664095599..d05fa22415 100644 --- a/ghost/admin/app/controllers/members.js +++ b/ghost/admin/app/controllers/members.js @@ -9,7 +9,7 @@ import {task} from 'ember-concurrency'; export default Controller.extend({ store: service(), - meta: null, + memberCount: null, members: null, searchText: '', init() { @@ -54,25 +54,23 @@ export default Controller.extend({ fetchMembers: task(function* () { let newFetchDate = new Date(); - let results; if (this._hasFetchedAll) { // fetch any records modified since last fetch - results = yield this.store.query('member', { + yield this.store.query('member', { limit: 'all', filter: `updated_at:>='${moment.utc(this._lastFetchDate).format('YYYY-MM-DD HH:mm:ss')}'`, order: 'created_at desc' }); } else { // fetch all records - results = yield this.store.query('member', { + yield this.store.query('member', { limit: 'all', order: 'created_at desc' }); this._hasFetchedAll = true; - this.set('meta', results.meta); } - + this.set('memberCount', this.store.peekAll('member').length); this._lastFetchDate = newFetchDate; }) }); diff --git a/ghost/admin/app/controllers/subscribers.js b/ghost/admin/app/controllers/subscribers.js deleted file mode 100644 index 2b75397978..0000000000 --- a/ghost/admin/app/controllers/subscribers.js +++ /dev/null @@ -1,116 +0,0 @@ -import $ from 'jquery'; -import Controller from '@ember/controller'; -import ghostPaths from 'ghost-admin/utils/ghost-paths'; -import moment from 'moment'; -import {computed} from '@ember/object'; -import {inject as service} from '@ember/service'; -import {task} from 'ember-concurrency'; - -const orderMap = { - email: 'email', - created_at: 'createdAtUTC', - status: 'status' -}; - -/* eslint-disable ghost/ember/alias-model-in-controller */ -export default Controller.extend({ - session: service(), - - queryParams: ['order', 'direction'], - order: 'created_at', - direction: 'desc', - - subscribers: null, - subscriberToDelete: null, - - init() { - this._super(...arguments); - this.set('subscribers', this.store.peekAll('subscriber')); - }, - - filteredSubscribers: computed('subscribers.@each.{email,createdAtUTC}', function () { - return this.subscribers.toArray().filter((subscriber) => { - return !subscriber.isNew && !subscriber.isDeleted; - }); - }), - - sortedSubscribers: computed('order', 'direction', 'subscribers.@each.{email,createdAtUTC,status}', function () { - let {filteredSubscribers, order, direction} = this; - - let sorted = filteredSubscribers.sort((a, b) => { - let values = [a.get(orderMap[order]), b.get(orderMap[order])]; - - if (direction === 'desc') { - values = values.reverse(); - } - - if (typeof values[0] === 'string') { - return values[0].localeCompare(values[1], undefined, {ignorePunctuation: true}); - } - - if (typeof values[0] === 'object' && values[0]._isAMomentObject) { - return values[0].valueOf() - values[1].valueOf(); - } - - return values[0] - values[1]; - }); - - return sorted; - }), - - actions: { - deleteSubscriber(subscriber) { - this.set('subscriberToDelete', subscriber); - }, - - confirmDeleteSubscriber() { - let subscriber = this.subscriberToDelete; - - return subscriber.destroyRecord().then(() => { - this.set('subscriberToDelete', null); - }); - }, - - cancelDeleteSubscriber() { - this.set('subscriberToDelete', null); - }, - - exportData() { - let exportUrl = ghostPaths().url.api('subscribers/csv'); - let accessToken = this.get('session.data.authenticated.access_token'); - let downloadURL = `${exportUrl}?access_token=${accessToken}`; - let iframe = $('#iframeDownload'); - - if (iframe.length === 0) { - iframe = $('