0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

Added CTA popup to comments like button (#21484)

REF PLG-246
- Whenever a user clicks the like button and they are not allowed to
like comments (either not signed in or not a paid member) we now show a
CTA popup asking them to sign in or upgrade to a paid membership.

---------

Co-authored-by: Kevin Ansfield <kevin@ghost.org>
This commit is contained in:
Sanne de Vries 2024-11-04 11:29:31 +01:00 committed by GitHub
parent c9f5b72b3c
commit 73884e8f69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 82 additions and 12 deletions

View file

@ -6,7 +6,7 @@ type Props = {
isPaid: boolean
};
const CTABox: React.FC<Props> = ({isFirst, isPaid}) => {
const {accentColor, commentCount, publication, member, t} = useAppContext();
const {accentColor, publication, member, t} = useAppContext();
const buttonStyle = {
backgroundColor: accentColor
@ -31,7 +31,7 @@ const CTABox: React.FC<Props> = ({isFirst, isPaid}) => {
));
return (
<section className={`flex flex-col items-center pt-[40px] ${member ? 'pb-[32px]' : 'pb-[48px]'} ${!isFirst && 'mt-4'} ${!member && commentCount ? 'border-t' : 'border-none'} border-[rgba(0,0,0,0.075)] sm:px-8 dark:border-[rgba(255,255,255,0.1)]`} data-testid="cta-box">
<>
<h1 className={`mb-[8px] text-center font-sans text-2xl tracking-tight text-black dark:text-[rgba(255,255,255,0.85)] ${isFirst ? 'font-semibold' : 'font-bold'}`}>
{titleText}
</h1>
@ -45,7 +45,7 @@ const CTABox: React.FC<Props> = ({isFirst, isPaid}) => {
<span className='mr-1 inline-block text-[15px]'>{t('Already a member?')}</span>
<button className="rounded-md text-sm font-semibold transition-all hover:opacity-90" data-testid="signin-button" style={linkStyle} type="button" onClick={handleSignInClick}>{t('Sign in')}</button>
</p>)}
</section>
</>
);
};

View file

@ -40,8 +40,7 @@ const Content = () => {
const isPaidOnly = commentsEnabled === 'paid';
const isPaidMember = member && !!member.paid;
// const showCTA = !member || (isPaidOnly && !isPaidMember);
const isFirst = pagination?.total === 0;
const hasOpenReplyForms = secundaryFormCount > 0;
return (
@ -49,7 +48,13 @@ const Content = () => {
<>
<ContentTitle count={commentCount} showCount={showCount} title={title}/>
<div>
{member ? (isPaidMember || !isPaidOnly ? <MainForm commentsCount={commentCount} /> : <CTABox isFirst={pagination?.total === 0} isPaid={isPaidOnly} />) : <CTABox isFirst={pagination?.total === 0} isPaid={isPaidOnly} />}
{(member && (isPaidMember || !isPaidOnly)) ? (
<MainForm commentsCount={commentCount} />
) : (
<section className="flex flex-col items-center py-6 sm:px-8 sm:py-10" data-testid="cta-box">
<CTABox isFirst={isFirst} isPaid={isPaidOnly} />
</section>
)}
</div>
<div className="z-20 mb-7 mt-3">
<span className="flex items-center gap-1.5 text-sm font-medium text-neutral-900 dark:text-neutral-100">
@ -73,7 +78,13 @@ const Content = () => {
</div>
<div>
{!hasOpenReplyForms
? (member ? (isPaidMember || !isPaidOnly ? <MainForm commentsCount={commentCount} /> : <CTABox isFirst={pagination?.total === 0} isPaid={isPaidOnly} />) : <CTABox isFirst={pagination?.total === 0} isPaid={isPaidOnly} />)
? (member ? (isPaidMember || !isPaidOnly ? <MainForm commentsCount={commentCount} /> :
<section className={`flex flex-col items-center pt-[40px] ${member ? 'pb-[32px]' : 'pb-[48px]'} ${!isFirst && 'mt-4'} ${(!member || (member && isPaidOnly)) && commentCount ? 'border-t' : 'border-none'} border-[rgba(0,0,0,0.075)] sm:px-8 dark:border-[rgba(255,255,255,0.1)]`} data-testid="cta-box">
<CTABox isFirst={isFirst} isPaid={isPaidOnly} />
</section>) :
<section className={`flex flex-col items-center pt-[40px] ${member ? 'pb-[32px]' : 'pb-[48px]'} ${!isFirst && 'mt-4'} ${(!member || (member && isPaidOnly)) && commentCount ? 'border-t' : 'border-none'} border-[rgba(0,0,0,0.075)] sm:px-8 dark:border-[rgba(255,255,255,0.1)]`} data-testid="cta-box">
<CTABox isFirst={isFirst} isPaid={isPaidOnly} />
</section>)
: null
}
</div>

View file

@ -1,4 +1,4 @@
import {Comment, useAppContext} from '../../../AppContext';
import {Comment, useAppContext, useLabs} from '../../../AppContext';
import {ReactComponent as LikeIcon} from '../../../images/icons/like.svg';
import {useState} from 'react';
@ -7,6 +7,7 @@ type Props = {
};
const LikeButton: React.FC<Props> = ({comment}) => {
const {dispatchAction, member, commentsEnabled} = useAppContext();
const labs = useLabs();
const [animationClass, setAnimation] = useState('');
const paidOnly = commentsEnabled === 'paid';
@ -14,7 +15,12 @@ const LikeButton: React.FC<Props> = ({comment}) => {
const canLike = member && (isPaidMember || !paidOnly);
const toggleLike = () => {
if (!canLike) {
if (!canLike && labs && labs.commentImprovements) {
dispatchAction('openPopup', {
type: 'ctaPopup'
});
return;
} else if (!canLike) {
return;
}
@ -37,6 +43,26 @@ const LikeButton: React.FC<Props> = ({comment}) => {
likeCursor = 'cursor-text';
}
if (labs && labs.commentImprovements) {
return (
<button
className={`duration-50 group flex cursor-pointer items-center font-sans text-base outline-0 transition-all ease-linear sm:text-sm ${
comment.liked ? 'text-black/90 dark:text-white/90' : 'text-black/50 hover:text-black/75 dark:text-white/60 dark:hover:text-white/75'
}`}
data-testid="like-button"
type="button"
onClick={toggleLike}
>
<LikeIcon
className={animationClass + ` mr-[6px] ${
comment.liked ? 'fill-black dark:fill-white stroke-black dark:stroke-white' : 'stroke-black/50 group-hover:stroke-black/75 dark:stroke-white/60 dark:group-hover:stroke-white/75'
} ${!comment.liked && canLike && 'group-hover:stroke-black/75 dark:group-hover:stroke-white/75'} transition duration-50 ease-linear`}
/>
{comment.count.likes}
</button>
);
}
return (
<CustomTag className={`duration-50 group flex items-center font-sans text-base outline-0 transition-all ease-linear sm:text-sm ${comment.liked ? 'text-black/90 dark:text-white/90' : 'text-black/50 hover:text-black/75 dark:text-white/60 dark:hover:text-white/75'} ${likeCursor}`} data-testid="like-button" type="button" onClick={toggleLike}>
<LikeIcon className={animationClass + ` mr-[6px] ${comment.liked ? 'fill-black dark:fill-white stroke-black dark:stroke-white' : 'stroke-black/50 group-hover:stroke-black/75 dark:stroke-white/60 dark:group-hover:stroke-white/75'} ${!comment.liked && canLike && 'group-hover:stroke-black/75 dark:group-hover:stroke-white/75'} transition duration-50 ease-linear`} />

View file

@ -0,0 +1,31 @@
import CTABox from '../content/CTABox';
import CloseButton from './CloseButton';
import {useAppContext} from '../../AppContext';
const CTAPopup = () => {
const {dispatchAction, member, commentsEnabled} = useAppContext();
const stopPropagation = (event: React.MouseEvent) => {
event.stopPropagation();
};
const close = () => {
dispatchAction('closePopup', {});
};
const paidOnly = commentsEnabled === 'paid';
const isFirst = !member;
return (
<div className="shadow-modal relative h-screen w-screen rounded-none bg-white p-[28px] text-center sm:h-auto sm:w-[500px] sm:rounded-xl sm:p-8 sm:text-left" onClick={close} onMouseDown={stopPropagation}>
<div className="flex h-full flex-col justify-center pt-10 sm:justify-normal sm:pt-0">
<div className="flex flex-col items-center pb-3 pt-6" data-testid="cta-box">
<CTABox isFirst={isFirst} isPaid={paidOnly} />
</div>
<CloseButton close={close} />
</div>
</div>
);
};
export default CTAPopup;

View file

@ -6,7 +6,7 @@ type Props = {
}
const CloseButton: React.FC<Props> = ({close}) => {
return (
<button className="absolute right-6 top-6 opacity-30 transition-opacity duration-100 ease-out hover:opacity-40 sm:right-8 sm:top-9" type="button" onClick={close}>
<button className="absolute right-6 top-5 opacity-30 transition-opacity duration-100 ease-out hover:opacity-40 sm:right-8 sm:top-9" type="button" onClick={close}>
<CloseIcon className="h-5 w-5 p-1 pr-0" />
</button>
);

View file

@ -45,7 +45,7 @@ const GenericPopup: React.FC<Props> = ({show, children, title, callback}) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="to-rgba(0,0,0,0.1) fixed left-0 top-0 flex h-screen w-screen justify-center overflow-hidden bg-gradient-to-b from-[rgba(0,0,0,0.2)] pt-0 backdrop-blur-[2px] sm:pt-12" onMouseDown={close}>
<div className="to-rgba(0,0,0,0.1) fixed left-0 top-0 flex h-screen w-screen justify-center overflow-hidden bg-gradient-to-b from-[rgba(0,0,0,0.2)] pt-0 backdrop-blur-[2px] sm:pt-[4vmin]" onMouseDown={close}>
<Transition.Child
enter="transition duration-200 delay-150 linear"
enterFrom="translate-y-4 opacity-0"

View file

@ -1,4 +1,5 @@
import AddDetailsPopup from './components/popups/AddDetailsPopup';
import CTAPopup from './components/popups/CTAPopup';
import React from 'react';
import ReportPopup from './components/popups/ReportPopup';
@ -7,7 +8,8 @@ import ReportPopup from './components/popups/ReportPopup';
*/
export const Pages = {
addDetailsPopup: AddDetailsPopup,
reportPopup: ReportPopup
reportPopup: ReportPopup,
ctaPopup: CTAPopup
};
export type PageName = keyof typeof Pages;