Add member's email status on member page (#15844)

closes TryGhost/Team#2270
- Show emails status depending on the reason email was blocked
(spam/fail)
This commit is contained in:
Elena Baidakova 2022-11-18 15:55:21 +04:00 committed by GitHub
parent ab8f16ce79
commit 696cdea4d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 124 additions and 38 deletions

View File

@ -64,8 +64,7 @@
<p> Maximum: <b>500</b> characters. Youve used
{{gh-count-down-characters this.scratchMember.note 500}}</p>
</GhFormGroup>
{{#if this.hasSingleNewsletter}}
{{#if (not-eq this.settings.editorDefaultEmailRecipients "disabled")}}
{{#if this.canShowSingleNewsletter}}
<GhFormGroup @classNames="gh-members-subscribed-checkbox mb0">
<div class="flex justify-between items-center">
<div>
@ -88,11 +87,10 @@
</div>
</GhFormGroup>
{{/if}}
{{/if}}
</div>
</div>
{{#if this.hasMultipleNewsletters}}
{{#if this.canShowMultipleNewsletters}}
<Member::NewsletterPreference
@member={{this.member}}
@newsletters={{this.newslettersList}}

View File

@ -45,14 +45,6 @@ export default class extends Component {
return hasAnActivePaidTier;
}
get hasSingleNewsletter() {
return this.newslettersList?.length === 1;
}
get hasMultipleNewsletters() {
return !!(this.newslettersList?.length > 1);
}
get isCreatingComplimentary() {
return this.args.isSaveRunning;
}
@ -124,6 +116,21 @@ export default class extends Component {
return null;
}
get canShowSingleNewsletter() {
return (
this.newslettersList?.length === 1
&& this.settings.editorDefaultEmailRecipients !== 'disabled'
&& !this.feature.get('suppressionList')
);
}
get canShowMultipleNewsletters() {
return (
(this.newslettersList?.length > 1 || this.feature.get('suppressionList'))
&& this.settings.editorDefaultEmailRecipients !== 'disabled'
);
}
@action
updateNewsletterPreference(event) {
if (!event.target.checked) {

View File

@ -1,12 +1,12 @@
<h4 class="gh-main-section-header small bn">Newsletters</h4>
<div class="gh-main-section-content grey">
<div class="gh-main-section-content grey {{if (feature "suppressionList") 'gh-member-newsletter-section'}}">
<div class="gh-member-newsletters">
{{#each this.newsletters as |newsletter|}}
<div class="gh-member-newsletter-row">
<div>
<h4 class="gh-member-newsletter-title">{{newsletter.name}}</h4>
</div>
<div class="for-switch">
<div class="for-switch {{if (and (feature "suppressionList") this.suppressionData.suppressed) 'disabled'}}">
<label class="switch" for={{newsletter.forId}}>
<Input
@checked={{newsletter.subscribed}}
@ -22,4 +22,34 @@
</div>
{{/each}}
</div>
{{#if (and (feature "suppressionList") this.suppressionData.suppressed)}}
<div class="gh-member-newsletter-footer red">
{{#if (eq this.suppressionData.reason 'fail')}}
{{svg-jar "event-email-delivery-failed" class="gh-member-newsletter-footer-icon"}}
{{/if}}
{{#if (eq this.suppressionData.reason 'spam')}}
{{svg-jar "event-email-delivery-spam" class="gh-member-newsletter-footer-icon"}}
{{/if}}
<div class="gh-member-newsletter-footer-text">
{{#if (eq this.suppressionData.reason 'fail')}}
Member email was suppressed due to it bouncing on {{this.suppressionData.date}}
{{/if}}
{{#if (eq this.suppressionData.reason 'spam')}}
Member email was suppressed due to a spam complaint on {{this.suppressionData.date}}
{{/if}}
<a class="midgrey" href="javascript:void(0)" target="_blank" rel="noopener noreferrer">Learn more</a>
</div>
</div>
{{/if}}
{{#if (and (feature "suppressionList") (not this.suppressionData.suppressed))}}
<div class="gh-member-newsletter-footer midgrey">
If disabled, member will <i>not</i> receive newsletter emails
</div>
{{/if}}
</div>

View File

@ -1,4 +1,5 @@
import Component from '@glimmer/component';
import moment from 'moment-timezone';
import {action} from '@ember/object';
import {tracked} from '@glimmer/tracking';
@ -26,6 +27,18 @@ export default class MembersNewsletterPreference extends Component {
return [];
}
get suppressionData() {
const {emailSuppression} = this.args.member;
const timestamp = emailSuppression?.info?.timestamp;
const formattedDate = timestamp ? moment(new Date(timestamp)).format('D MMM YYYY') : null;
return {
suppressed: emailSuppression?.suppressed,
reason: emailSuppression?.info?.reason,
date: formattedDate
};
}
@action
updateNewsletterPreference(newsletter, event) {
let updatedNewsletters = [];

View File

@ -115,12 +115,12 @@ export default class MembersEventsFetcher extends Resource {
return;
}
const member = yield this.store.findRecord('member', memberId);
if (member.email === 'spam@ghost.org') {
if (member.email === 'spam@member.test') {
this.data.unshift(mockData('email_delivered_event'));
this.data.unshift(mockData('email_complaint_event'));
}
if (member.email === 'fail@ghost.org') {
if (member.email === 'fail@member.test') {
this.data.unshift(mockData('email_failed_event'));
}
}
@ -150,7 +150,7 @@ function mockData(eventType) {
member: {
id: '63737a1719675aed3b7cc988',
uuid: '5c753e47-9f49-43ad-86d4-c5c0168519a2',
email: 'spam@ghost.org',
email: 'spam@member.test',
status: 'free',
name: 'Spam',
expertise: null,

View File

@ -24,6 +24,7 @@ export default Model.extend(ValidationEngine, {
tiers: attr('member-tier'),
newsletters: hasMany('newsletter', {embedded: 'always', async: false}),
emailSuppression: attr(),
labels: hasMany('label', {embedded: 'always', async: false}),

View File

@ -62,6 +62,7 @@ export default class FeatureService extends Service {
@feature('sourceAttribution') sourceAttribution;
@feature('lexicalEditor') lexicalEditor;
@feature('audienceFeedback') audienceFeedback;
@feature('suppressionList') suppressionList;
_user = null;

View File

@ -1122,6 +1122,42 @@ textarea.gh-member-details-textarea {
margin-bottom: 0!important;
}
.gh-main-section-content.gh-member-newsletter-section {
padding-bottom: 16px;
}
.gh-member-newsletter-section .for-switch.disabled {
opacity: 0.1;
}
.gh-member-newsletter-footer-text {
font-weight: 500;
}
.gh-member-newsletter-footer-text a {
display: inline-block;
text-decoration: underline;
margin-left: 2px;
font-weight: 400;
}
.gh-member-newsletter-footer {
display: flex;
gap: 4px;
align-items: center;
font-size: 1.25rem;
margin-top: 12px;
}
.gh-member-newsletter-footer-icon {
width: 24px;
height: 24px;
}
.gh-member-newsletter-footer-icon [class*="__body"] {
stroke: #95A1AD;
}
.gh-member-feed-container {
display: flex;
flex-grow: 1;

View File

@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none">
<path d="M14.412 18.978a1.22 1.22 0 0 1-1.163-.332l-2.414-2.404-2.592 1.34.1-3.843-3.036-3.035a1.219 1.219 0 0 1-.332-1.108 1.24 1.24 0 0 1 .82-.953L17.68 4.678a1.24 1.24 0 0 1 1.595 1.595l-1.307 3.919M18.917 4.926 8.339 13.743" stroke="#6C747D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path class="body" d="M14.412 18.978a1.22 1.22 0 0 1-1.163-.332l-2.414-2.404-2.592 1.34.1-3.843-3.036-3.035a1.219 1.219 0 0 1-.332-1.108 1.24 1.24 0 0 1 .82-.953L17.68 4.678a1.24 1.24 0 0 1 1.595 1.595l-1.307 3.919M18.917 4.926 8.339 13.743" stroke="#6C747D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.634 13.35v2.869" stroke="#F50B23" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="17.639" cy="18.75" r=".75" fill="#F50B23"/>
</svg>

Before

Width:  |  Height:  |  Size: 580 B

After

Width:  |  Height:  |  Size: 593 B