Added newsletter management for member (#2336)
refs https://github.com/TryGhost/Team/issues/1492 Allows site owners to manage member's newsletter preference directly in Admin. - also adds `visibility` property for newsletter model - updates members test model to include `visibility`
This commit is contained in:
parent
b49db57a90
commit
e240506c6c
|
@ -90,6 +90,14 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{#if (feature "multipleNewsletters")}}
|
||||
<Member::NewsletterPreference
|
||||
@member={{this.member}}
|
||||
@newsletters={{this.newslettersList}}
|
||||
@setMemberNewsletters={{this.setMemberNewsletters}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.isStripeConnected}}
|
||||
<h4 class="gh-main-section-header small bn">Subscriptions</h4>
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ export default class extends Component {
|
|||
|
||||
@tracked showMemberProductModal = false;
|
||||
@tracked productsList;
|
||||
@tracked newslettersList;
|
||||
|
||||
get canShowStripeInfo() {
|
||||
return !this.member.get('isNew') && this.membersUtils.isStripeEnabled;
|
||||
|
@ -110,6 +111,9 @@ export default class extends Component {
|
|||
@action
|
||||
setup() {
|
||||
this.fetchProducts.perform();
|
||||
if (this.feature.get('multipleNewsletters')) {
|
||||
this.fetchNewsletters.perform();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -122,6 +126,11 @@ export default class extends Component {
|
|||
this.member.set('labels', labels);
|
||||
}
|
||||
|
||||
@action
|
||||
setMemberNewsletters(newsletters) {
|
||||
this.member.set('newsletters', newsletters);
|
||||
}
|
||||
|
||||
@action
|
||||
closeMemberProductModal() {
|
||||
this.showMemberProductModal = false;
|
||||
|
@ -197,4 +206,15 @@ export default class extends Component {
|
|||
*fetchProducts() {
|
||||
this.productsList = yield this.store.query('product', {filter: 'type:paid+active:true', include: 'monthly_price,yearly_price'});
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*fetchNewsletters() {
|
||||
this.newslettersList = yield this.store.query('newsletter', {filter: 'status:active'});
|
||||
if (this.member.get('isNew')) {
|
||||
const defaultNewsletters = this.newslettersList.filter((newsletter) => {
|
||||
return newsletter.subscribeOnSignup && newsletter.visibility === 'members';
|
||||
});
|
||||
this.setMemberNewsletters(defaultNewsletters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<h4 class="gh-main-section-header small bn">Newsletters</h4>
|
||||
<div class="gh-main-section-content grey">
|
||||
{{#each this.newsletters as |newsletter|}}
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<h4 class="gh-setting-title m">{{newsletter.name}}</h4>
|
||||
<p class="gh-setting-desc">{{newsletter.description}}</p>
|
||||
</div>
|
||||
<div class="for-switch">
|
||||
<label class="switch" for={{newsletter.forId}}>
|
||||
<Input
|
||||
@checked={{newsletter.subscribed}}
|
||||
@type="checkbox"
|
||||
id={{newsletter.forId}}
|
||||
name="subscribed"
|
||||
data-test-checkbox="member-subscribed"
|
||||
{{on "click" (fn this.updateNewsletterPreference newsletter)}}
|
||||
/>
|
||||
<span class="input-toggle-component"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
|
@ -0,0 +1,46 @@
|
|||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class MembersNewsletterPreference extends Component {
|
||||
@tracked filterValue;
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
}
|
||||
|
||||
get newsletters() {
|
||||
if (this.args.newsletters?.length > 0) {
|
||||
return this.args.newsletters.map((d) => {
|
||||
return {
|
||||
name: d.name,
|
||||
description: d.description,
|
||||
subscribed: !!this.args.member?.get('newsletters')?.find((n) => {
|
||||
return n.id === d.id;
|
||||
}),
|
||||
id: d.id,
|
||||
forId: `${d.id}-checkbox`
|
||||
};
|
||||
});
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
@action
|
||||
updateNewsletterPreference(newsletter, event) {
|
||||
let updatedNewsletters = [];
|
||||
const selectedNewsletter = this.args.newsletters.find((d) => {
|
||||
return d.id === newsletter.id;
|
||||
});
|
||||
if (!event.target.checked) {
|
||||
updatedNewsletters = this.args.member.newsletters.filter((d) => {
|
||||
return d.id !== newsletter.id;
|
||||
});
|
||||
} else {
|
||||
updatedNewsletters = this.args.member.newsletters.filter((d) => {
|
||||
return d.id !== newsletter.id;
|
||||
}).concat(selectedNewsletter);
|
||||
}
|
||||
this.args.setMemberNewsletters(updatedNewsletters);
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ export default Model.extend(ValidationEngine, {
|
|||
emailOpenRate: attr('number'),
|
||||
|
||||
products: attr('member-product'),
|
||||
newsletters: hasMany('newsletter', {embedded: 'always', async: false}),
|
||||
|
||||
labels: hasMany('label', {embedded: 'always', async: false}),
|
||||
emailRecipients: hasMany('emailRecipient', {async: true}),
|
||||
|
|
|
@ -15,6 +15,7 @@ export default class Newsletter extends Model.extend(ValidationEngine) {
|
|||
@attr({defaultValue: 'active'}) status;
|
||||
@attr({defaultValue: ''}) recipientFilter;
|
||||
@attr({defaultValue: false}) subscribeOnSignup;
|
||||
@attr({defaultValue: 'members'}) visibility;
|
||||
@attr({defaultValue: 0}) sortOrder;
|
||||
|
||||
// Design-related properties
|
||||
|
|
|
@ -7,7 +7,8 @@ export default class MemberSerializer extends ApplicationSerializer.extend(Embed
|
|||
createdAtUTC: {key: 'created_at'},
|
||||
lastSeenAtUTC: {key: 'last_seen_at'},
|
||||
labels: {embedded: 'always'},
|
||||
emailRecipients: {embedded: 'always'}
|
||||
emailRecipients: {embedded: 'always'},
|
||||
newsletters: {embedded: 'always'}
|
||||
};
|
||||
|
||||
serialize(/*snapshot, options*/) {
|
||||
|
|
|
@ -59,5 +59,19 @@ export default BaseValidator.create({
|
|||
}
|
||||
|
||||
model.hasValidated.addObject('senderReplyTo');
|
||||
},
|
||||
|
||||
visibility(model) {
|
||||
if (isBlank(model.visibility)) {
|
||||
model.errors.add('visibility', 'Please enter visibility.');
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
if (!validator.isIn(model.senderReplyTo, ['members', 'paid'])) {
|
||||
model.errors.add('visibility', 'Can only be set to "members" or "paid".');
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
model.hasValidated.addObject('visibility');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,5 +4,6 @@ export default Model.extend({
|
|||
labels: hasMany(),
|
||||
emailRecipients: hasMany(),
|
||||
products: hasMany(),
|
||||
newsletters: hasMany(),
|
||||
subscriptions: hasMany()
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue