Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: comments pre-moderation #68

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions admin/src/components/Item/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import CardItem from './CardItem';
import ItemFooter from '../ItemFooter';
import ItemHeader from '../ItemHeader';
import useDataManager from '../../hooks/useDataManager';
import { APPROVAL_STATUS } from '../../utils/constants';

const Item = ({
id,
Expand All @@ -26,6 +27,9 @@ const Item = ({
createdAt,
updatedAt,
relatedContentTypes,
onApproveCommentClick,
onRejectCommentClick,
approvalStatus,
}) => {
const { push } = useHistory();
const { getSearchParams } = useDataManager();
Expand All @@ -43,13 +47,17 @@ const Item = ({
};

const isAbuseReported = !isEmpty(reports);
const isItemHeaderDisplayed = blocked || blockedThread || isNew || removed || isAbuseReported;
const isPending = approvalStatus === APPROVAL_STATUS.PENDING;
const isItemHeaderDisplayed = blocked || blockedThread || isNew || removed || isAbuseReported || isPending;
const headerProps = {
blocked,
blockedThread,
isNew,
isAbuseReported,
isRemoved: removed,
onApproveCommentClick,
onRejectCommentClick,
approvalStatus,
};

const footerProps = {
Expand All @@ -67,7 +75,7 @@ const Item = ({
onClick={onClick}
active={id === parsedId}
>
{ isItemHeaderDisplayed && (<ItemHeader { ...headerProps } />) }
{isItemHeaderDisplayed && (<ItemHeader { ...headerProps } />) }
<p>{content}</p>
<ItemFooter {...footerProps} />
</CardItem>
Expand All @@ -87,6 +95,13 @@ Item.propTypes = {
blocked: PropTypes.bool,
blockedThread: PropTypes.bool,
isNew: PropTypes.bool,
approvalStatus: PropTypes.oneOf([
APPROVAL_STATUS.APPROVED,
APPROVAL_STATUS.PENDING,
APPROVAL_STATUS
]),
onApproveCommentClick: PropTypes.func,
onRejectCommentClick: PropTypes.func,
};

export default Item;
30 changes: 23 additions & 7 deletions admin/src/components/ItemDetails/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import ItemModeration from '../ItemModeration';
import ItemHeader from '../ItemHeader';
import AbuseReportsPopUp from '../AbuseReportsPopUp';
import pluginId from '../../pluginId';
import { APPROVAL_STATUS } from "../../utils/constants";

const ItemDetails = ({
id,
Expand All @@ -36,6 +37,9 @@ const ItemDetails = ({
onBlockClick,
onBlockThreadClick,
onAbuseReportResolve,
onApproveCommentClick,
onRejectCommentClick,
approvalStatus,
}) => {
const [showPopUp, setPopUpVisibility] = useState(false);

Expand All @@ -58,14 +62,15 @@ const ItemDetails = ({
};
const hasThreads = (threadsCount !== undefined) && (threadsCount > 0);
const isAbuseReported = !isEmpty(reports);
const isItemHeaderDisplayed = blocked || blockedThread || removed || isAbuseReported;
const isPending = approvalStatus === APPROVAL_STATUS.PENDING;
const isItemHeaderDisplayed = blocked || blockedThread || removed || isAbuseReported || isPending;
const footerProps = {
authorName,
authorUser,
related: isArray(related) ? first(related) : related,
created_at: created_at || createdAt,
updated_at: updated_at || updatedAt,
isDelailedView: true,
isDetailedView: true,
};
const headerProps = {
active,
Expand All @@ -74,15 +79,19 @@ const ItemDetails = ({
isRemoved: removed,
abuseReports: reports || [],
isAbuseReported: !isEmpty(reports),
isDelailedView: true,
isDetailedView: true,
onReportsClick: onPopUpOpen,
approvalStatus,
};
const moderationProps = {
id,
blocked,
blockedThread,
onBlockClick,
onBlockThreadClick,
onApproveCommentClick,
onRejectCommentClick,
approvalStatus,
};
const reportsPopUpProps = {
blocked,
Expand All @@ -105,20 +114,20 @@ const ItemDetails = ({
clickable={clickable}
root={root}
active={active}>
{ isItemHeaderDisplayed && (<ItemHeader { ...headerProps } />) }
{isItemHeaderDisplayed && (<ItemHeader { ...headerProps } />) }
<p>{content}</p>
<ItemFooter {...footerProps} />
</CardItem>
{ hasThreads && (<CardLevelCounter>
{hasThreads && (<CardLevelCounter>
<FormattedMessage id={`${pluginId}.list.item.threads.count`} values={{ count: threadsCount }}/>
<CardLevelCounterLink onClick={onClick}>
<FormattedMessage id={`${pluginId}.list.item.threads.drilldown`} />
<FontAwesomeIcon icon={faArrowRight} />
</CardLevelCounterLink>
</CardLevelCounter>
)}
{ active && !removed && (<ItemModeration { ...moderationProps } />) }
{ (!isEmpty(reports) && active) && (
{active && !removed && (<ItemModeration { ...moderationProps } />)}
{(!isEmpty(reports) && active) && (
<AbuseReportsPopUp
{...reportsPopUpProps}
/>
Expand Down Expand Up @@ -146,6 +155,13 @@ ItemDetails.propTypes = {
onBlockClick: PropTypes.func,
onBlockThreadClick: PropTypes.func,
onAbuseReportResolve: PropTypes.func,
approvalStatus: PropTypes.oneOf([
APPROVAL_STATUS.APPROVED,
APPROVAL_STATUS.PENDING,
APPROVAL_STATUS
]),
onApproveCommentClick: PropTypes.func,
onRejectCommentClick: PropTypes.func,
};

export default ItemDetails;
11 changes: 11 additions & 0 deletions admin/src/components/ItemHeader/CardHeaderPending.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import styled from 'styled-components';

import { colors } from 'strapi-helper-plugin';

import CardHeaderBlocked from './CardHeaderBlocked';

const CardHeaderPending = styled(CardHeaderBlocked)`
color: ${colors.blue};
`;

export default CardHeaderPending;
43 changes: 30 additions & 13 deletions admin/src/components/ItemHeader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,34 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useGlobalContext, CheckPermissions } from 'strapi-helper-plugin';
import { faLock, faStream, faAsterisk, faFire } from '@fortawesome/free-solid-svg-icons';
import { faLock, faStream, faAsterisk, faFire, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
import Wrapper from './Wrapper';
import CardHeaderBlocked from './CardHeaderBlocked';
import CardHeaderPending from './CardHeaderPending';
import pluginId from '../../pluginId';
import CardHeaderIndicatorsContainer from './CardHeaderIndicatorsContainer';
import CardHeaderIndicatorBlue from './CardHeaderIndicatorBlue';
import CardHeaderIndicatorRed from './CardHeaderIndicatorRed';
import CardHeaderReports from './CardHeaderReports';
import pluginPermissions from '../../permissions';
import { APPROVAL_STATUS } from "../../utils/constants";

const ItemHeader = ({ active, isDelailedView, blocked, blockedThread, isNew, isAbuseReported, isRemoved, abuseReports, onReportsClick }) => {
const ItemHeader = ({ active, isDetailedView, blocked, blockedThread, isNew, isAbuseReported, isRemoved, abuseReports, onReportsClick, approvalStatus }) => {
const { formatMessage } = useGlobalContext();
const isBlocked = blocked || blockedThread;
const hasAnyIndicators = isNew || (isAbuseReported && !isDelailedView);
const hasAnyIndicators = isNew || (isAbuseReported && !isDetailedView);

return (
<Wrapper hasMargin={isBlocked || isRemoved || (isAbuseReported && isDelailedView)}>
{ isBlocked && (
<Wrapper hasMargin={isBlocked || isRemoved || (isAbuseReported && isDetailedView)}>
{isBlocked && (
<CheckPermissions permissions={pluginPermissions.moderate}>
<CardHeaderBlocked>
<FontAwesomeIcon icon={faLock} />
{blockedThread && <FontAwesomeIcon icon={faStream} />}
<FormattedMessage id={`${pluginId}.list.item.header.blocked${blockedThread ? '.thread' : ''}`} />
</CardHeaderBlocked>
</CheckPermissions>
) }
)}
{
isRemoved && (
<CheckPermissions permissions={pluginPermissions.moderate}>
Expand All @@ -40,8 +42,18 @@ const ItemHeader = ({ active, isDelailedView, blocked, blockedThread, isNew, isA
</CheckPermissions>
)
}
{ (isDelailedView && active) && (<>
{ isAbuseReported && (
{
approvalStatus === APPROVAL_STATUS.PENDING && (
<CheckPermissions permissions={pluginPermissions.moderate}>
<CardHeaderPending>
<FontAwesomeIcon icon={faExclamationCircle} />
<FormattedMessage id={`${pluginId}.list.item.header.pending`} />
</CardHeaderPending>
</CheckPermissions>
)
}
{(isDetailedView && active) && (<>
{isAbuseReported && (
<CheckPermissions permissions={pluginPermissions.moderateReports}>
<CardHeaderReports href="#abuse-reports" onClick={onReportsClick}>
<FontAwesomeIcon icon={faFire} />
Expand All @@ -51,13 +63,13 @@ const ItemHeader = ({ active, isDelailedView, blocked, blockedThread, isNew, isA
)}
</>)}
{hasAnyIndicators && <CardHeaderIndicatorsContainer>
{ isNew && (<CardHeaderIndicatorBlue title={formatMessage({ id: `${pluginId}.list.item.indication.new`})}>
{isNew && (<CardHeaderIndicatorBlue title={formatMessage({ id: `${pluginId}.list.item.indication.new`})}>
<FontAwesomeIcon icon={faAsterisk} />
</CardHeaderIndicatorBlue>) }
</CardHeaderIndicatorBlue>)}
<CheckPermissions permissions={pluginPermissions.moderateReports}>
{ isAbuseReported && (<CardHeaderIndicatorRed title={formatMessage({ id: `${pluginId}.list.item.indication.abuse`})}>
{isAbuseReported && (<CardHeaderIndicatorRed title={formatMessage({ id: `${pluginId}.list.item.indication.abuse`})}>
<FontAwesomeIcon icon={faFire} />
</CardHeaderIndicatorRed>) }
</CardHeaderIndicatorRed>)}
</CheckPermissions>
</CardHeaderIndicatorsContainer>
}
Expand All @@ -67,14 +79,19 @@ const ItemHeader = ({ active, isDelailedView, blocked, blockedThread, isNew, isA

ItemHeader.propTypes = {
active: PropTypes.bool,
isDelailedView: PropTypes.bool,
isDetailedView: PropTypes.bool,
blocked: PropTypes.bool,
blockedThread: PropTypes.bool,
isNew: PropTypes.bool,
removed: PropTypes.bool,
isAbuseReported: PropTypes.bool,
abuseReports: PropTypes.array,
onReportsClick: PropTypes.func,
approvalStatus: PropTypes.oneOf([
APPROVAL_STATUS.APPROVED,
APPROVAL_STATUS.PENDING,
APPROVAL_STATUS
])
};

export default ItemHeader;
47 changes: 42 additions & 5 deletions admin/src/components/ItemModeration/index.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,51 @@
import React from 'react';
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import { useGlobalContext, CheckPermissions } from 'strapi-helper-plugin';
import { Button } from '@buffetjs/core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faComment, faCommentSlash, faComments } from '@fortawesome/free-solid-svg-icons';
import { faComment, faCommentSlash, faComments, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
import Wrapper from './Wrapper';
import pluginId from '../../pluginId';
import pluginPermissions from '../../permissions';
import { APPROVAL_STATUS } from '../../utils/constants';

const ItemModeration = ({ id, blocked, blockedThread, onBlockClick, onBlockThreadClick }) => {

const ItemModeration = ({ id, blocked, blockedThread, onBlockClick, onBlockThreadClick, onApproveCommentClick, onRejectCommentClick, approvalStatus }) => {
const { formatMessage } = useGlobalContext();
const canBeApproved =
approvalStatus === APPROVAL_STATUS.PENDING ||
approvalStatus === APPROVAL_STATUS.REJECTED;
const approvalButtonProps = useMemo(
() =>
canBeApproved
? {
onClick: onApproveCommentClick.bind(null, id),
color: 'primary',
label: formatMessage({
id: `${pluginId}.list.item.moderation.button.comment.approval.approve`,
}),
}
: {
onClick: onRejectCommentClick.bind(null, id),
color: 'delete',
label: formatMessage({
id: `${pluginId}.list.item.moderation.button.comment.approval.reject`,
}),
},
[canBeApproved, id],
);

return (
<CheckPermissions permissions={pluginPermissions.moderate}>
<Wrapper>
{ !blockedThread && (
{approvalStatus ? (
<CheckPermissions permissions={pluginPermissions.moderateComments}>
<Button
{...approvalButtonProps}
icon={<FontAwesomeIcon icon={faExclamationCircle} />}
/>
</CheckPermissions>
) : null}
{!blockedThread && (
<CheckPermissions permissions={pluginPermissions.moderateComments}>
<Button
onClick={e => onBlockClick(id)}
Expand All @@ -42,6 +72,13 @@ ItemModeration.propTypes = {
blockedThread: PropTypes.bool,
onBlockClick: PropTypes.func.isRequired,
onBlockThreadClick: PropTypes.func.isRequired,
approvalStatus: PropTypes.oneOf([
APPROVAL_STATUS.APPROVED,
APPROVAL_STATUS.PENDING,
APPROVAL_STATUS
]),
onApproveCommentClick: PropTypes.func,
onRejectCommentClick: PropTypes.func,
};

export default ItemModeration;
4 changes: 4 additions & 0 deletions admin/src/containers/DataManagerProvider/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ export const BLOCK_COMMENT_SUCCESS = 'BLOCK_COMMENT_SUCCESS';
export const BLOCK_COMMENT_THREAD_SUCCESS = 'BLOCK_COMMENT_THREAD_SUCCESS';
export const RESOLVE_ABUSE_REPORT = 'RESOLVE_ABUSE_REPORT';
export const RESOLVE_ABUSE_REPORT_SUCCESS = 'RESOLVE_ABUSE_REPORT_SUCCESS';
export const APPROVE_COMMENT = 'APPROVE_COMMENT';
export const REJECT_COMMENT = 'REJECT_COMMENT';
export const APPROVE_COMMENT_SUCCESS = 'APPROVE_COMMENT_SUCCESS';
export const REJECT_COMMENT_SUCCESS = 'REJECT_COMMENT_SUCCESS';
Loading