diff --git a/ghost/admin/app/components/modals/edit-newsletter.js b/ghost/admin/app/components/modals/edit-newsletter.js index 0a058ad24e..324143f387 100644 --- a/ghost/admin/app/components/modals/edit-newsletter.js +++ b/ghost/admin/app/components/modals/edit-newsletter.js @@ -1,9 +1,13 @@ import Component from '@glimmer/component'; +import ConfirmNewsletterEmailModal from './edit-newsletter/confirm-newsletter-email'; 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 EditNewsletterModal extends Component { + @service modals; + static modalOptions = { className: 'fullscreen-modal-full-overlay fullscreen-modal-portal-settings' }; @@ -31,8 +35,17 @@ export default class EditNewsletterModal extends Component { @task *saveTask() { try { + const newEmail = this.args.data.newsletter.senderEmail; + const result = yield this.args.data.newsletter.save(); + if (result._meta?.sent_email_verification) { + yield this.modals.open(ConfirmNewsletterEmailModal, { + newEmail, + currentEmail: this.args.data.newsletter.senderEmail + }); + } + this.args.data.afterSave?.(result); return result; diff --git a/ghost/admin/app/components/modals/edit-newsletter/confirm-newsletter-email.hbs b/ghost/admin/app/components/modals/edit-newsletter/confirm-newsletter-email.hbs new file mode 100644 index 0000000000..6ec8e7010a --- /dev/null +++ b/ghost/admin/app/components/modals/edit-newsletter/confirm-newsletter-email.hbs @@ -0,0 +1,26 @@ +
\ No newline at end of file diff --git a/ghost/admin/app/components/modals/edit-newsletter/verify-newsletter-email.hbs b/ghost/admin/app/components/modals/edit-newsletter/verify-newsletter-email.hbs new file mode 100644 index 0000000000..9bf758b998 --- /dev/null +++ b/ghost/admin/app/components/modals/edit-newsletter/verify-newsletter-email.hbs @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/ghost/admin/app/components/modals/edit-newsletter/verify-newsletter-email.js b/ghost/admin/app/components/modals/edit-newsletter/verify-newsletter-email.js new file mode 100644 index 0000000000..b06c627028 --- /dev/null +++ b/ghost/admin/app/components/modals/edit-newsletter/verify-newsletter-email.js @@ -0,0 +1,49 @@ +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 VerifyNewsletterEmail extends Component { + @service ajax; + @service ghostPaths; + @service router; + @service store; + + @tracked error = null; + @tracked newsletter = null; + + constructor() { + super(...arguments); + this.verifyEmailTask.perform(this.args.data.token); + + this.router.on('routeDidChange', this.handleRouteChange); + } + + willDestroy() { + super.willDestroy(...arguments); + this.router.off('routeDidChange', this.handleRouteChange); + } + + @task + *verifyEmailTask(token) { + try { + const url = this.ghostPaths.url.api('newsletters', 'verify-email'); + + const response = yield this.ajax.put(url, {data: {token}}); + + if (response.newsletters) { + this.store.pushPayload('newsletter', response); + const newsletter = this.store.peekRecord('newsletter', response.newsletters[0].id); + this.newsletter = newsletter; + } + } catch (e) { + this.error = e.message; + } + } + + @action + handleRouteChange() { + this.args.close(); + } +} diff --git a/ghost/admin/app/components/settings/members-email-labs.js b/ghost/admin/app/components/settings/members-email-labs.js index eb273a3f93..4c9dfb5b4c 100644 --- a/ghost/admin/app/components/settings/members-email-labs.js +++ b/ghost/admin/app/components/settings/members-email-labs.js @@ -1,7 +1,6 @@ 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'; const US = {flag: 'πΊπΈ', name: 'US', baseUrl: 'https://api.mailgun.net/v3'}; @@ -9,8 +8,6 @@ const EU = {flag: 'πͺπΊ', name: 'EU', baseUrl: 'https://api.eu.mailgun.net/v export default class MembersEmailLabs extends Component { @service config; - @service ghostPaths; - @service ajax; @service settings; // set recipientsSelectValue as a static property because within this @@ -19,41 +16,12 @@ export default class MembersEmailLabs extends Component { // from settings as it would equate to "none" @tracked recipientsSelectValue = this._getDerivedRecipientsSelectValue(); - @tracked showFromAddressConfirmation = false; - mailgunRegions = [US, EU]; - replyAddresses = [ - { - label: 'Newsletter email address (' + this.fromAddress + ')', - value: 'newsletter' - }, - { - label: 'Support email address (' + this.supportAddress + ')', - value: 'support' - } - ]; - get emailNewsletterEnabled() { return this.settings.get('editorDefaultEmailRecipients') !== 'disabled'; } - get emailPreviewVisible() { - return this.recipientsSelectValue !== 'none'; - } - - get selectedReplyAddress() { - return this.replyAddresses.findBy('value', this.settings.get('membersReplyAddress')); - } - - get disableUpdateFromAddressButton() { - const savedFromAddress = this.settings.get('membersFromAddress') || ''; - if (!savedFromAddress.includes('@') && this.config.emailDomain) { - return !this.fromAddress || (this.fromAddress === `${savedFromAddress}@${this.config.emailDomain}`); - } - return !this.fromAddress || (this.fromAddress === savedFromAddress); - } - get mailgunRegion() { if (!this.settings.get('mailgunBaseUrl')) { return US; @@ -72,11 +40,6 @@ export default class MembersEmailLabs extends Component { }; } - @action - toggleFromAddressConfirmation() { - this.showFromAddressConfirmation = !this.showFromAddressConfirmation; - } - @action setMailgunDomain(event) { this.settings.set('mailgunDomain', event.target.value); @@ -98,11 +61,6 @@ export default class MembersEmailLabs extends Component { this.settings.set('mailgunBaseUrl', region.baseUrl); } - @action - setFromAddress(fromAddress) { - this.setEmailAddress('fromAddress', fromAddress); - } - @action toggleEmailTrackOpens(event) { if (event) { @@ -129,13 +87,6 @@ export default class MembersEmailLabs extends Component { this.recipientsSelectValue = this._getDerivedRecipientsSelectValue(); } - @action - setReplyAddress(event) { - const newReplyAddress = event.value; - - this.settings.set('membersReplyAddress', newReplyAddress); - } - @action setDefaultEmailRecipients(value) { // Update the underlying setting properties to match the selected recipients option @@ -169,24 +120,6 @@ export default class MembersEmailLabs extends Component { this.settings.set('editorDefaultEmailRecipientsFilter', filter); } - @task({drop: true}) - *updateFromAddress() { - let url = this.ghostPaths.url.api('/settings/members/email'); - try { - const response = yield this.ajax.post(url, { - data: { - email: this.fromAddress, - type: 'fromAddressUpdate' - } - }); - this.toggleFromAddressConfirmation(); - return response; - } catch (e) { - // Failed to send email, retry - return false; - } - } - _getDerivedRecipientsSelectValue() { const defaultEmailRecipients = this.settings.get('editorDefaultEmailRecipients'); const defaultEmailRecipientsFilter = this.settings.get('editorDefaultEmailRecipientsFilter'); diff --git a/ghost/admin/app/controllers/settings/members-email-labs.js b/ghost/admin/app/controllers/settings/members-email-labs.js index c73308202f..4eac4cbac1 100644 --- a/ghost/admin/app/controllers/settings/members-email-labs.js +++ b/ghost/admin/app/controllers/settings/members-email-labs.js @@ -1,37 +1,14 @@ import Controller from '@ember/controller'; -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 MembersEmailLabsController extends Controller { - @service config; - @service session; @service settings; - // from/supportAddress are set here so that they can be reset to saved values on save - // to avoid it looking like they've been saved when they have a separate update process - @tracked fromAddress = ''; - @tracked supportAddress = ''; + queryParams = ['verifyEmail']; - @action - setEmailAddress(property, email) { - this[property] = email; - } - - parseEmailAddress(address) { - const emailAddress = address || 'noreply'; - // Adds default domain as site domain - if (emailAddress.indexOf('@') < 0 && this.config.emailDomain) { - return `${emailAddress}@${this.config.emailDomain}`; - } - return emailAddress; - } - - resetEmailAddresses() { - this.fromAddress = this.parseEmailAddress(this.settings.get('membersFromAddress')); - this.supportAddress = this.parseEmailAddress(this.settings.get('membersSupportAddress')); - } + @tracked verifyEmail = null; @task({drop: true}) *saveSettings() { diff --git a/ghost/admin/app/models/newsletter.js b/ghost/admin/app/models/newsletter.js index 1557bdcc77..9a69254add 100644 --- a/ghost/admin/app/models/newsletter.js +++ b/ghost/admin/app/models/newsletter.js @@ -27,4 +27,8 @@ export default class Newsletter extends Model.extend(ValidationEngine) { @attr({defaultValue: 'sans_serif'}) bodyFontCategory; @attr() footerContent; @attr({defaultValue: true}) showBadge; + + // HACK - not a real model attribute but a workaround for Ember Data not + // exposing meta from save responses + @attr _meta; } diff --git a/ghost/admin/app/routes/settings/members-email-labs.js b/ghost/admin/app/routes/settings/members-email-labs.js index 32c9c5a225..9ef9d9c3d0 100644 --- a/ghost/admin/app/routes/settings/members-email-labs.js +++ b/ghost/admin/app/routes/settings/members-email-labs.js @@ -1,5 +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 {action} from '@ember/object'; import {inject as service} from '@ember/service'; @@ -9,30 +10,37 @@ export default class MembersEmailLabsRoute extends AdminRoute { @service notifications; @service settings; + queryParams = { + verifyEmail: { + replace: true + } + }; + confirmModal = null; hasConfirmed = false; - beforeModel(transition) { + beforeModel() { super.beforeModel(...arguments); if (!this.feature.multipleNewsletters) { return this.transitionTo('settings.members-email'); } - - if (transition.to.queryParams?.fromAddressUpdate === 'success') { - this.notifications.showAlert( - `Newsletter email address has been updated`, - {type: 'success', key: 'members.settings.from-address.updated'} - ); - } } model() { return this.settings.reload(); } - setupController(controller) { - controller.resetEmailAddresses(); + afterModel(model, transition) { + if (transition.to.queryParams.verifyEmail) { + this.modals.open(VerifyNewsletterEmail, { + token: transition.to.queryParams.verifyEmail + }); + + // clear query param so it doesn't linger and cause problems re-entering route + transition.abort(); + return this.transitionTo('settings.members-email-labs', {queryParams: {verifyEmail: null}}); + } } @action diff --git a/ghost/admin/app/serializers/newsletter.js b/ghost/admin/app/serializers/newsletter.js new file mode 100644 index 0000000000..d09f69783f --- /dev/null +++ b/ghost/admin/app/serializers/newsletter.js @@ -0,0 +1,27 @@ +/* eslint-disable camelcase */ +import ApplicationSerializer from './application'; + +export default class MemberSerializer extends ApplicationSerializer { + // HACK: Ember Data doesn't expose `meta` properties consistently + // - https://github.com/emberjs/data/issues/2905 + // + // We need the `meta` data returned when saving so we extract it and dump + // it onto the model as an attribute then delete it again when serializing. + normalizeResponse() { + const json = super.normalizeResponse(...arguments); + + if (json.meta && json.data.attributes) { + json.data.attributes._meta = json.meta; + } + + return json; + } + + serialize() { + const json = super.serialize(...arguments); + + delete json._meta; + + return json; + } +} diff --git a/ghost/admin/app/templates/settings/members-email-labs.hbs b/ghost/admin/app/templates/settings/members-email-labs.hbs index 1bcc332c3b..4c2511febf 100644 --- a/ghost/admin/app/templates/settings/members-email-labs.hbs +++ b/ghost/admin/app/templates/settings/members-email-labs.hbs @@ -20,11 +20,7 @@