diff --git a/components/ActionMenu/ReportAbuseMenuItem.js b/components/ActionMenu/ReportAbuseMenuItem.js new file mode 100644 index 00000000..eee37de0 --- /dev/null +++ b/components/ActionMenu/ReportAbuseMenuItem.js @@ -0,0 +1,50 @@ +import { t } from 'ttag'; +import MenuItem from '@material-ui/core/MenuItem'; +import { makeStyles } from '@material-ui/core/styles'; + +import { getSpamReportUrl } from 'constants/urls'; +import useCurrentUser from 'lib/useCurrentUser'; + +const useStyles = makeStyles({ + link: { + color: 'inherit', + textDecoration: 'none', + }, +}); + +/** + * + * @param {string} itemUserId - the author's user ID of the item + */ +export function useCanReportAbuse(itemUserId) { + const currentUser = useCurrentUser(); + return currentUser && currentUser.id !== itemUserId; +} + +/** + * @param {object} props + * @param {string} props.userId - spammer's user ID + * @param {'replyRequest' | 'articleReplyFeedback' | 'reply'} props.itemType - reported spam item type + * @param {string} props.itemId - reply ID for reply; article ID for replyRequest; article ID,reply ID (separated in comma) for article reply feedback. + * + * @returns {string} Pre-filled URL to the google form that reports spam. + */ +function ReportAbuseMenuItem(props) { + const classes = useStyles(); + const canReportAbuse = useCanReportAbuse(props.userId); + + if (!canReportAbuse) return null; + + return ( + + {t`Report abuse`} + + ); +} + +export default ReportAbuseMenuItem; diff --git a/components/ActionMenu/index.js b/components/ActionMenu/index.js index 7d7ff1f6..a4eb9bff 100644 --- a/components/ActionMenu/index.js +++ b/components/ActionMenu/index.js @@ -1,2 +1,5 @@ import ActionMenu from './ActionMenu'; export default ActionMenu; + +import ReportAbuseMenuItem, { useCanReportAbuse } from './ReportAbuseMenuItem'; +export { ReportAbuseMenuItem, useCanReportAbuse }; diff --git a/components/ArticleReply/ArticleReply.js b/components/ArticleReply/ArticleReply.js index abd86eae..20cad5b7 100644 --- a/components/ArticleReply/ArticleReply.js +++ b/components/ArticleReply/ArticleReply.js @@ -56,12 +56,14 @@ const ArticleReplyData = gql` } ...ArticleReplySummaryData ...ArticleReplyFeedbackControlData + ...ReplyActionsData } ${Hyperlinks.fragments.HyperlinkData} ${ArticleReplyFeedbackControl.fragments.ArticleReplyFeedbackControlData} ${ReplyInfo.fragments.replyInfo} ${Avatar.fragments.AvatarData} ${ArticleReplySummary.fragments.ArticleReplySummaryData} + ${ReplyActions.fragments.ReplyActionsData} `; const ArticleReplyForUser = gql` @@ -71,9 +73,11 @@ const ArticleReplyForUser = gql` replyId canUpdateStatus ...ArticleReplyFeedbackControlDataForUser + ...ReplyActionsDataForUser } ${ArticleReplyFeedbackControl.fragments .ArticleReplyFeedbackControlDataForUser} + ${ReplyActions.fragments.ReplyActionsDataForUser} `; const ArticleReply = React.memo(({ articleReply }) => { diff --git a/components/ArticleReply/ReplyActions.js b/components/ArticleReply/ReplyActions.js index e3a1cb81..d0b2a9cc 100644 --- a/components/ArticleReply/ReplyActions.js +++ b/components/ArticleReply/ReplyActions.js @@ -3,9 +3,27 @@ import gql from 'graphql-tag'; import { useMutation } from '@apollo/react-hooks'; import { t } from 'ttag'; -import ActionMenu from 'components/ActionMenu'; +import ActionMenu, { + ReportAbuseMenuItem, + useCanReportAbuse, +} from 'components/ActionMenu'; import { MenuItem } from '@material-ui/core'; +const ReplyActionsData = gql` + fragment ReplyActionsData on ArticleReply { + articleId + replyId + userId + status + } +`; + +const ReplyActionsDataForUser = gql` + fragment ReplyActionsDataForUser on ArticleReply { + canUpdateStatus + } +`; + const UPDATE_ARTICLE_REPLY_STATUS = gql` mutation UpdateArticleReplyStatus( $articleId: String! @@ -17,14 +35,17 @@ const UPDATE_ARTICLE_REPLY_STATUS = gql` replyId: $replyId status: $status ) { - articleId - replyId - status + ...ReplyActionsData + ...ReplyActionsDataForUser } + ${ReplyActionsData} + ${ReplyActionsDataForUser} } `; const ReplyActions = ({ articleReply }) => { + const canReportAbuse = useCanReportAbuse(articleReply.userId); + const [ updateArticleReplyStatus, { loading: updatingArticleReplyStatus }, @@ -50,20 +71,34 @@ const ReplyActions = ({ articleReply }) => { }); }, [updateArticleReplyStatus]); - if (!articleReply.canUpdateStatus) return null; + if (!articleReply.canUpdateStatus && !canReportAbuse) return null; return ( - - {articleReply.status === 'NORMAL' ? t`Delete` : t`Restore`} - + {articleReply.canUpdateStatus && ( + + {articleReply.status === 'NORMAL' ? t`Delete` : t`Restore`} + + )} + {canReportAbuse && ( + + )} ); }; +ReplyActions.fragments = { + ReplyActionsData, + ReplyActionsDataForUser, +}; + export default ReplyActions; diff --git a/components/ReplyRequestReason/ReplyRequestReason.js b/components/ReplyRequestReason/ReplyRequestReason.js index 02e0ff06..9cffedd1 100644 --- a/components/ReplyRequestReason/ReplyRequestReason.js +++ b/components/ReplyRequestReason/ReplyRequestReason.js @@ -1,11 +1,16 @@ import React from 'react'; +import gql from 'graphql-tag'; +import PropTypes from 'prop-types'; import { useMutation } from '@apollo/react-hooks'; import { makeStyles } from '@material-ui/core/styles'; import { Box, Button } from '@material-ui/core'; + import { ThumbUpIcon, ThumbDownIcon } from 'components/icons'; import Avatar from 'components/AppLayout/Widgets/Avatar'; -import gql from 'graphql-tag'; -import PropTypes from 'prop-types'; +import ActionMenu, { + ReportAbuseMenuItem, + useCanReportAbuse, +} from 'components/ActionMenu'; const useStyles = makeStyles(theme => ({ root: { @@ -77,7 +82,7 @@ const UPDATE_VOTE = gql` ${ReplyRequestInfo} `; -function ReplyRequestReason({ replyRequest }) { +function ReplyRequestReason({ replyRequest, articleId }) { const { id: replyRequestId, reason: replyRequestReason, @@ -87,6 +92,7 @@ function ReplyRequestReason({ replyRequest }) { user, } = replyRequest; + const canReportAbuse = useCanReportAbuse(user.id); const [voteReason, { loading }] = useMutation(UPDATE_VOTE); const handleVote = vote => { voteReason({ variables: { vote, replyRequestId } }); @@ -132,6 +138,17 @@ function ReplyRequestReason({ replyRequest }) { + {canReportAbuse && ( + + + + + + )} ); } diff --git a/constants/urls.js b/constants/urls.js index 47ca4dd7..d9a0c8c5 100644 --- a/constants/urls.js +++ b/constants/urls.js @@ -21,3 +21,16 @@ export const FACEBOOK_SHARE_URL_PREFIX = export const DONATION_URL = 'https://ocf.neticrm.tw/civicrm/contribute/transact?id=48'; + +/** + * @param {object} params + * @param {string} params.userId - spammer's user ID + * @param {'replyRequest' | 'articleReplyFeedback' | 'reply'} params.itemType - reported spam item type + * @param {string} params.itemId - reply ID for reply; article ID for replyRequest; article ID,reply ID (separated in comma) for article reply feedback. + * + * @returns {string} Pre-filled URL to the google form that reports spam. + */ +export const getSpamReportUrl = ({ userId, itemType, itemId }) => { + // Prefilled URL as constant, manually edited to become template string + return `https://docs.google.com/forms/d/e/1FAIpQLSf7d8xCAz682vR3WLRVTxqqbWiFXLd6ShZpOnsXXTmAbPFcUA/viewform?usp=pp_url&entry.1302713624=${userId}&entry.192715150=${itemId}&entry.511781180=${itemType}&entry.1691230719=${location.href}`; +};