2
1
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2023-12-13 21:00:40 +01:00

Implemented one-click-subscribe for recommendations (#18067)

fixes https://github.com/TryGhost/Product/issues/3856
This commit is contained in:
Simon Backx 2023-09-11 17:06:15 +02:00 committed by GitHub
parent 8263e34adc
commit f130fb2e85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 109 additions and 10 deletions

View file

@ -625,6 +625,13 @@ export default class App extends React.Component {
}, 2000);
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(`[Portal] Failed to dispatch action: ${action}`, error);
if (data && data.throwErrors) {
throw error;
}
const popupNotification = createPopupNotification({
type: `${action}:failed`,
autoHide: true, closeable: true, status: 'error', state: this.state,

View file

@ -1,3 +1,4 @@
import setupGhostApi from './utils/api';
import {HumanReadableError} from './utils/errors';
import {createPopupNotification, getMemberEmail, getMemberName, getProductCadenceFromPrice, removePortalLinkFromUrl} from './utils/helpers';
@ -474,6 +475,41 @@ async function updateProfile({data, state, api}) {
};
}
async function oneClickSubscribe({data: {siteUrl}, state}) {
const externalSiteApi = setupGhostApi({siteUrl: siteUrl, apiUrl: 'not-defined', contentApiKey: 'not-defined'});
const {t, member} = state;
const referrerUrl = window.location.href;
const referrerSource = window.location.hostname.replace(/^www\./, '');
await externalSiteApi.member.sendMagicLink({
emailType: 'signup',
name: member.name,
email: member.email,
autoRedirect: false,
customUrlHistory: [
{
time: Date.now(),
referrerSource,
referrerMedium: 'Ghost Recommendations',
referrerUrl
}
]
});
return {
popupNotification: createPopupNotification({
type: 'subscribe:success',
autoHide: true,
closeable: true,
duration: 10000,
status: 'success',
state,
message: t(`To complete signup, click the confirmation link in your inbox. If it doesn't arrive within 3 minutes, check your spam folder!`)
})
};
}
const Actions = {
togglePopup,
openPopup,
@ -496,7 +532,8 @@ const Actions = {
checkoutPlan,
updateNewsletterPreference,
showPopupNotification,
removeEmailFromSuppressionList
removeEmailFromSuppressionList,
oneClickSubscribe
};
/** Handle actions in the App, returns updated state */

View file

@ -1,8 +1,9 @@
import AppContext from '../../AppContext';
import {useContext, useState, useEffect} from 'react';
import {useContext, useState, useEffect, useCallback} 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';
export const RecommendationsPageStyles = `
.gh-portal-recommendation-item .gh-portal-list-detail {
@ -13,6 +14,7 @@ export const RecommendationsPageStyles = `
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.gh-portal-recommendation-item-favicon {
@ -64,21 +66,73 @@ const RecommendationIcon = ({title, favicon, featuredImage}) => {
return (<img className="gh-portal-recommendation-item-favicon" src={icon} alt={title} onError={hideIcon} />);
};
const prepareTab = () => {
return window.open('', '_blank');
};
const openTab = (tab, url) => {
if (tab) {
tab.location.href = url;
tab.focus();
} else {
// Probably failed to create a tab
window.location.href = url;
}
};
const RecommendationItem = (recommendation) => {
const {t} = useContext(AppContext);
const {t, onAction, member} = useContext(AppContext);
const {title, url, reason, favicon, one_click_subscribe: oneClickSubscribe, featured_image: featuredImage} = recommendation;
const allowOneClickSubscribe = member && oneClickSubscribe;
const [subscribed, setSubscribed] = useState(false);
const visitHandler = useCallback(() => {
// Open url in a new tab
const tab = window.open(url, '_blank');
tab?.focus();
}, [url]);
const oneClickSubscribeHandler = useCallback(async () => {
// We need to open a tab immediately, otherwise it is not possible to open a tab in case of errors later
// after the async operation is done (browser blocks it outside of user interaction)
const tab = prepareTab();
try {
await onAction('oneClickSubscribe', {
siteUrl: url,
throwErrors: true
});
setSubscribed(true);
tab.close();
} catch (_) {
// Open portal signup page
const signupUrl = new URL('#/portal/signup', url);
// Trigger a visit
openTab(tab, signupUrl);
}
}, [setSubscribed, url]);
const clickHandler = useCallback((e) => {
if (allowOneClickSubscribe) {
oneClickSubscribeHandler(e);
} else {
visitHandler(e);
}
}, [allowOneClickSubscribe, oneClickSubscribeHandler, visitHandler]);
return (
<section className="gh-portal-recommendation-item">
<div className="gh-portal-list-detail gh-portal-list-big">
<div className="gh-portal-recommendation-item-header">
<div className="gh-portal-recommendation-item-header" onClick={visitHandler}>
<RecommendationIcon title={title} favicon={favicon} featuredImage={featuredImage} />
<h3>{title}</h3>
</div>
{reason && <p>{reason}</p>}
</div>
<div>
<a href={url} target="_blank" rel="noopener noreferrer" className="gh-portal-btn gh-portal-btn-list">{oneClickSubscribe ? t('Subscribe') : t('Visit')}</a>
{subscribed && <CheckmarkIcon className='gh-portal-checkmark-icon' alt='' />}
{!subscribed && <button type="button" className="gh-portal-btn gh-portal-btn-list" onClick={clickHandler}>{allowOneClickSubscribe ? t('Subscribe') : t('Visit')}</button>}
</div>
</section>
);
@ -93,8 +147,8 @@ const RecommendationsPage = () => {
useEffect(() => {
api.site.recommendations({limit: 100}).then((data) => {
setRecommendations(
shuffleRecommendations(data.recommendations
));
shuffleRecommendations(data.recommendations)
);
}).catch((err) => {
// eslint-disable-next-line no-console
console.error(err);

View file

@ -231,7 +231,7 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) {
});
},
async sendMagicLink({email, emailType, labels, name, oldEmail, newsletters, redirect}) {
async sendMagicLink({email, emailType, labels, name, oldEmail, newsletters, redirect, customUrlHistory, autoRedirect = true}) {
const url = endpointFor({type: 'members', resource: 'send-magic-link'});
const body = {
name,
@ -241,9 +241,10 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) {
emailType,
labels,
requestSrc: 'portal',
redirect
redirect,
autoRedirect
};
const urlHistory = getUrlHistory();
const urlHistory = customUrlHistory ?? getUrlHistory();
if (urlHistory) {
body.urlHistory = urlHistory;
}