Added "Opt-in existing subscribers" option to newsletter creation

refs https://github.com/TryGhost/Team/issues/1528

- adds "opt-in existing" toggle to newsletter modal that's only shown when creating a newsletter
  - defaults to true
- updated newsletter save flow to show confirmation before creation
  - alters message to reflect auto-subscribe selection
  - count of existing subscribed members is not implemented as it's not yet supported by the API
- updated newsletter adapter and save flow to use auto opt-in selection
  - when option is checked the save URL is changed to `POST /members/?opt_in_existing=true`
- modified task button component to ignore a task return value of `canceled` so when it's received the buttons returns to the idle state instead of showing a saved or failed state
  - used by save routine when the "Back to edit" button is clicked in the create confirmation modal
This commit is contained in:
Kevin Ansfield 2022-04-19 20:01:08 +01:00
parent 5758fc91fc
commit b377927d26
9 changed files with 134 additions and 7 deletions

View File

@ -0,0 +1,14 @@
import ApplicationAdapter from 'ghost-admin/adapters/application';
export default class Newsletter extends ApplicationAdapter {
buildIncludeURL(store, modelName, id, snapshot, requestType, query) {
const url = this.buildURL(modelName, id, snapshot, requestType, query);
const parsedUrl = new URL(url);
if (snapshot?.adapterOptions?.optInExisting) {
parsedUrl.searchParams.append('opt_in_existing', 'true');
}
return parsedUrl.toString();
}
}

View File

@ -84,7 +84,7 @@ const GhTaskButton = Component.extend({
}
let value = this.get('task.last.value');
return (taskName === lastTaskName) && !isBlank(value) && value !== false;
return (taskName === lastTaskName) && !isBlank(value) && value !== false && value !== 'canceled';
}),
isSuccessClass: computed('isSuccess', function () {
@ -99,7 +99,7 @@ const GhTaskButton = Component.extend({
return false;
}
return (taskName === lastTaskName) && this.get('task.last.error') !== undefined;
return (taskName === lastTaskName) && this.get('task.last.error') !== undefined && this.task.last?.error !== null;
}),
isFailureClass: computed('isFailure', function () {

View File

@ -13,7 +13,11 @@
</div>
{{#if (eq this.tab "settings")}}
<Modals::EditNewsletter::Settings @newsletter={{@data.newsletter}} />
<Modals::EditNewsletter::Settings
@newsletter={{@data.newsletter}}
@optInExisting={{this.optInExisting}}
@setOptInExisting={{this.setOptInExisting}}
/>
{{else}}
<Modals::EditNewsletter::Design @newsletter={{@data.newsletter}} />
{{/if}}

View File

@ -1,4 +1,5 @@
import Component from '@glimmer/component';
import ConfirmCreateModal from './edit-newsletter/confirm-create';
import ConfirmNewsletterEmailModal from './edit-newsletter/confirm-newsletter-email';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
@ -13,6 +14,7 @@ export default class EditNewsletterModal extends Component {
};
@tracked tab = 'settings';
@tracked optInExisting = this.args.data.newsletter.isNew;
willDestroy() {
super.willDestroy(...arguments);
@ -32,12 +34,33 @@ export default class EditNewsletterModal extends Component {
this.saveTask.perform();
}
@action
setOptInExisting(value) {
this.optInExisting = value;
}
@task
*saveTask() {
try {
yield this.args.data.newsletter.validate({});
const {optInExisting} = this;
const shouldCreate = yield this.modals.open(ConfirmCreateModal, {
optInExisting,
newsletter: this.args.data.newsletter
});
if (!shouldCreate) {
// ensure task button returns to idle state
return 'canceled';
}
const newEmail = this.args.data.newsletter.senderEmail;
const result = yield this.args.data.newsletter.save();
const result = yield this.args.data.newsletter.save({
adapterOptions: {optInExisting}
});
if (result._meta?.sent_email_verification) {
yield this.modals.open(ConfirmNewsletterEmailModal, {
@ -51,8 +74,8 @@ export default class EditNewsletterModal extends Component {
return result;
} catch (e) {
if (e === undefined) {
// validation error
return false;
// ensure task button shows failed state
throw new Error('Validation failed');
}
throw e;

View File

@ -0,0 +1,47 @@
<div class="modal-content">
<header class="modal-header" data-test-modal="confirm-newsletter-email">
<h1>All set? Here's what happens next</h1>
</header>
<button type="button" class="close" role="button" title="Close" {{on "click" (fn @close false)}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
<div class="modal-body">
<p>
{{#if @data.optInExisting}}
{{!-- TODO: add members count once API supports it --}}
{{!-- {{#let (members-count-fetcher query=(hash filter="")) as |countFetcher|}}
Your newsletter <strong>{{@data.newsletter.name}}</strong> will be
immediately made live and your
{{#if countFetcher.count}}<strong>{{countFetcher.count}}</strong>{{/if}}
existing newsletter subscribers will be automatically opted-in to receive it.
{{/let}} --}}
Your newsletter <strong>{{@data.newsletter.name}}</strong> will be
immediately made live and your existing newsletter subscribers
will be automatically opted-in to receive it.
{{else}}
Your newsletter <strong>{{@data.newsletter.name}}</strong> will be
immediately made live. Your existing newsletter subscribers will
<strong>not</strong> be opted-in to receive it.
{{/if}}
</p>
</div>
<div class="modal-footer">
<button
type="button"
class="gh-btn"
{{on "click" (fn @close false)}}
>
<span>Back to edit</span>
</button>
<button
type="button"
class="gh-btn gh-btn-black"
{{on "click" (fn @close true)}}
{{on-key "Enter"}}
>
<span>Create newsletter</span>
</button>
</div>
</div>

View File

@ -83,6 +83,23 @@
</div>
</div>
</GhFormGroup>
{{#if @newsletter.isNew}}
<GhFormGroup>
<label for="opt-in-existing" class="modal-fullsettings-title">Opt-in existing subscribers</label>
<div class="for-switch small">
<div class="container">
<input
type="checkbox"
id="opt-in-existing"
checked={{@optInExisting}}
{{on "check" this.setOptInExisting}}
>
<button type="button" class="input-toggle-component" {{on "click" this.toggleOptInExisting}}></button>
</div>
</div>
</GhFormGroup>
{{/if}}
</div>
</fieldset>
</div>

View File

@ -24,4 +24,14 @@ export default class EditNewsletterSettingsForm extends Component {
onValueChange(property, value) {
this.args.newsletter[property] = value;
}
@action
setOptInExisting(event) {
this.args.setOptInExisting(event.target.value);
}
@action
toggleOptInExisting() {
this.args.setOptInExisting(!this.args.optInExisting);
}
}

View File

@ -5,7 +5,7 @@ export default function mockNewsletters(server) {
server.get('/newsletters/', paginatedResponse('newsletters'));
server.get('/newsletters/:id/');
server.post('/newsletters/', function ({newsletters}) {
server.post('/newsletters/', function ({newsletters}, {queryParams}) {
const attrs = this.normalizedRequestAttrs();
// sender email can't be set without verification
@ -23,6 +23,13 @@ export default function mockNewsletters(server) {
};
}
if (queryParams.opt_in_existing === 'true') {
newsletters.all().models.forEach((n) => {
newsletter.members.mergeCollection(n.members);
});
newsletter.save();
}
return collection;
});

View File

@ -0,0 +1,5 @@
import {Model, hasMany} from 'miragejs';
export default Model.extend({
members: hasMany()
});