From 562123f06abaa8a559ec38574ae640bd87c5a989 Mon Sep 17 00:00:00 2001 From: Simon Backx Date: Tue, 12 Sep 2023 11:21:37 +0200 Subject: [PATCH] Added attribution to recommendation clicks (#18077) fixes https://github.com/TryGhost/Product/issues/3865 --- apps/portal/src/actions.js | 4 +-- .../components/pages/RecommendationsPage.js | 26 +++++++++++++++---- apps/portal/src/utils/helpers.js | 5 ++++ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/apps/portal/src/actions.js b/apps/portal/src/actions.js index e6fac26ff6..6aafb386b7 100644 --- a/apps/portal/src/actions.js +++ b/apps/portal/src/actions.js @@ -1,6 +1,6 @@ import setupGhostApi from './utils/api'; import {HumanReadableError} from './utils/errors'; -import {createPopupNotification, getMemberEmail, getMemberName, getProductCadenceFromPrice, removePortalLinkFromUrl} from './utils/helpers'; +import {createPopupNotification, getMemberEmail, getMemberName, getProductCadenceFromPrice, removePortalLinkFromUrl, getRefDomain} from './utils/helpers'; function switchPage({data, state}) { return { @@ -480,7 +480,7 @@ async function oneClickSubscribe({data: {siteUrl}, state}) { const {t, member} = state; const referrerUrl = window.location.href; - const referrerSource = window.location.hostname.replace(/^www\./, ''); + const referrerSource = getRefDomain(); await externalSiteApi.member.sendMagicLink({ emailType: 'signup', diff --git a/apps/portal/src/components/pages/RecommendationsPage.js b/apps/portal/src/components/pages/RecommendationsPage.js index b78c291f6e..3d3199857b 100644 --- a/apps/portal/src/components/pages/RecommendationsPage.js +++ b/apps/portal/src/components/pages/RecommendationsPage.js @@ -1,10 +1,11 @@ import AppContext from '../../AppContext'; -import {useContext, useState, useEffect, useCallback} from 'react'; +import {useContext, useState, useEffect, useCallback, useMemo} from 'react'; import CloseButton from '../common/CloseButton'; import {clearURLParams} from '../../utils/notifications'; import LoadingPage from './LoadingPage'; import {ReactComponent as CheckmarkIcon} from '../../images/icons/checkmark-fill.svg'; import {ReactComponent as LoaderIcon} from '../../images/icons/loader.svg'; +import {getRefDomain} from '../../utils/helpers'; export const RecommendationsPageStyles = ` .gh-portal-recommendation-item .gh-portal-list-detail { @@ -95,10 +96,25 @@ const RecommendationItem = (recommendation) => { const [subscribed, setSubscribed] = useState(false); const [loading, setLoading] = useState(false); + const refUrl = useMemo(() => { + try { + const ref = new URL(url); + + if (ref.searchParams.has('ref') || ref.searchParams.has('utm_source') || ref.searchParams.has('source')) { + // Don't overwrite + keep existing source attribution + return url; + } + ref.searchParams.set('ref', getRefDomain()); + return ref.toString(); + } catch (_) { + return url; + } + }, [url]); + const visitHandler = useCallback(() => { // Open url in a new tab - openTab(url); - }, [url]); + openTab(refUrl); + }, [refUrl]); const oneClickSubscribeHandler = useCallback(async () => { try { @@ -110,13 +126,13 @@ const RecommendationItem = (recommendation) => { setSubscribed(true); } catch (_) { // Open portal signup page - const signupUrl = new URL('#/portal/signup', url); + const signupUrl = new URL('#/portal/signup', refUrl); // Trigger a visit openTab(signupUrl); } setLoading(false); - }, [setSubscribed, url]); + }, [setSubscribed, url, refUrl]); const clickHandler = useCallback((e) => { if (loading) { diff --git a/apps/portal/src/utils/helpers.js b/apps/portal/src/utils/helpers.js index e399a6b76f..ee0fbf17b5 100644 --- a/apps/portal/src/utils/helpers.js +++ b/apps/portal/src/utils/helpers.js @@ -259,6 +259,11 @@ export function hasMultipleProducts({site}) { return false; } +export function getRefDomain() { + const referrerSource = window.location.hostname.replace(/^www\./, ''); + return referrerSource; +} + export function hasMultipleProductsFeature({site}) { const {portal_products: portalProducts} = site || {}; return !!portalProducts;