diff --git a/ghost/portal/src/App.js b/ghost/portal/src/App.js index ade61dc220..07cafe5e00 100644 --- a/ghost/portal/src/App.js +++ b/ghost/portal/src/App.js @@ -10,7 +10,7 @@ import * as Fixtures from './utils/fixtures'; import ActionHandler from './actions'; import './App.css'; import NotificationParser from './utils/notifications'; -import {createPopupNotification, getCurrencySymbol, getFirstpromoterId, getProductFromId, getQueryPrice, getSiteDomain, isComplimentaryMember, isInviteOnlySite, isSentryEventAllowed, removePortalLinkFromUrl} from './utils/helpers'; +import {createPopupNotification, getAvailablePrices, getCurrencySymbol, getFirstpromoterId, getProductFromId, getQueryPrice, getSiteDomain, isComplimentaryMember, isInviteOnlySite, isSentryEventAllowed, removePortalLinkFromUrl} from './utils/helpers'; const handleDataAttributes = require('./data-attributes'); const React = require('react'); @@ -335,6 +335,7 @@ export default class App extends React.Component { fetchLinkData() { const productMonthlyPriceQueryRegex = /^(?:(\w+?))?\/monthly$/; const productYearlyPriceQueryRegex = /^(?:(\w+?))?\/monthly$/; + const offersRegex = /^offers\/(\w+?)\/?$/; const [path] = window.location.hash.substr(1).split('?'); const linkRegex = /^\/portal\/?(?:\/(\w+(?:\/\w+)*))?\/?$/; if (path && linkRegex.test(path)) { @@ -344,7 +345,8 @@ export default class App extends React.Component { const showPopup = ( ['monthly', 'yearly'].includes(pageQuery) || productMonthlyPriceQueryRegex.test(pageQuery) || - productYearlyPriceQueryRegex.test(pageQuery) + productYearlyPriceQueryRegex.test(pageQuery) || + offersRegex.test(pageQuery) ) ? false : true; return { showPopup, @@ -515,12 +517,28 @@ export default class App extends React.Component { this.setState(updatedState); } + handleOfferQuery({site, offerId}) { + removePortalLinkFromUrl(); + const prices = getAvailablePrices({site}); + const priceId = prices?.[0]?.id; + if (this.state.member) { + this.dispatchAction('checkoutPlan', {plan: priceId, offerId}); + } else { + this.dispatchAction('signup', {plan: priceId, offerId}); + } + } + /** Handle direct signup link for a price */ handleSignupQuery({site, pageQuery}) { const productMonthlyPriceQueryRegex = /^(?:(\w+?))?\/monthly$/; const productYearlyPriceQueryRegex = /^(?:(\w+?))?\/monthly$/; + const offerQueryRegex = /^offers\/(\w+?)\/?$/; let priceId = pageQuery; - if (productMonthlyPriceQueryRegex.test(pageQuery || '')) { + if (offerQueryRegex.test(pageQuery || '')) { + const [, offerId] = pageQuery.match(offerQueryRegex); + this.handleOfferQuery({site, offerId}); + return; + } else if (productMonthlyPriceQueryRegex.test(pageQuery || '')) { const [, productId] = pageQuery.match(productMonthlyPriceQueryRegex); const product = getProductFromId({site, productId}); priceId = product?.monthlyPrice?.id; @@ -544,7 +562,12 @@ export default class App extends React.Component { const customPricesSignupRegex = /^signup\/?(?:\/(\w+?))?\/?$/; const customMonthlyProductSignup = /^signup\/?(?:\/(\w+?))\/monthly\/?$/; const customYearlyProductSignup = /^signup\/?(?:\/(\w+?))\/yearly\/?$/; - if (path === 'signup') { + const customOfferRegex = /^offers\/(\w+?)\/?$/; + if (customOfferRegex.test(path)) { + return { + pageQuery: path + }; + } else if (path === 'signup') { return { page: 'signup' }; diff --git a/ghost/portal/src/actions.js b/ghost/portal/src/actions.js index 39196e11d6..1c4200c51f 100644 --- a/ghost/portal/src/actions.js +++ b/ghost/portal/src/actions.js @@ -93,11 +93,11 @@ async function signin({data, api, state}) { async function signup({data, state, api}) { try { - const {plan, email, name} = data; + const {plan, email, name, offerId} = data; if (plan.toLowerCase() === 'free') { await api.member.sendMagicLink(data); } else { - await api.member.checkoutPlan({plan, email, name}); + await api.member.checkoutPlan({plan, email, name, offerId}); } return { page: 'magiclink', @@ -116,9 +116,10 @@ async function signup({data, state, api}) { async function checkoutPlan({data, state, api}) { try { - const {plan} = data; + const {plan, offerId} = data; await api.member.checkoutPlan({ plan, + offerId, metadata: { checkoutType: 'upgrade' } diff --git a/ghost/portal/src/utils/api.js b/ghost/portal/src/utils/api.js index d982b449ed..5af738610a 100644 --- a/ghost/portal/src/utils/api.js +++ b/ghost/portal/src/utils/api.js @@ -195,7 +195,7 @@ function setupGhostApi({siteUrl = window.location.origin}) { }); }, - async checkoutPlan({plan, cancelUrl, successUrl, email: customerEmail, name, metadata = {}} = {}) { + async checkoutPlan({plan, cancelUrl, successUrl, email: customerEmail, name, offerId, metadata = {}} = {}) { const siteUrlObj = new URL(siteUrl); const identity = await api.member.identity(); const url = endpointFor({type: 'members', resource: 'create-stripe-checkout-session'}); @@ -229,6 +229,7 @@ function setupGhostApi({siteUrl = window.location.origin}) { }, body: JSON.stringify({ priceId: plan, + offerId, identity: identity, metadata: metadataObj, successUrl,