mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Restricted actions for logged in members and paid members
refs https://github.com/TryGhost/Team/issues/1693 - Added a new data attribute to the injected stript tag: `data-comments-enabled`. This contains the commentsEnabled setting, and can be 'all' or 'paid' (when it is off the comments section is never injected). - Added a new component `<NotPaidBox>`, which is visible when a member is signed in but doesn't have paid access to comment - Prevented clicking the reply and like buttons when a member doesn't have access
This commit is contained in:
parent
dd5a4bb35e
commit
2657af11f6
7 changed files with 72 additions and 25 deletions
|
@ -282,6 +282,7 @@ export default class App extends React.Component {
|
||||||
colorScheme: this.props.colorScheme,
|
colorScheme: this.props.colorScheme,
|
||||||
avatarSaturation: this.props.avatarSaturation,
|
avatarSaturation: this.props.avatarSaturation,
|
||||||
accentColor: this.props.accentColor,
|
accentColor: this.props.accentColor,
|
||||||
|
commentsEnabled: this.props.commentsEnabled,
|
||||||
dispatchAction: (_action, data) => this.dispatchAction(_action, data),
|
dispatchAction: (_action, data) => this.dispatchAction(_action, data),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,7 +21,7 @@ const Comment = (props) => {
|
||||||
setIsInReplyMode(current => !current);
|
setIsInReplyMode(current => !current);
|
||||||
};
|
};
|
||||||
|
|
||||||
const {admin, avatarSaturation} = useContext(AppContext);
|
const {admin, avatarSaturation, member, commentsEnabled} = useContext(AppContext);
|
||||||
const comment = props.comment;
|
const comment = props.comment;
|
||||||
const hasReplies = comment.replies && comment.replies.length > 0;
|
const hasReplies = comment.replies && comment.replies.length > 0;
|
||||||
const isNotPublished = comment.status !== 'published';
|
const isNotPublished = comment.status !== 'published';
|
||||||
|
@ -35,6 +35,10 @@ const Comment = (props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const paidOnly = commentsEnabled === 'paid';
|
||||||
|
const isPaidMember = member && !!member.paid;
|
||||||
|
const canReply = member && (isPaidMember || !paidOnly);
|
||||||
|
|
||||||
if (isInEditMode) {
|
if (isInEditMode) {
|
||||||
return (
|
return (
|
||||||
<Form comment={comment} toggle={toggleEditMode} parent={props.parent} isEdit={true} avatarSaturation={props.avatarSaturation} />
|
<Form comment={comment} toggle={toggleEditMode} parent={props.parent} isEdit={true} avatarSaturation={props.avatarSaturation} />
|
||||||
|
@ -55,7 +59,7 @@ const Comment = (props) => {
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-14 flex gap-5 items-center">
|
<div className="ml-14 flex gap-5 items-center">
|
||||||
<Like comment={comment} />
|
<Like comment={comment} />
|
||||||
{(isNotPublished || !props.parent) && <Reply comment={comment} toggleReply={toggleReplyMode} isReplying={isInReplyMode} />}
|
{canReply && (isNotPublished || !props.parent) && <Reply comment={comment} toggleReply={toggleReplyMode} isReplying={isInReplyMode} />}
|
||||||
<div className="text-sm text-neutral-400 dark:text-[rgba(255,255,255,0.5)] font-sans">{formatRelativeTime(comment.created_at)}</div>
|
<div className="text-sm text-neutral-400 dark:text-[rgba(255,255,255,0.5)] font-sans">{formatRelativeTime(comment.created_at)}</div>
|
||||||
<More comment={comment} toggleEdit={toggleEditMode} />
|
<More comment={comment} toggleEdit={toggleEditMode} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import Loading from './Loading';
|
||||||
import Form from './Form';
|
import Form from './Form';
|
||||||
import Comment from './Comment';
|
import Comment from './Comment';
|
||||||
import Pagination from './Pagination';
|
import Pagination from './Pagination';
|
||||||
|
import NotPaidBox from './NotPaidBox';
|
||||||
|
|
||||||
const CommentsBox = (props) => {
|
const CommentsBox = (props) => {
|
||||||
const luminance = (r, g, b) => {
|
const luminance = (r, g, b) => {
|
||||||
|
@ -40,7 +41,7 @@ const CommentsBox = (props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const {accentColor, pagination, member, comments} = useContext(AppContext);
|
const {accentColor, pagination, member, comments, commentsEnabled} = useContext(AppContext);
|
||||||
|
|
||||||
const commentsElements = comments.slice().reverse().map(comment => <Comment comment={comment} key={comment.id} avatarSaturation={props.avatarSaturation} />);
|
const commentsElements = comments.slice().reverse().map(comment => <Comment comment={comment} key={comment.id} avatarSaturation={props.avatarSaturation} />);
|
||||||
|
|
||||||
|
@ -50,6 +51,9 @@ const CommentsBox = (props) => {
|
||||||
'--gh-accent-color': accentColor ?? 'blue'
|
'--gh-accent-color': accentColor ?? 'blue'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const paidOnly = commentsEnabled === 'paid';
|
||||||
|
const isPaidMember = member && !!member.paid;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={'ghost-display ' + containerClass} style={style}>
|
<section className={'ghost-display ' + containerClass} style={style}>
|
||||||
<Pagination />
|
<Pagination />
|
||||||
|
@ -59,7 +63,7 @@ const CommentsBox = (props) => {
|
||||||
{commentsElements}
|
{commentsElements}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{ member ? <Form commentsCount={commentsCount} avatarSaturation={props.avatarSaturation} /> : <NotSignedInBox /> }
|
{ member ? (isPaidMember || !paidOnly ? <Form commentsCount={commentsCount} avatarSaturation={props.avatarSaturation} /> : <NotPaidBox />) : <NotSignedInBox /> }
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
: <Loading />}
|
: <Loading />}
|
||||||
|
|
|
@ -3,33 +3,42 @@ import {ReactComponent as LikeIcon} from '../images/icons/like.svg';
|
||||||
import AppContext from '../AppContext';
|
import AppContext from '../AppContext';
|
||||||
|
|
||||||
function Like(props) {
|
function Like(props) {
|
||||||
const {onAction, member} = useContext(AppContext);
|
const {dispatchAction, member, commentsEnabled} = useContext(AppContext);
|
||||||
const [animationClass, setAnimation] = useState('');
|
const [animationClass, setAnimation] = useState('');
|
||||||
|
|
||||||
let likeCursor = 'cursor-pointer';
|
const paidOnly = commentsEnabled === 'paid';
|
||||||
if (!member) {
|
const isPaidMember = member && !!member.paid;
|
||||||
likeCursor = 'cursor-text';
|
const canLike = member && (isPaidMember || !paidOnly);
|
||||||
}
|
|
||||||
|
|
||||||
const toggleLike = () => {
|
const toggleLike = () => {
|
||||||
if (member) {
|
if (!canLike) {
|
||||||
if (!props.comment.liked) {
|
return;
|
||||||
onAction('likeComment', props.comment);
|
}
|
||||||
setAnimation('animate-heartbeat');
|
|
||||||
setTimeout(() => {
|
if (!props.comment.liked) {
|
||||||
setAnimation('');
|
dispatchAction('likeComment', props.comment);
|
||||||
}, 400);
|
setAnimation('animate-heartbeat');
|
||||||
} else {
|
setTimeout(() => {
|
||||||
onAction('unlikeComment', props.comment);
|
setAnimation('');
|
||||||
}
|
}, 400);
|
||||||
|
} else {
|
||||||
|
dispatchAction('unlikeComment', props.comment);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If can like: use <button> element, otherwise use a <span>
|
||||||
|
const CustomTag = canLike ? `button` : `span`;
|
||||||
|
|
||||||
|
let likeCursor = 'cursor-pointer';
|
||||||
|
if (!canLike) {
|
||||||
|
likeCursor = 'cursor-text';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className={`flex font-sans items-center text-sm ${props.comment.liked ? 'text-neutral-900 dark:text-[rgba(255,255,255,0.9)]' : 'text-neutral-400 dark:text-[rgba(255,255,255,0.5)]'} ${likeCursor}`} onClick={toggleLike}>
|
<CustomTag className={`flex font-sans items-center text-sm ${props.comment.liked ? 'text-neutral-900 dark:text-[rgba(255,255,255,0.9)]' : 'text-neutral-400 dark:text-[rgba(255,255,255,0.5)]'} ${likeCursor}`} onClick={toggleLike}>
|
||||||
<LikeIcon className={animationClass + ` mr-[6px] ${props.comment.liked ? 'fill-neutral-900 stroke-neutral-900 dark:fill-white dark:stroke-white' : 'stroke-neutral-400 dark:stroke-[rgba(255,255,255,0.5)]'}`} />
|
<LikeIcon className={animationClass + ` mr-[6px] ${props.comment.liked ? 'fill-neutral-900 stroke-neutral-900 dark:fill-white dark:stroke-white' : 'stroke-neutral-400 dark:stroke-[rgba(255,255,255,0.5)]'}`} />
|
||||||
{props.comment.likes_count}
|
{props.comment.likes_count}
|
||||||
</button>
|
</CustomTag>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
28
apps/comments-ui/src/components/NotPaidBox.js
Normal file
28
apps/comments-ui/src/components/NotPaidBox.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import {useContext} from 'react';
|
||||||
|
import AppContext from '../AppContext';
|
||||||
|
|
||||||
|
const NotPaidBox = (props) => {
|
||||||
|
const {accentColor} = useContext(AppContext);
|
||||||
|
|
||||||
|
const boxStyle = {
|
||||||
|
background: accentColor
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonStyle = {
|
||||||
|
color: accentColor
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="text-center mb-1 bg-neutral-900 rounded-lg pt-12 pb-10 px-8" style={boxStyle}>
|
||||||
|
<h1 className="text-center text-white text-[28px] font-sans font-semibold mb-6 tracking-tight">Want to join the discussion?</h1>
|
||||||
|
<a className="bg-white font-sans py-3 px-4 mb-6 rounded inline-block font-medium" style={buttonStyle} href="#/portal/signup">
|
||||||
|
Subscribe now
|
||||||
|
</a>
|
||||||
|
<p className="font-sans text-center text-white">
|
||||||
|
You need to be subscribed to a paid plan to be able to join the discussion.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotPaidBox;
|
|
@ -37,8 +37,9 @@ function getSiteData() {
|
||||||
const avatarSaturation = scriptTag.dataset.avatarSaturation;
|
const avatarSaturation = scriptTag.dataset.avatarSaturation;
|
||||||
const accentColor = scriptTag.dataset.accentColor;
|
const accentColor = scriptTag.dataset.accentColor;
|
||||||
const appVersion = scriptTag.dataset.appVersion;
|
const appVersion = scriptTag.dataset.appVersion;
|
||||||
|
const commentsEnabled = scriptTag.dataset.commentsEnabled;
|
||||||
|
|
||||||
return {siteUrl, apiKey, apiUrl, sentryDsn, postId, adminUrl, colorScheme, avatarSaturation, accentColor, appVersion};
|
return {siteUrl, apiKey, apiUrl, sentryDsn, postId, adminUrl, colorScheme, avatarSaturation, accentColor, appVersion, commentsEnabled};
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -58,13 +59,13 @@ function setup({siteUrl}) {
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
// const customSiteUrl = getSiteUrl();
|
// const customSiteUrl = getSiteUrl();
|
||||||
const {siteUrl: customSiteUrl, sentryDsn, postId, adminUrl, colorScheme, avatarSaturation, accentColor, appVersion} = getSiteData();
|
const {siteUrl: customSiteUrl, ...siteData} = getSiteData();
|
||||||
const siteUrl = customSiteUrl || window.location.origin;
|
const siteUrl = customSiteUrl || window.location.origin;
|
||||||
setup({siteUrl});
|
setup({siteUrl});
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
{<App appVersion={appVersion} adminUrl={adminUrl} siteUrl={siteUrl} customSiteUrl={customSiteUrl} sentryDsn={sentryDsn} postId={postId} colorScheme={colorScheme} avatarSaturation={avatarSaturation} accentColor={accentColor} />}
|
{<App siteUrl={siteUrl} customSiteUrl={customSiteUrl} {...siteData} />}
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById(ROOT_DIV_ID)
|
document.getElementById(ROOT_DIV_ID)
|
||||||
);
|
);
|
||||||
|
|
|
@ -109,6 +109,6 @@ export function getBundledCssLink({appVersion}) {
|
||||||
if (process.env.NODE_ENV === 'production' && appVersion) {
|
if (process.env.NODE_ENV === 'production' && appVersion) {
|
||||||
return `https://unpkg.com/@tryghost/comments-ui@~${appVersion}/umd/main.css`;
|
return `https://unpkg.com/@tryghost/comments-ui@~${appVersion}/umd/main.css`;
|
||||||
} else {
|
} else {
|
||||||
return 'http://localhost:4000/main.css';
|
return 'https://comments.localhost/main.css';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue