mirror of
https://github.com/TryGhost/Ghost-Admin.git
synced 2023-12-14 02:33:04 +01:00
3155e6091d
no-issue * Supported cancellation of subscriptions on delete This makes the cancellation of subscriptions much more obvious to the user, and we err on the side of caution by *not* cancelling by default. * Updated base adapter to handle urls with query params After creating the member adapter and overriding the urlForDeleteRecord method the flow would take the url from that and it would get passed into the buildUrl method of the base adapter. At this point it would append a "/" _after_ the query param. It would ouput http://admin.com/ghost/api?query=blah/ rather than http://admin.com/ghost/api/?query=blah
167 lines
4.9 KiB
JavaScript
167 lines
4.9 KiB
JavaScript
import Controller, {inject as controller} from '@ember/controller';
|
|
import EmberObject, {action, defineProperty} from '@ember/object';
|
|
import boundOneWay from 'ghost-admin/utils/bound-one-way';
|
|
import moment from 'moment';
|
|
import {alias} from '@ember/object/computed';
|
|
import {inject as service} from '@ember/service';
|
|
import {task} from 'ember-concurrency-decorators';
|
|
import {tracked} from '@glimmer/tracking';
|
|
|
|
const SCRATCH_PROPS = ['name', 'email', 'note'];
|
|
|
|
export default class MemberController extends Controller {
|
|
@controller members;
|
|
@service session;
|
|
@service dropdown;
|
|
@service membersStats;
|
|
@service notifications;
|
|
@service router;
|
|
@service store;
|
|
|
|
@tracked isLoading = false;
|
|
@tracked showDeleteMemberModal = false;
|
|
@tracked showImpersonateMemberModal = false;
|
|
@tracked showUnsavedChangesModal = false;
|
|
|
|
leaveScreenTransition = null;
|
|
|
|
// Computed properties -----------------------------------------------------
|
|
|
|
@alias('model') member;
|
|
|
|
get scratchMember() {
|
|
let scratchMember = EmberObject.create({member: this.member});
|
|
SCRATCH_PROPS.forEach(prop => defineProperty(scratchMember, prop, boundOneWay(`member.${prop}`)));
|
|
return scratchMember;
|
|
}
|
|
|
|
get subscribedAt() {
|
|
// member can be a proxy object in a sparse array so .get is required
|
|
let memberSince = moment(this.member.get('createdAtUTC')).from(moment());
|
|
let createdDate = moment(this.member.get('createdAtUTC')).format('D MMM YYYY');
|
|
return `${createdDate} (${memberSince})`;
|
|
}
|
|
|
|
// Actions -----------------------------------------------------------------
|
|
|
|
@action
|
|
setProperty(propKey, value) {
|
|
this._saveMemberProperty(propKey, value);
|
|
}
|
|
|
|
@action
|
|
toggleDeleteMemberModal() {
|
|
this.showDeleteMemberModal = !this.showDeleteMemberModal;
|
|
}
|
|
|
|
@action
|
|
toggleImpersonateMemberModal() {
|
|
this.showImpersonateMemberModal = !this.showImpersonateMemberModal;
|
|
}
|
|
|
|
@action
|
|
save() {
|
|
return this.saveTask.perform();
|
|
}
|
|
|
|
@action
|
|
deleteMember(cancelSubscriptions = false) {
|
|
let options = {
|
|
adapterOptions: {
|
|
cancel: cancelSubscriptions
|
|
}
|
|
};
|
|
return this.member.destroyRecord(options).then(() => {
|
|
this.members.refreshData();
|
|
return this.transitionToRoute('members');
|
|
}, (error) => {
|
|
return this.notifications.showAPIError(error, {key: 'member.delete'});
|
|
});
|
|
}
|
|
|
|
@action
|
|
toggleUnsavedChangesModal(transition) {
|
|
let leaveTransition = this.leaveScreenTransition;
|
|
|
|
if (!transition && this.showUnsavedChangesModal) {
|
|
this.leaveScreenTransition = null;
|
|
this.showUnsavedChangesModal = false;
|
|
return;
|
|
}
|
|
|
|
if (!leaveTransition || transition.targetName === leaveTransition.targetName) {
|
|
this.leaveScreenTransition = transition;
|
|
|
|
// if a save is running, wait for it to finish then transition
|
|
if (this.save.isRunning) {
|
|
return this.save.last.then(() => {
|
|
transition.retry();
|
|
});
|
|
}
|
|
|
|
// we genuinely have unsaved data, show the modal
|
|
this.showUnsavedChangesModal = true;
|
|
}
|
|
}
|
|
|
|
@action
|
|
leaveScreen() {
|
|
this.member.rollbackAttributes();
|
|
return this.leaveScreenTransition.retry();
|
|
}
|
|
|
|
// Tasks -------------------------------------------------------------------
|
|
|
|
@task({drop: true})
|
|
*saveTask() {
|
|
let {member, scratchMember} = this;
|
|
|
|
// if Cmd+S is pressed before the field loses focus make sure we're
|
|
// saving the intended property values
|
|
let scratchProps = scratchMember.getProperties(SCRATCH_PROPS);
|
|
member.setProperties(scratchProps);
|
|
|
|
try {
|
|
yield member.save();
|
|
member.updateLabels();
|
|
this.members.refreshData();
|
|
|
|
// replace 'member.new' route with 'member' route
|
|
this.replaceRoute('member', member);
|
|
|
|
return member;
|
|
} catch (error) {
|
|
if (error) {
|
|
this.notifications.showAPIError(error, {key: 'member.save'});
|
|
}
|
|
}
|
|
}
|
|
|
|
@task
|
|
*fetchMemberTask(memberId) {
|
|
this.isLoading = true;
|
|
|
|
this.member = yield this.store.findRecord('member', memberId, {
|
|
reload: true
|
|
});
|
|
|
|
this.isLoading = false;
|
|
}
|
|
|
|
// Private -----------------------------------------------------------------
|
|
|
|
_saveMemberProperty(propKey, newValue) {
|
|
let currentValue = this.member.get(propKey);
|
|
|
|
if (newValue) {
|
|
newValue = newValue.trim();
|
|
}
|
|
|
|
// avoid modifying empty values and triggering inadvertant unsaved changes modals
|
|
if (newValue !== false && !newValue && !currentValue) {
|
|
return;
|
|
}
|
|
|
|
this.member.set(propKey, newValue);
|
|
}
|
|
}
|