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,
|
||||
avatarSaturation: this.props.avatarSaturation,
|
||||
accentColor: this.props.accentColor,
|
||||
commentsEnabled: this.props.commentsEnabled,
|
||||
dispatchAction: (_action, data) => this.dispatchAction(_action, data),
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,7 +21,7 @@ const Comment = (props) => {
|
|||
setIsInReplyMode(current => !current);
|
||||
};
|
||||
|
||||
const {admin, avatarSaturation} = useContext(AppContext);
|
||||
const {admin, avatarSaturation, member, commentsEnabled} = useContext(AppContext);
|
||||
const comment = props.comment;
|
||||
const hasReplies = comment.replies && comment.replies.length > 0;
|
||||
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) {
|
||||
return (
|
||||
<Form comment={comment} toggle={toggleEditMode} parent={props.parent} isEdit={true} avatarSaturation={props.avatarSaturation} />
|
||||
|
@ -55,7 +59,7 @@ const Comment = (props) => {
|
|||
</div>
|
||||
<div className="ml-14 flex gap-5 items-center">
|
||||
<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>
|
||||
<More comment={comment} toggleEdit={toggleEditMode} />
|
||||
</div>
|
||||
|
|
|
@ -5,6 +5,7 @@ import Loading from './Loading';
|
|||
import Form from './Form';
|
||||
import Comment from './Comment';
|
||||
import Pagination from './Pagination';
|
||||
import NotPaidBox from './NotPaidBox';
|
||||
|
||||
const CommentsBox = (props) => {
|
||||
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} />);
|
||||
|
||||
|
@ -50,6 +51,9 @@ const CommentsBox = (props) => {
|
|||
'--gh-accent-color': accentColor ?? 'blue'
|
||||
};
|
||||
|
||||
const paidOnly = commentsEnabled === 'paid';
|
||||
const isPaidMember = member && !!member.paid;
|
||||
|
||||
return (
|
||||
<section className={'ghost-display ' + containerClass} style={style}>
|
||||
<Pagination />
|
||||
|
@ -59,7 +63,7 @@ const CommentsBox = (props) => {
|
|||
{commentsElements}
|
||||
</div>
|
||||
<div>
|
||||
{ member ? <Form commentsCount={commentsCount} avatarSaturation={props.avatarSaturation} /> : <NotSignedInBox /> }
|
||||
{ member ? (isPaidMember || !paidOnly ? <Form commentsCount={commentsCount} avatarSaturation={props.avatarSaturation} /> : <NotPaidBox />) : <NotSignedInBox /> }
|
||||
</div>
|
||||
</>
|
||||
: <Loading />}
|
||||
|
|
|
@ -3,33 +3,42 @@ import {ReactComponent as LikeIcon} from '../images/icons/like.svg';
|
|||
import AppContext from '../AppContext';
|
||||
|
||||
function Like(props) {
|
||||
const {onAction, member} = useContext(AppContext);
|
||||
const {dispatchAction, member, commentsEnabled} = useContext(AppContext);
|
||||
const [animationClass, setAnimation] = useState('');
|
||||
|
||||
let likeCursor = 'cursor-pointer';
|
||||
if (!member) {
|
||||
likeCursor = 'cursor-text';
|
||||
}
|
||||
const paidOnly = commentsEnabled === 'paid';
|
||||
const isPaidMember = member && !!member.paid;
|
||||
const canLike = member && (isPaidMember || !paidOnly);
|
||||
|
||||
const toggleLike = () => {
|
||||
if (member) {
|
||||
if (!props.comment.liked) {
|
||||
onAction('likeComment', props.comment);
|
||||
setAnimation('animate-heartbeat');
|
||||
setTimeout(() => {
|
||||
setAnimation('');
|
||||
}, 400);
|
||||
} else {
|
||||
onAction('unlikeComment', props.comment);
|
||||
}
|
||||
if (!canLike) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!props.comment.liked) {
|
||||
dispatchAction('likeComment', props.comment);
|
||||
setAnimation('animate-heartbeat');
|
||||
setTimeout(() => {
|
||||
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 (
|
||||
<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)]'}`} />
|
||||
{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 accentColor = scriptTag.dataset.accentColor;
|
||||
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 {};
|
||||
}
|
||||
|
@ -58,13 +59,13 @@ function setup({siteUrl}) {
|
|||
|
||||
function init() {
|
||||
// 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;
|
||||
setup({siteUrl});
|
||||
|
||||
ReactDOM.render(
|
||||
<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>,
|
||||
document.getElementById(ROOT_DIV_ID)
|
||||
);
|
||||
|
|
|
@ -109,6 +109,6 @@ export function getBundledCssLink({appVersion}) {
|
|||
if (process.env.NODE_ENV === 'production' && appVersion) {
|
||||
return `https://unpkg.com/@tryghost/comments-ui@~${appVersion}/umd/main.css`;
|
||||
} else {
|
||||
return 'http://localhost:4000/main.css';
|
||||
return 'https://comments.localhost/main.css';
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue