Skip to content

Commit

Permalink
Preventing user from marking own expense as spam (#10025)
Browse files Browse the repository at this point in the history
Co-authored-by: Benjamin Piouffle <[email protected]>
  • Loading branch information
samkevin1 and Betree authored Sep 11, 2024
1 parent 7d29af8 commit 7a38435
Show file tree
Hide file tree
Showing 30 changed files with 147 additions and 48 deletions.
32 changes: 25 additions & 7 deletions components/expenses/ConfirmProcessExpenseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import useProcessExpense from '../../lib/expenses/useProcessExpense';
import type { Expense } from '../../lib/graphql/types/v2/graphql';

import { Flex } from '../Grid';
import MessageBox from '../MessageBox';
import RichTextEditor from '../RichTextEditor';
import StyledButton from '../StyledButton';
import StyledModal, { ModalBody, ModalFooter, ModalHeader } from '../StyledModal';
Expand All @@ -15,8 +16,8 @@ import { toast } from '../ui/useToast';

const messages = defineMessages({
reasonPlaceholder: {
defaultMessage: 'e.g. Email Address is wrong',
id: 'g0KLMH',
defaultMessage: 'e.g, We never worked with this person.',
id: 'mpLU2S',
},
REQUEST_RE_APPROVAL_TITLE: {
id: 'expense.requestReApproval.btn',
Expand Down Expand Up @@ -108,17 +109,21 @@ const messages = defineMessages({
},
MARK_AS_SPAM_DESCRIPTION: {
id: 'Expense.MarkAsSpamWarning',
defaultMessage: 'This will prevent the submitter account to post new expenses. Are you sure?',
defaultMessage: 'This will prevent the submitter account to post new expenses.',
},
MARK_AS_SPAM_CONFIRM_BUTTON: {
id: 'actions.spam',
defaultMessage: 'Mark as Spam',
},
MARK_AS_SPAM_LABEL: {
id: 'Expense.MarkAsSpamLabel',
defaultMessage: 'Why are you marking this expense as spam?',
},
});

const MessagesPerType: Record<
ConfirmProcessExpenseModalType,
{ title: MessageDescriptor; description: MessageDescriptor; confirmBtn: MessageDescriptor }
{ title: MessageDescriptor; description: MessageDescriptor; confirmBtn: MessageDescriptor; label?: MessageDescriptor }
> = {
REQUEST_RE_APPROVAL: {
title: messages.REQUEST_RE_APPROVAL_TITLE,
Expand Down Expand Up @@ -159,6 +164,7 @@ const MessagesPerType: Record<
title: messages.MARK_AS_SPAM_TITLE,
description: messages.MARK_AS_SPAM_DESCRIPTION,
confirmBtn: messages.MARK_AS_SPAM_CONFIRM_BUTTON,
label: messages.MARK_AS_SPAM_LABEL,
},
};

Expand Down Expand Up @@ -250,9 +256,21 @@ export default function ConfirmProcessExpenseModal({ type, onClose, expense }: C
<StyledModal role="alertdialog" onClose={onClose} trapFocus>
<ModalHeader>{intl.formatMessage(MessagesPerType[type].title)}</ModalHeader>
<ModalBody pt={2}>
<P mb={3} color="black.700" lineHeight="20px">
{intl.formatMessage(MessagesPerType[type].description)}
</P>
<Flex>
<MessageBox lineHeight="20px" mb={10} type="warning" withIcon>
{intl.formatMessage(MessagesPerType[type].description)}
</MessageBox>
</Flex>

{MessagesPerType[type].label && (
<P as="label" htmlFor="expense-ban-reason" mb={2} color="black.700" fontWeight="500">
{intl.formatMessage(
{ id: 'OptionalFieldLabel', defaultMessage: '{field} (optional)' },
{ field: intl.formatMessage(MessagesPerType[type].label) },
)}
</P>
)}

<RichTextEditor
data-cy="confirm-action-text"
kind="COMMENT"
Expand Down
26 changes: 25 additions & 1 deletion components/expenses/ExpenseMoreActionsButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,21 @@ import { Trash2 as IconTrash } from '@styled-icons/feather/Trash2';
import { get } from 'lodash';
import { ArrowRightLeft, FileText } from 'lucide-react';
import { useRouter } from 'next/router';
import { FormattedMessage } from 'react-intl';
import { FormattedMessage, useIntl } from 'react-intl';
import styled from 'styled-components';
import { margin } from 'styled-system';

import expenseTypes from '../../lib/constants/expenseTypes';
import useProcessExpense from '../../lib/expenses/useProcessExpense';
import useClipboard from '../../lib/hooks/useClipboard';
import useLoggedInUser from '../../lib/hooks/useLoggedInUser';
import { getCollectivePageCanonicalURL, getCollectivePageRoute, getDashboardRoute } from '../../lib/url-helpers';

import { DashboardContext } from '../dashboard/DashboardContext';
import { DownloadLegalDocument } from '../legal-documents/DownloadLegalDocument';
import PopupMenu from '../PopupMenu';
import StyledButton from '../StyledButton';
import { useToast } from '../ui/useToast';

import ConfirmProcessExpenseModal from './ConfirmProcessExpenseModal';
import ExpenseConfirmDeletion from './ExpenseConfirmDeletionModal';
Expand Down Expand Up @@ -92,13 +94,18 @@ const ExpenseMoreActionsButton = ({
const [hasDeleteConfirm, setDeleteConfirm] = React.useState(false);
const { isCopied, copy } = useClipboard();
const { account } = React.useContext(DashboardContext);
const { toast } = useToast();
const intl = useIntl();

const router = useRouter();
const permissions = expense?.permissions;

const processExpense = useProcessExpense({
expense,
});

const { LoggedInUser } = useLoggedInUser();

const showDeleteConfirmMoreActions = isOpen => {
setDeleteConfirm(isOpen);
onModalToggle?.(isOpen);
Expand Down Expand Up @@ -137,6 +144,20 @@ const ExpenseMoreActionsButton = ({
buttonStyle="dangerSecondary"
data-cy="spam-button"
onClick={async () => {
const isSubmitter = expense.createdByAccount.legacyId === LoggedInUser?.CollectiveId;

if (isSubmitter) {
toast({
variant: 'error',
message: intl.formatMessage({
id: 'expense.spam.notAllowed',
defaultMessage: "You can't mark your own expenses as spam",
}),
});

return;
}

setProcessModal('MARK_AS_SPAM');
setOpen(false);
}}
Expand Down Expand Up @@ -322,6 +343,9 @@ ExpenseMoreActionsButton.propTypes = {
slug: PropTypes.string.isRequired,
}),
}),
createdByAccount: PropTypes.shape({
legacyId: PropTypes.number.isRequired,
}),
}),
/** Called with an error if anything wrong happens */
onError: PropTypes.func,
Expand Down
23 changes: 20 additions & 3 deletions components/expenses/ProcessExpenseButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import styled from 'styled-components';
import PERMISSION_CODES, { ReasonMessage } from '../../lib/constants/permissions';
import { i18nGraphqlException } from '../../lib/errors';
import { API_V2_CONTEXT, gql } from '../../lib/graphql/helpers';
import useLoggedInUser from '../../lib/hooks/useLoggedInUser';
import { collectiveAdminsMustConfirmAccountingCategory } from './lib/accounting-categories';

import {
Expand Down Expand Up @@ -71,7 +72,7 @@ export const hasProcessButtons = permissions => {
const messages = defineMessages({
markAsSpamWarning: {
id: 'Expense.MarkAsSpamWarning',
defaultMessage: 'This will prevent the submitter account to post new expenses. Are you sure?',
defaultMessage: 'This will prevent the submitter account to post new expenses.',
},
});

Expand Down Expand Up @@ -151,6 +152,7 @@ const ProcessExpenseButtons = ({
const [processExpense, { loading, error }] = useMutation(processExpenseMutation, mutationOptions);
const intl = useIntl();
const { toast } = useToast();
const { LoggedInUser } = useLoggedInUser();

React.useEffect(() => {
onModalToggle?.(!!confirmProcessExpenseAction);
Expand Down Expand Up @@ -246,6 +248,20 @@ const ProcessExpenseButtons = ({
buttonStyle="dangerSecondary"
data-cy="spam-button"
onClick={() => {
const isSubmitter = expense.createdByAccount.legacyId === LoggedInUser?.CollectiveId;

if (isSubmitter) {
toast({
variant: 'error',
message: intl.formatMessage({
id: 'expense.spam.notAllowed',
defaultMessage: "You can't mark your own expenses as spam",
}),
});

return;
}

if (confirm(intl.formatMessage(messages.markAsSpamWarning))) {
triggerAction('MARK_AS_SPAM');
}
Expand Down Expand Up @@ -370,21 +386,22 @@ ProcessExpenseButtons.propTypes = {
message: PropTypes.string,
}),
),
createdByAccount: PropTypes.shape({
legacyId: PropTypes.number.isRequired,
}),
}).isRequired,
/** The account where the expense has been submitted */
collective: PropTypes.object.isRequired,
host: PropTypes.object,
/** Props passed to all buttons. Useful to customize sizes, spaces, etc. */
buttonProps: PropTypes.object,
showError: PropTypes.bool,
onSuccess: PropTypes.func,
/** Called when the expense gets deleted */
onDelete: PropTypes.func,
/** Checks if the delete action is inside the more actions button */
isMoreActions: PropTypes.bool,
/** Called when a modal is opened/closed with a boolean like (isOpen) */
onModalToggle: PropTypes.func,
displayMarkAsIncomplete: PropTypes.bool,
displaySecurityChecks: PropTypes.bool,
isViewingExpenseInHostContext: PropTypes.bool,
disabled: PropTypes.bool,
Expand Down
2 changes: 2 additions & 0 deletions components/expenses/graphql/fragments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ export const expensePageExpenseFieldsFragment = gql`
name
type
imageUrl
legacyId
...AccountHoverCardFields
}
host {
Expand Down Expand Up @@ -740,6 +741,7 @@ export const expensesListFieldsFragment = gql`
type
slug
name
legacyId
...AccountHoverCardFields
}
}
Expand Down
2 changes: 1 addition & 1 deletion lang/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -1379,7 +1379,7 @@
"Expense.InvoiceItems": "Invoice items",
"Expense.JoinAndSubmit": "Join and Submit",
"expense.markAsPaid": "Mark as paid",
"Expense.MarkAsSpamWarning": "This will prevent the submitter account to post new expenses. Are you sure?",
"Expense.MarkAsSpamWarning": "This will prevent the submitter account to post new expenses.",
"Expense.markAsUnpaid": "Mark expense as unpaid",
"expense.markAsUnpaid.btn": "Mark as unpaid",
"Expense.markAsUnpaid.details": "The amount will be credited back to the Collective balance.",
Expand Down
2 changes: 1 addition & 1 deletion lang/bg-BG.json
Original file line number Diff line number Diff line change
Expand Up @@ -1379,7 +1379,7 @@
"Expense.InvoiceItems": "Invoice items",
"Expense.JoinAndSubmit": "Join and Submit",
"expense.markAsPaid": "Mark as paid",
"Expense.MarkAsSpamWarning": "This will prevent the submitter account to post new expenses. Are you sure?",
"Expense.MarkAsSpamWarning": "This will prevent the submitter account to post new expenses.",
"Expense.markAsUnpaid": "Mark expense as unpaid",
"expense.markAsUnpaid.btn": "Mark as unpaid",
"Expense.markAsUnpaid.details": "The amount will be credited back to the Collective balance.",
Expand Down
2 changes: 1 addition & 1 deletion lang/bs-BA.json
Original file line number Diff line number Diff line change
Expand Up @@ -1379,7 +1379,7 @@
"Expense.InvoiceItems": "Invoice items",
"Expense.JoinAndSubmit": "Join and Submit",
"expense.markAsPaid": "Mark as paid",
"Expense.MarkAsSpamWarning": "This will prevent the submitter account to post new expenses. Are you sure?",
"Expense.MarkAsSpamWarning": "This will prevent the submitter account to post new expenses.",
"Expense.markAsUnpaid": "Mark expense as unpaid",
"expense.markAsUnpaid.btn": "Mark as unpaid",
"Expense.markAsUnpaid.details": "The amount will be credited back to the Collective balance.",
Expand Down
6 changes: 4 additions & 2 deletions lang/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -1384,7 +1384,8 @@
"Expense.InvoiceItems": "Invoice items",
"Expense.JoinAndSubmit": "Join and Submit",
"expense.markAsPaid": "Mark as paid",
"Expense.MarkAsSpamWarning": "This will prevent the submitter account to post new expenses. Are you sure?",
"Expense.MarkAsSpamLabel": "Why are you marking this expense as spam?",
"Expense.MarkAsSpamWarning": "This will prevent the submitter account to post new expenses.",
"Expense.markAsUnpaid": "Mark expense as unpaid",
"expense.markAsUnpaid.btn": "Marca com a pendent de pagar",
"Expense.markAsUnpaid.details": "The amount will be credited back to the Collective balance.",
Expand Down Expand Up @@ -1432,6 +1433,7 @@
"Expense.SeeDetails": "See expense details",
"Expense.SendInvite": "Send Invite",
"Expense.SignUpInfoBox": "You need to create an account to receive a payment from {collectiveName}, by clicking 'Join and Submit' you agree to create an account on {WebsiteName}.",
"expense.spam.notAllowed": "You can't mark your own expenses as spam",
"expense.status": "Status",
"Expense.Status.Refunded": "Refunded",
"Expense.Submitted": "Expense submitted",
Expand Down Expand Up @@ -1736,7 +1738,6 @@
"FXOuRH": "Put expense on hold",
"FxUka3": "Net Amount",
"FZQER9": "Delete {type, select, TICKET {Ticket} other {Tier}}",
"g0KLMH": "e.g. Email Address is wrong",
"g1BbRX": "Includes Platform Tip: {amount}",
"G65XME": "Select {name}",
"GAFyW+": "Your password was updated.",
Expand Down Expand Up @@ -2443,6 +2444,7 @@
"MonthlyBudget": "Monthly budget",
"moreInfo": "Més informació",
"mp9gR3": "Freezing the collective will prevent them from accepting and distributing contributions till they meet the requirements. This is a security measure to make sure the admins are within their rights. Read More.",
"mpLU2S": "e.g, We never worked with this person.",
"mqg/wj": "Date the funds were received.",
"mqX77s": "You can not approve this collective as it doesn’t satisfy the minimum admin policy set by you.",
"mr2kVW": "No details to show",
Expand Down
6 changes: 4 additions & 2 deletions lang/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1384,7 +1384,8 @@
"Expense.InvoiceItems": "Položky faktury",
"Expense.JoinAndSubmit": "Join and Submit",
"expense.markAsPaid": "Označit jako uhrazené",
"Expense.MarkAsSpamWarning": "This will prevent the submitter account to post new expenses. Are you sure?",
"Expense.MarkAsSpamLabel": "Why are you marking this expense as spam?",
"Expense.MarkAsSpamWarning": "This will prevent the submitter account to post new expenses.",
"Expense.markAsUnpaid": "Mark expense as unpaid",
"expense.markAsUnpaid.btn": "Označit jako nezaplacené",
"Expense.markAsUnpaid.details": "The amount will be credited back to the Collective balance.",
Expand Down Expand Up @@ -1432,6 +1433,7 @@
"Expense.SeeDetails": "See expense details",
"Expense.SendInvite": "Send Invite",
"Expense.SignUpInfoBox": "You need to create an account to receive a payment from {collectiveName}, by clicking 'Join and Submit' you agree to create an account on {WebsiteName}.",
"expense.spam.notAllowed": "You can't mark your own expenses as spam",
"expense.status": "Stav",
"Expense.Status.Refunded": "Refunded",
"Expense.Submitted": "Expense submitted",
Expand Down Expand Up @@ -1736,7 +1738,6 @@
"FXOuRH": "Put expense on hold",
"FxUka3": "Net Amount",
"FZQER9": "Delete {type, select, TICKET {Ticket} other {Tier}}",
"g0KLMH": "e.g. Email Address is wrong",
"g1BbRX": "Includes Platform Tip: {amount}",
"G65XME": "Select {name}",
"GAFyW+": "Your password was updated.",
Expand Down Expand Up @@ -2443,6 +2444,7 @@
"MonthlyBudget": "Monthly budget",
"moreInfo": "Více informací",
"mp9gR3": "Freezing the collective will prevent them from accepting and distributing contributions till they meet the requirements. This is a security measure to make sure the admins are within their rights. Read More.",
"mpLU2S": "e.g, We never worked with this person.",
"mqg/wj": "Date the funds were received.",
"mqX77s": "You can not approve this collective as it doesn’t satisfy the minimum admin policy set by you.",
"mr2kVW": "No details to show",
Expand Down
2 changes: 1 addition & 1 deletion lang/da-DK.json
Original file line number Diff line number Diff line change
Expand Up @@ -1379,7 +1379,7 @@
"Expense.InvoiceItems": "Invoice items",
"Expense.JoinAndSubmit": "Join and Submit",
"expense.markAsPaid": "Mark as paid",
"Expense.MarkAsSpamWarning": "This will prevent the submitter account to post new expenses. Are you sure?",
"Expense.MarkAsSpamWarning": "This will prevent the submitter account to post new expenses.",
"Expense.markAsUnpaid": "Mark expense as unpaid",
"expense.markAsUnpaid.btn": "Mark as unpaid",
"Expense.markAsUnpaid.details": "The amount will be credited back to the Collective balance.",
Expand Down
4 changes: 3 additions & 1 deletion lang/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,7 @@
"Expense.InvoiceItems": "Rechnungsposten",
"Expense.JoinAndSubmit": "Beitreten und Absenden",
"expense.markAsPaid": "Als bezahlt markieren",
"Expense.MarkAsSpamLabel": "Why are you marking this expense as spam?",
"Expense.MarkAsSpamWarning": "Dadurch wird verhindert, dass das Absender-Konto neue Ausgaben veröffentlichen kann. Bist du sicher?",
"Expense.markAsUnpaid": "Ausgabe als unbezahlt markieren",
"expense.markAsUnpaid.btn": "Als unbezahlt markieren",
Expand Down Expand Up @@ -1432,6 +1433,7 @@
"Expense.SeeDetails": "Ausgabendetails anzeigen",
"Expense.SendInvite": "Einladung senden",
"Expense.SignUpInfoBox": "Du musst ein Konto erstellen, um eine Zahlung von {collectiveName} zu erhalten. Wenn du auf 'Anmelden und Abschicken' klickst, stimmst du zu, ein Konto auf {WebsiteName} zu erstellen.",
"expense.spam.notAllowed": "You can't mark your own expenses as spam",
"expense.status": "Status",
"Expense.Status.Refunded": "Erstattet",
"Expense.Submitted": "Ausgabe eingereicht",
Expand Down Expand Up @@ -1736,7 +1738,6 @@
"FXOuRH": "Ausgaben zurückhalten",
"FxUka3": "Nettobetrag",
"FZQER9": "Lösche {type, select, TICKET {Ticket} other {Stufe}}",
"g0KLMH": "z.B. E-Mail-Adresse ist falsch",
"g1BbRX": "Includes Platform Tip: {amount}",
"G65XME": "{name} auswählen",
"GAFyW+": "Dein Passwort wurde aktualisiert.",
Expand Down Expand Up @@ -2443,6 +2444,7 @@
"MonthlyBudget": "Monatliches Budget",
"moreInfo": "Mehr Informationen",
"mp9gR3": "Das Einfrieren des Kollektivs verhindert, dass es Beiträge annimmt und verteilt, bis es die Anforderungen erfüllt. Dies ist eine Sicherheitsmaßnahme, um sicherzustellen, dass die Admins innerhalb ihrer Rechte handeln. Lies mehr.",
"mpLU2S": "e.g, We never worked with this person.",
"mqg/wj": "Datum an dem die Zuwendung empfangen wurde.",
"mqX77s": "Du kannst dieses Kollektiv nicht genehmigen, da es die von dir festgelegte Mindest-Admin-Richtlinie nicht erfüllt.",
"mr2kVW": "Keine Details zum Anzeigen",
Expand Down
2 changes: 1 addition & 1 deletion lang/el-GR.json
Original file line number Diff line number Diff line change
Expand Up @@ -1379,7 +1379,7 @@
"Expense.InvoiceItems": "Προϊόντα",
"Expense.JoinAndSubmit": "Συμμετοχή και υποβολή",
"expense.markAsPaid": "Σήμανση ως πληρωμένο",
"Expense.MarkAsSpamWarning": "This will prevent the submitter account to post new expenses. Are you sure?",
"Expense.MarkAsSpamWarning": "This will prevent the submitter account to post new expenses.",
"Expense.markAsUnpaid": "Σήμανση εξόδων ως ανεξόφλητων",
"expense.markAsUnpaid.btn": "Σήμανση ως ανεξόφλητο",
"Expense.markAsUnpaid.details": "The amount will be credited back to the Collective balance.",
Expand Down
Loading

0 comments on commit 7a38435

Please sign in to comment.