diff --git a/ghost/admin/.lint-todo b/ghost/admin/.lint-todo index 44ad70613c..3e598c38b5 100644 --- a/ghost/admin/.lint-todo +++ b/ghost/admin/.lint-todo @@ -1009,3 +1009,4 @@ remove|ember-template-lint|no-action|34|39|34|39|e65f48edccba27e52c1f8358a9795dc remove|ember-template-lint|no-action|50|35|50|35|7432725bd18c48f69bf22dc9487d14d25dc6c1b7|1662681600000|1673053200000|1678237200000|app/templates/signin.hbs remove|ember-template-lint|no-passed-in-event-handlers|33|28|33|28|5b371baf419f247953b91b626611cb831c524af3|1662681600000|1673053200000|1678237200000|app/templates/signin.hbs remove|ember-template-lint|no-passed-in-event-handlers|50|28|50|28|40caf07c7cebf6f4321c5b7e7f2f426b5c30217b|1662681600000|1673053200000|1678237200000|app/templates/signin.hbs +remove|ember-template-lint|require-iframe-title|1|0|1|0|d1c9631d150af53ca33b16c8c280c9d815bf43da|1662681600000|1673053200000|1678237200000|app/components/gh-billing-iframe.hbs diff --git a/ghost/admin/app/components/gh-billing-iframe.hbs b/ghost/admin/app/components/gh-billing-iframe.hbs index 9ba092062c..5a0ed00241 100644 --- a/ghost/admin/app/components/gh-billing-iframe.hbs +++ b/ghost/admin/app/components/gh-billing-iframe.hbs @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/ghost/admin/app/components/gh-billing-iframe.js b/ghost/admin/app/components/gh-billing-iframe.js index 05dd46e471..dd137c99a7 100644 --- a/ghost/admin/app/components/gh-billing-iframe.js +++ b/ghost/admin/app/components/gh-billing-iframe.js @@ -1,10 +1,10 @@ -import Component from '@ember/component'; -import classic from 'ember-classic-decorator'; +import Component from '@glimmer/component'; +import {action} from '@ember/object'; import {htmlSafe} from '@ember/template'; import {inject} from 'ghost-admin/decorators/inject'; import {inject as service} from '@ember/service'; +import {tracked} from '@glimmer/tracking'; -@classic export default class GhBillingIframe extends Component { @service billing; @service ghostPaths; @@ -13,36 +13,46 @@ export default class GhBillingIframe extends Component { @inject config; - isOwner = null; - fetchingSubscription = false; + @tracked isOwner = null; + @tracked fetchingSubscription = false; - didInsertElement() { - super.didInsertElement(...arguments); + willDestroy() { + super.willDestroy(...arguments); + window.removeEventListener('message', this.handleIframeMessage); + } + @action + setup() { this.billing.getBillingIframe().src = this.billing.getIframeURL(); + window.addEventListener('message', this.handleIframeMessage); + } - window.addEventListener('message', (event) => { - // only process messages coming from the billing iframe - if (event?.data && this.billing.getIframeURL().includes(event?.origin)) { - if (event.data?.request === 'token') { - this._handleTokenRequest(); - } + @action + async handleIframeMessage(event) { + if (this.isDestroyed || this.isDestroying) { + return; + } - if (event.data?.request === 'forceUpgradeInfo') { - this._handleForceUpgradeRequest(); - } - - if (event.data?.subscription) { - this._handleSubscriptionUpdate(event.data); - } + // only process messages coming from the billing iframe + if (event?.data && this.billing.getIframeURL().includes(event?.origin)) { + if (event.data?.request === 'token') { + this._handleTokenRequest(); } - }); + + if (event.data?.request === 'forceUpgradeInfo') { + this._handleForceUpgradeRequest(); + } + + if (event.data?.subscription) { + this._handleSubscriptionUpdate(event.data); + } + } } _handleTokenRequest() { - this.set('fetchingSubscription', false); + this.fetchingSubscription = false; let token; - const ghostIdentityUrl = this.get('ghostPaths.url').api('identities'); + const ghostIdentityUrl = this.ghostPaths.url.api('identities'); this.ajax.request(ghostIdentityUrl).then((response) => { token = response && response.identities && response.identities[0] && response.identities[0].token; @@ -51,11 +61,11 @@ export default class GhBillingIframe extends Component { response: token }, '*'); - this.set('isOwner', true); + this.isOwner = true; }).catch((error) => { if (error.payload?.errors && error.payload.errors[0]?.type === 'NoPermissionError') { // no permission means the current user requesting the token is not the owner of the site. - this.set('isOwner', false); + this.isOwner = false; // Avoid letting the BMA waiting for a message and send an empty token response instead this.billing.getBillingIframe().contentWindow.postMessage({ @@ -69,8 +79,8 @@ export default class GhBillingIframe extends Component { // NOTE: the handler is placed here to avoid additional logic to check if iframe has loaded // receiving a 'token' request is an indication that page is ready - if (!this.fetchingSubscription && !this.billing.get('subscription') && token) { - this.set('fetchingSubscription', true); + if (!this.fetchingSubscription && !this.billing.subscription && token) { + this.fetchingSubscription = true; this.billing.getBillingIframe().contentWindow.postMessage({ query: 'getSubscription', response: 'subscription' @@ -100,8 +110,8 @@ export default class GhBillingIframe extends Component { } _handleSubscriptionUpdate(data) { - this.billing.set('subscription', data.subscription); - this.billing.set('checkoutRoute', data?.checkoutRoute || '/plans'); + this.billing.subscription = data.subscription; + this.billing.checkoutRoute = data?.checkoutRoute ?? '/plans'; if (data.subscription.status === 'active' && this.config.hostSettings?.forceUpgrade) { // config might not be updated after a subscription has been set to active. @@ -126,7 +136,7 @@ export default class GhBillingIframe extends Component { ) { // The action param will be picked up on a transition from the router and can // then send the destination route as a message to the BMA, which then handles the redirect. - const checkoutAction = this.billing.get('billingRouteRoot') + '?action=checkout'; + const checkoutAction = this.billing.billingRouteRoot + '?action=checkout'; this.notifications.showAlert(htmlSafe(`Your audience has grown! To continue publishing, the site owner must confirm pricing for this number of members here`), {type: 'warn', key: 'billing.exceeded'}); } else { diff --git a/ghost/admin/app/routes/pro.js b/ghost/admin/app/routes/pro.js index 20af600d7a..3b61b6f39e 100644 --- a/ghost/admin/app/routes/pro.js +++ b/ghost/admin/app/routes/pro.js @@ -21,12 +21,12 @@ export default class ProRoute extends AuthenticatedRoute { return this.transitionTo('home'); } - this.billing.set('previousTransition', transition); + this.billing.previousTransition = transition; } model(params) { if (params.action) { - this.billing.set('action', params.action); + this.billing.action = params.action; } this.billing.toggleProWindow(true); diff --git a/ghost/admin/app/services/billing.js b/ghost/admin/app/services/billing.js index 09358150e2..a14ef0c6c7 100644 --- a/ghost/admin/app/services/billing.js +++ b/ghost/admin/app/services/billing.js @@ -1,8 +1,7 @@ import Service, {inject as service} from '@ember/service'; -import classic from 'ember-classic-decorator'; import {inject} from 'ghost-admin/decorators/inject'; +import {tracked} from '@glimmer/tracking'; -@classic export default class BillingService extends Service { @service ghostPaths; @service router; @@ -11,14 +10,15 @@ export default class BillingService extends Service { @inject config; billingRouteRoot = '#/pro'; - billingWindowOpen = false; - subscription = null; - previousRoute = null; - action = null; - ownerUser = null; - init() { - super.init(...arguments); + @tracked billingWindowOpen = false; + @tracked subscription = null; + @tracked previousRoute = null; + @tracked action = null; + @tracked ownerUser = null; + + constructor() { + super(...arguments); if (this.config.hostSettings?.billing?.url) { window.addEventListener('message', (event) => { @@ -70,7 +70,7 @@ export default class BillingService extends Service { await this.store.findAll('user', {reload: true}); user = this.store.peekAll('user').findBy('isOwnerOnly', true); } - this.set('ownerUser', user); + this.ownerUser = user; } return this.ownerUser; } @@ -88,7 +88,7 @@ export default class BillingService extends Service { }, '*'); } - this.set('action', null); + this.action = null; } } @@ -103,7 +103,7 @@ export default class BillingService extends Service { this.sendRouteUpdate(); - this.set('billingWindowOpen', value); + this.billingWindowOpen = value; } // Controls navigation to billing window modal which is triggered from the application UI. @@ -119,7 +119,7 @@ export default class BillingService extends Service { return; } - this.set('previousRoute', currentRoute); + this.previousRoute = currentRoute; // Ensures correct "getIframeURL" calculation when syncing iframe location // in toggleProWindow