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