mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-01 02:41:39 -05:00
Switched to a minimal form when creating a newsletter (#2356)
no issue The full edit newsletter form with all the settings, design options, and preview felt quite overwhelming when the only piece of data that's required to create a newsletter is the name. - re-organised the newsletter modal components by renaming `modals/edit-newlsetter` to `modals/newsletters` to better represent the full suite of modals that are used in newsletter management - added a `modals/newsletters/new` component containing a minimal form with name/description/opt-in-existing fields - switched the `new-newsletter` route to open the new modal rather than the previous dual-purpose edit modal - moved message about newsletter creation into the create modal and dropped the separate create confirmation modal - dropped unnecessary unsaved-changes confirmation - removed the now-unused opt-in-existing behaviour from the edit newsletter modal Co-authored-by: Peter Zimon <peter.zimon@gmail.com>
This commit is contained in:
parent
76ec5bef46
commit
404d3c44cf
20 changed files with 151 additions and 144 deletions
|
@ -1,37 +0,0 @@
|
|||
<div class="modal-content">
|
||||
<header class="modal-header" data-test-modal="confirm-newsletter-create">
|
||||
<h1>{{@data.newsletter.name}}</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}}
|
||||
{{#let (members-count-fetcher query=(hash filter="newsletters.status:active")) as |countFetcher|}}
|
||||
This newsletter will be available to <strong>all members</strong>. Your {{#if countFetcher.count}}<strong>{{countFetcher.count}}</strong>{{/if}} existing subscriber{{#if (gt countFetcher.count 1)}}s{{/if}} will also be opted-in to receive it. Sound good?
|
||||
{{/let}}
|
||||
{{else}}
|
||||
The newsletter will be available to <strong>all new members</strong>. Existing members won’t be subscribed, but may visit their account area to opt-in to future emails.
|
||||
{{/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>
|
|
@ -13,13 +13,11 @@
|
|||
</div>
|
||||
|
||||
{{#if (eq this.tab "settings")}}
|
||||
<Modals::EditNewsletter::Settings
|
||||
<Modals::Newsletters::Edit::Settings
|
||||
@newsletter={{@data.newsletter}}
|
||||
@optInExisting={{this.optInExisting}}
|
||||
@setOptInExisting={{this.setOptInExisting}}
|
||||
/>
|
||||
{{else}}
|
||||
<Modals::EditNewsletter::Design @newsletter={{@data.newsletter}} />
|
||||
<Modals::Newsletters::Edit::Design @newsletter={{@data.newsletter}} />
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
|
@ -39,7 +37,7 @@
|
|||
data-test-button="save-newsletter"
|
||||
/>
|
||||
</div>
|
||||
<Modals::EditNewsletter::Preview @newsletter={{@data.newsletter}} />
|
||||
<Modals::Newsletters::Edit::Preview @newsletter={{@data.newsletter}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,6 +1,5 @@
|
|||
import Component from '@glimmer/component';
|
||||
import ConfirmCreateModal from './edit-newsletter/confirm-create';
|
||||
import ConfirmNewsletterEmailModal from './edit-newsletter/confirm-newsletter-email';
|
||||
import ConfirmNewsletterEmailModal from './confirm-newsletter-email';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
@ -14,7 +13,6 @@ export default class EditNewsletterModal extends Component {
|
|||
};
|
||||
|
||||
@tracked tab = 'settings';
|
||||
@tracked optInExisting = this.args.data.newsletter.isNew;
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
|
@ -34,35 +32,14 @@ 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;
|
||||
|
||||
if (this.args.data.newsletter.isNew) {
|
||||
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({
|
||||
adapterOptions: {optInExisting}
|
||||
});
|
||||
const result = yield this.args.data.newsletter.save();
|
||||
|
||||
if (result._meta?.sent_email_verification) {
|
||||
yield this.modals.open(ConfirmNewsletterEmailModal, {
|
|
@ -41,7 +41,7 @@
|
|||
<GhErrorMessage @errors={{@newsletter.errors}} @property="senderName" />
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @classNames="vertical" @errors={{@newsletter.errors}} @hasValidated={{@newsletter.hasValidated}} @property="senderEmail">
|
||||
<GhFormGroup @classNames="vertical" @errors={{@newsletter.errors}} @hasValidated={{@newsletter.hasValidated}} @property="senderEmail">
|
||||
<span class="flex items-center justify-between">
|
||||
<label for="newsletter-sender-email" class="modal-fullsettings-title ml2">Newsletter email address</label>
|
||||
<span class="tooltip-top-left" data-tooltip="Defaults to {{full-email-address "noreply"}} if empty">{{svg-jar "info" class="fill-darkgrey w4 h4"}}</span>
|
||||
|
@ -88,23 +88,6 @@
|
|||
</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>
|
77
ghost/admin/app/components/modals/newsletters/new.hbs
Normal file
77
ghost/admin/app/components/modals/newsletters/new.hbs
Normal file
|
@ -0,0 +1,77 @@
|
|||
<div class="modal-content">
|
||||
<header class="modal-header">
|
||||
<h1>Create newsletter</h1>
|
||||
</header>
|
||||
<button type="button" class="close" title="Close" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
<fieldset>
|
||||
<GhFormGroup @errors={{@data.newsletter.errors}} @hasValidated={{@data.newsletter.hasValidated}} @property="name">
|
||||
<label for="newsletter-title" class="modal-fullsettings-title">Name</label>
|
||||
<input
|
||||
id="newsletter-title"
|
||||
type="text"
|
||||
class="gh-input miw-100 form-text"
|
||||
value={{@data.newsletter.name}}
|
||||
placeholder="Weekly Roundup"
|
||||
{{on "input" (fn this.onInput "name")}}
|
||||
/>
|
||||
<GhErrorMessage @errors={{@data.newsletter.errors}} @property="name" />
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @errors={{@data.newsletter.errors}} @hasValidated={{@data.newsletter.hasValidated}} @property="description">
|
||||
<label for="newsletter-description" class="modal-fullsettings-title">Description</label>
|
||||
<textarea
|
||||
id="newsletter-description"
|
||||
class="gh-input miw-100 form-text"
|
||||
{{on "input" (fn this.onInput "description")}}
|
||||
>{{@data.newsletter.description}}</textarea>
|
||||
<GhErrorMessage @errors={{@data.newsletter.errors}} @property="description" />
|
||||
</GhFormGroup>
|
||||
|
||||
<GhFormGroup @classNames="flex justify-between items-start mb2">
|
||||
<div class="mr3">
|
||||
<label for="opt-in-existing" class="modal-fullsettings-title">Opt-in existing subscribers</label>
|
||||
<p>
|
||||
{{#if this.optInExisting}}
|
||||
{{#let (members-count-fetcher query=(hash filter="newsletters.status:active")) as |countFetcher|}}
|
||||
This newsletter will be available to <strong>all members</strong>. Your {{#if countFetcher.count}}<strong>{{countFetcher.count}}</strong>{{/if}} existing subscriber{{#if (gt countFetcher.count 1)}}s{{/if}} will also be opted-in to receive it.
|
||||
{{/let}}
|
||||
{{else}}
|
||||
The newsletter will be available to <strong>all new members</strong>. Existing members won’t be subscribed, but may visit their account area to opt-in to future emails.
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="for-switch small">
|
||||
<div class="container">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="opt-in-existing"
|
||||
checked={{this.optInExisting}}
|
||||
{{on "check" this.setOptInExisting}}
|
||||
>
|
||||
<button type="button" class="input-toggle-component" {{on "click" this.toggleOptInExisting}}></button>
|
||||
</div>
|
||||
</div>
|
||||
</GhFormGroup>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" type="button" {{on "click" @close}}>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
|
||||
<GhTaskButton
|
||||
@buttonText="Create"
|
||||
@runningText="Creating"
|
||||
@successText="Created"
|
||||
@task={{this.saveTask}}
|
||||
@idleClass="gh-btn-primary"
|
||||
@class="gh-btn gh-btn-icon"
|
||||
{{on-key "cmd+s" this.saveViaKeyboard priority=1}}
|
||||
{{on-key "Enter" this.saveViaKeyboard priority=1}}
|
||||
data-test-button="save-newsletter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
61
ghost/admin/app/components/modals/newsletters/new.js
Normal file
61
ghost/admin/app/components/modals/newsletters/new.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class NewNewsletterModal extends Component {
|
||||
@service modals;
|
||||
|
||||
@tracked optInExisting = this.args.data.newsletter.isNew;
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
this.args.data.newsletter.rollbackAttributes();
|
||||
}
|
||||
|
||||
@action
|
||||
onInput(property, event) {
|
||||
this.args.data.newsletter[property] = event.target.value;
|
||||
}
|
||||
|
||||
@action
|
||||
saveViaKeyboard(event, responder) {
|
||||
responder.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
this.saveTask.perform();
|
||||
}
|
||||
|
||||
@action
|
||||
setOptInExisting(event) {
|
||||
this.optInExisting = event.target.value;
|
||||
}
|
||||
|
||||
@action
|
||||
toggleOptInExisting() {
|
||||
this.optInExisting = !this.optInExisting;
|
||||
}
|
||||
|
||||
@task
|
||||
*saveTask() {
|
||||
try {
|
||||
yield this.args.data.newsletter.validate({});
|
||||
|
||||
const result = yield this.args.data.newsletter.save({
|
||||
adapterOptions: {optInExisting: this.optInExisting}
|
||||
});
|
||||
|
||||
this.args.data.afterSave?.(result);
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
if (e === undefined) {
|
||||
// ensure task button shows failed state
|
||||
throw new Error('Validation failed');
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import Component from '@glimmer/component';
|
||||
import ConfirmArchiveModal from '../../modals/edit-newsletter/confirm-archive';
|
||||
import ConfirmUnarchiveModal from '../../modals/edit-newsletter/confirm-unarchive';
|
||||
import ConfirmArchiveModal from '../../modals/newsletters/confirm-archive';
|
||||
import ConfirmUnarchiveModal from '../../modals/newsletters/confirm-unarchive';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import AdminRoute from 'ghost-admin/routes/admin';
|
||||
import ConfirmUnsavedChangesModal from '../../components/modals/confirm-unsaved-changes';
|
||||
import VerifyNewsletterEmail from '../../components/modals/edit-newsletter/verify-newsletter-email';
|
||||
import VerifyNewsletterEmail from '../../components/modals/newsletters/verify-newsletter-email';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import AdminRoute from 'ghost-admin/routes/admin';
|
||||
import ConfirmUnsavedChangesModal from '../../../components/modals/confirm-unsaved-changes';
|
||||
import EditNewsletterModal from '../../../components/modals/edit-newsletter';
|
||||
import EditNewsletterModal from '../../../components/modals/newsletters/edit';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import AdminRoute from 'ghost-admin/routes/admin';
|
||||
import ConfirmUnsavedChangesModal from '../../../components/modals/confirm-unsaved-changes';
|
||||
import EditNewsletterModal from '../../../components/modals/edit-newsletter';
|
||||
import NewNewsletterModal from '../../../components/modals/newsletters/new';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
|
@ -19,7 +18,7 @@ export default class NewNewsletterRoute extends AdminRoute {
|
|||
setupController(controller, model) {
|
||||
this.newsletterModal?.close();
|
||||
|
||||
this.newsletterModal = this.modals.open(EditNewsletterModal, {
|
||||
this.newsletterModal = this.modals.open(NewNewsletterModal, {
|
||||
newsletter: model,
|
||||
afterSave: this.afterSave
|
||||
}, {
|
||||
|
@ -35,64 +34,13 @@ export default class NewNewsletterRoute extends AdminRoute {
|
|||
deactivate() {
|
||||
this.isLeaving = true;
|
||||
this.newsletterModal?.close();
|
||||
|
||||
this.isLeaving = false;
|
||||
this.newsletterModal = null;
|
||||
|
||||
this.confirmModal = null;
|
||||
this.hasConfirmed = false;
|
||||
}
|
||||
|
||||
@action
|
||||
async willTransition(transition) {
|
||||
if (this.hasConfirmed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
transition.abort();
|
||||
|
||||
// wait for any existing confirm modal to be closed before allowing transition
|
||||
if (this.confirmModal) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldLeave = await this.confirmUnsavedChanges();
|
||||
|
||||
if (shouldLeave) {
|
||||
this.hasConfirmed = true;
|
||||
return transition.retry();
|
||||
}
|
||||
}
|
||||
|
||||
async confirmUnsavedChanges() {
|
||||
const newsletter = this.newsletterModal?._data.newsletter;
|
||||
|
||||
if (newsletter && newsletter.hasDirtyAttributes && Object.keys(newsletter.changedAttributes()).length > 0) {
|
||||
this.confirmModal = this.modals.open(ConfirmUnsavedChangesModal)
|
||||
.then((discardChanges) => {
|
||||
if (discardChanges === true) {
|
||||
newsletter.rollbackAttributes();
|
||||
}
|
||||
return discardChanges;
|
||||
}).finally(() => {
|
||||
this.confirmModal = null;
|
||||
});
|
||||
|
||||
return this.confirmModal;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@action
|
||||
async beforeModalClose() {
|
||||
const shouldLeave = await this.confirmUnsavedChanges();
|
||||
|
||||
if (shouldLeave && !this.isLeaving) {
|
||||
if (!this.isLeaving) {
|
||||
this.router.transitionTo('settings.members-email-labs');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue