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 (
+
+
+
+ );
+}
+
+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.canUpdateStatus && (
+
+ )}
+ {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}`;
+};