Add ability to report a Group
This commit is contained in:
parent
948d66bcab
commit
30ef70440f
15 changed files with 206 additions and 63 deletions
|
@ -4,7 +4,7 @@ import { openModal } from './modals';
|
|||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { Account, ChatMessage, Status } from 'soapbox/types/entities';
|
||||
import type { Account, ChatMessage, Group, Status } from 'soapbox/types/entities';
|
||||
|
||||
const REPORT_INIT = 'REPORT_INIT';
|
||||
const REPORT_CANCEL = 'REPORT_CANCEL';
|
||||
|
@ -20,19 +20,29 @@ const REPORT_BLOCK_CHANGE = 'REPORT_BLOCK_CHANGE';
|
|||
|
||||
const REPORT_RULE_CHANGE = 'REPORT_RULE_CHANGE';
|
||||
|
||||
enum ReportableEntities {
|
||||
ACCOUNT = 'ACCOUNT',
|
||||
CHAT_MESSAGE = 'CHAT_MESSAGE',
|
||||
GROUP = 'GROUP',
|
||||
STATUS = 'STATUS'
|
||||
}
|
||||
|
||||
type ReportedEntity = {
|
||||
status?: Status
|
||||
chatMessage?: ChatMessage
|
||||
group?: Group
|
||||
}
|
||||
|
||||
const initReport = (account: Account, entities?: ReportedEntity) => (dispatch: AppDispatch) => {
|
||||
const { status, chatMessage } = entities || {};
|
||||
const initReport = (entityType: ReportableEntities, account: Account, entities?: ReportedEntity) => (dispatch: AppDispatch) => {
|
||||
const { status, chatMessage, group } = entities || {};
|
||||
|
||||
dispatch({
|
||||
type: REPORT_INIT,
|
||||
entityType,
|
||||
account,
|
||||
status,
|
||||
chatMessage,
|
||||
group,
|
||||
});
|
||||
|
||||
return dispatch(openModal('REPORT'));
|
||||
|
@ -56,7 +66,8 @@ const submitReport = () =>
|
|||
return api(getState).post('/api/v1/reports', {
|
||||
account_id: reports.getIn(['new', 'account_id']),
|
||||
status_ids: reports.getIn(['new', 'status_ids']),
|
||||
message_ids: [reports.getIn(['new', 'chat_message', 'id'])],
|
||||
message_ids: [reports.getIn(['new', 'chat_message', 'id'])].filter(Boolean),
|
||||
group_id: reports.getIn(['new', 'group', 'id']),
|
||||
rule_ids: reports.getIn(['new', 'rule_ids']),
|
||||
comment: reports.getIn(['new', 'comment']),
|
||||
forward: reports.getIn(['new', 'forward']),
|
||||
|
@ -97,6 +108,7 @@ const changeReportRule = (ruleId: string) => ({
|
|||
});
|
||||
|
||||
export {
|
||||
ReportableEntities,
|
||||
REPORT_INIT,
|
||||
REPORT_CANCEL,
|
||||
REPORT_SUBMIT_REQUEST,
|
||||
|
|
|
@ -12,7 +12,7 @@ import { toggleBookmark, toggleFavourite, togglePin, toggleReblog } from 'soapbo
|
|||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { deleteStatusModal, toggleStatusSensitivityModal } from 'soapbox/actions/moderation';
|
||||
import { initMuteModal } from 'soapbox/actions/mutes';
|
||||
import { initReport } from 'soapbox/actions/reports';
|
||||
import { initReport, ReportableEntities } from 'soapbox/actions/reports';
|
||||
import { deleteStatus, editStatus, toggleMuteStatus } from 'soapbox/actions/statuses';
|
||||
import DropdownMenu from 'soapbox/components/dropdown-menu';
|
||||
import StatusActionButton from 'soapbox/components/status-action-button';
|
||||
|
@ -254,7 +254,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
|||
secondary: intl.formatMessage(messages.blockAndReport),
|
||||
onSecondary: () => {
|
||||
dispatch(blockAccount(account.id));
|
||||
dispatch(initReport(account, { status }));
|
||||
dispatch(initReport(ReportableEntities.STATUS, account, { status }));
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
@ -271,7 +271,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
|||
};
|
||||
|
||||
const handleReport: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
dispatch(initReport(status.account as Account, { status }));
|
||||
dispatch(initReport(ReportableEntities.STATUS, status.account as Account, { status }));
|
||||
};
|
||||
|
||||
const handleConversationMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
|
|
|
@ -14,7 +14,7 @@ interface IIconButton extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|||
/** Don't render a background behind the icon. */
|
||||
transparent?: boolean
|
||||
/** Predefined styles to display for the button. */
|
||||
theme?: 'seamless' | 'outlined'
|
||||
theme?: 'seamless' | 'outlined' | 'secondary'
|
||||
/** Override the data-testid */
|
||||
'data-testid'?: string
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ const IconButton = React.forwardRef((props: IIconButton, ref: React.ForwardedRef
|
|||
className={clsx('flex items-center space-x-2 rounded-full p-1 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:ring-offset-0', {
|
||||
'bg-white dark:bg-transparent': !transparent,
|
||||
'border border-solid bg-transparent border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-900 dark:text-gray-100 focus:ring-primary-500': theme === 'outlined',
|
||||
'border-transparent bg-primary-100 dark:bg-primary-800 hover:bg-primary-50 dark:hover:bg-primary-700 focus:bg-primary-100 dark:focus:bg-primary-800 text-primary-500 dark:text-primary-200': theme === 'secondary',
|
||||
'opacity-50': filteredProps.disabled,
|
||||
}, className)}
|
||||
{...filteredProps}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { mentionCompose, directCompose } from 'soapbox/actions/compose';
|
|||
import { blockDomain, unblockDomain } from 'soapbox/actions/domain-blocks';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { initMuteModal } from 'soapbox/actions/mutes';
|
||||
import { initReport } from 'soapbox/actions/reports';
|
||||
import { initReport, ReportableEntities } from 'soapbox/actions/reports';
|
||||
import { setSearchAccount } from 'soapbox/actions/search';
|
||||
import { getSettings } from 'soapbox/actions/settings';
|
||||
import Badge from 'soapbox/components/badge';
|
||||
|
@ -136,7 +136,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
|||
secondary: intl.formatMessage(messages.blockAndReport),
|
||||
onSecondary: () => {
|
||||
dispatch(blockAccount(account.id));
|
||||
dispatch(initReport(account));
|
||||
dispatch(initReport(ReportableEntities.ACCOUNT, account));
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
|||
};
|
||||
|
||||
const onReport = () => {
|
||||
dispatch(initReport(account));
|
||||
dispatch(initReport(ReportableEntities.ACCOUNT, account));
|
||||
};
|
||||
|
||||
const onMute = () => {
|
||||
|
|
|
@ -6,7 +6,7 @@ import React, { useMemo, useState } from 'react';
|
|||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { initReport } from 'soapbox/actions/reports';
|
||||
import { initReport, ReportableEntities } from 'soapbox/actions/reports';
|
||||
import DropdownMenu from 'soapbox/components/dropdown-menu';
|
||||
import { HStack, Icon, Stack, Text } from 'soapbox/components/ui';
|
||||
import emojify from 'soapbox/features/emoji';
|
||||
|
@ -24,7 +24,7 @@ import ChatMessageReactionWrapper from './chat-message-reaction-wrapper/chat-mes
|
|||
|
||||
import type { Menu as IMenu } from 'soapbox/components/dropdown-menu';
|
||||
import type { IMediaGallery } from 'soapbox/components/media-gallery';
|
||||
import type { ChatMessage as ChatMessageEntity } from 'soapbox/types/entities';
|
||||
import type { Account, ChatMessage as ChatMessageEntity } from 'soapbox/types/entities';
|
||||
|
||||
const messages = defineMessages({
|
||||
copy: { id: 'chats.actions.copy', defaultMessage: 'Copy' },
|
||||
|
@ -178,7 +178,7 @@ const ChatMessage = (props: IChatMessage) => {
|
|||
if (features.reportChats) {
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.report),
|
||||
action: () => dispatch(initReport(normalizeAccount(chat.account) as any, { chatMessage } as any)),
|
||||
action: () => dispatch(initReport(ReportableEntities.CHAT_MESSAGE, normalizeAccount(chat.account) as Account, { chatMessage })),
|
||||
icon: require('@tabler/icons/flag.svg'),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import { toggleBookmark, togglePin, toggleReblog } from 'soapbox/actions/interac
|
|||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { deleteStatusModal, toggleStatusSensitivityModal } from 'soapbox/actions/moderation';
|
||||
import { initMuteModal } from 'soapbox/actions/mutes';
|
||||
import { initReport } from 'soapbox/actions/reports';
|
||||
import { initReport, ReportableEntities } from 'soapbox/actions/reports';
|
||||
import { deleteStatus } from 'soapbox/actions/statuses';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import StillImage from 'soapbox/components/still-image';
|
||||
|
@ -176,13 +176,13 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
|||
secondary: intl.formatMessage(messages.blockAndReport),
|
||||
onSecondary: () => {
|
||||
dispatch(blockAccount(account.id));
|
||||
dispatch(initReport(account, { status }));
|
||||
dispatch(initReport(ReportableEntities.STATUS, account, { status }));
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const handleReport = () => {
|
||||
dispatch(initReport(account, { status }));
|
||||
dispatch(initReport(ReportableEntities.STATUS, account, { status }));
|
||||
};
|
||||
|
||||
const handleModerate = () => {
|
||||
|
|
|
@ -12,6 +12,7 @@ import { isDefaultHeader } from 'soapbox/utils/accounts';
|
|||
|
||||
import GroupActionButton from './group-action-button';
|
||||
import GroupMemberCount from './group-member-count';
|
||||
import GroupOptionsButton from './group-options-button';
|
||||
import GroupPrivacy from './group-privacy';
|
||||
import GroupRelationship from './group-relationship';
|
||||
|
||||
|
@ -140,7 +141,10 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
|
|||
/>
|
||||
</Stack>
|
||||
|
||||
<GroupActionButton group={group} />
|
||||
<HStack alignItems='center' space={2}>
|
||||
<GroupOptionsButton group={group} />
|
||||
<GroupActionButton group={group} />
|
||||
</HStack>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import React, { useMemo } from 'react';
|
||||
|
||||
import { initReport, ReportableEntities } from 'soapbox/actions/reports';
|
||||
import DropdownMenu, { Menu } from 'soapbox/components/dropdown-menu';
|
||||
import { IconButton } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
|
||||
import { GroupRoles } from 'soapbox/schemas/group-member';
|
||||
|
||||
import type { Account, Group } from 'soapbox/types/entities';
|
||||
|
||||
interface IGroupActionButton {
|
||||
group: Group
|
||||
}
|
||||
|
||||
const GroupOptionsButton = ({ group }: IGroupActionButton) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const account = useOwnAccount();
|
||||
|
||||
const isMember = group.relationship?.role === GroupRoles.USER;
|
||||
const isBlocked = group.relationship?.blocked_by;
|
||||
|
||||
const menu: Menu = useMemo(() => ([
|
||||
{
|
||||
text: 'Report',
|
||||
icon: require('@tabler/icons/flag.svg'),
|
||||
action: () => dispatch(initReport(ReportableEntities.GROUP, account as Account, { group })),
|
||||
},
|
||||
]), []);
|
||||
|
||||
if (isBlocked || !isMember || menu.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu items={menu} placement='bottom'>
|
||||
<IconButton
|
||||
src={require('@tabler/icons/dots.svg')}
|
||||
theme='secondary'
|
||||
iconClassName='h-5 w-5'
|
||||
className='self-stretch px-2.5'
|
||||
/>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default GroupOptionsButton;
|
|
@ -2,6 +2,7 @@ import userEvent from '@testing-library/user-event';
|
|||
import { Map as ImmutableMap, Record as ImmutableRecord, Set as ImmutableSet } from 'immutable';
|
||||
import React from 'react';
|
||||
|
||||
import { ReportableEntities } from 'soapbox/actions/reports';
|
||||
import { __stub } from 'soapbox/api';
|
||||
|
||||
import { render, screen, waitFor } from '../../../../../../jest/test-helpers';
|
||||
|
@ -29,6 +30,7 @@ describe('<ReportModal />', () => {
|
|||
account_id: '1',
|
||||
status_ids: ImmutableSet(['1']),
|
||||
rule_ids: ImmutableSet(),
|
||||
entityType: ReportableEntities.STATUS,
|
||||
})(),
|
||||
})(),
|
||||
statuses: ImmutableMap({
|
||||
|
|
|
@ -2,9 +2,10 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { blockAccount } from 'soapbox/actions/accounts';
|
||||
import { submitReport, submitReportSuccess, submitReportFail } from 'soapbox/actions/reports';
|
||||
import { submitReport, submitReportSuccess, submitReportFail, ReportableEntities } from 'soapbox/actions/reports';
|
||||
import { expandAccountTimeline } from 'soapbox/actions/timelines';
|
||||
import AttachmentThumbs from 'soapbox/components/attachment-thumbs';
|
||||
import GroupCard from 'soapbox/components/group-card';
|
||||
import List, { ListItem } from 'soapbox/components/list';
|
||||
import StatusContent from 'soapbox/components/status-content';
|
||||
import { Avatar, HStack, Icon, Modal, ProgressBar, Stack, Text } from 'soapbox/components/ui';
|
||||
|
@ -24,6 +25,7 @@ const messages = defineMessages({
|
|||
submit: { id: 'report.submit', defaultMessage: 'Submit' },
|
||||
reportContext: { id: 'report.chatMessage.context', defaultMessage: 'When reporting a user’s message, the five messages before and five messages after the one selected will be passed along to our moderation team for context.' },
|
||||
reportMessage: { id: 'report.chatMessage.title', defaultMessage: 'Report message' },
|
||||
reportGroup: { id: 'report.group.title', defaultMessage: 'Report Group' },
|
||||
cancel: { id: 'common.cancel', defaultMessage: 'Cancel' },
|
||||
previous: { id: 'report.previous', defaultMessage: 'Previous' },
|
||||
});
|
||||
|
@ -35,9 +37,26 @@ enum Steps {
|
|||
}
|
||||
|
||||
const reportSteps = {
|
||||
ONE: ReasonStep,
|
||||
TWO: OtherActionsStep,
|
||||
THREE: ConfirmationStep,
|
||||
[ReportableEntities.ACCOUNT]: {
|
||||
ONE: ReasonStep,
|
||||
TWO: OtherActionsStep,
|
||||
THREE: ConfirmationStep,
|
||||
},
|
||||
[ReportableEntities.CHAT_MESSAGE]: {
|
||||
ONE: ReasonStep,
|
||||
TWO: OtherActionsStep,
|
||||
THREE: ConfirmationStep,
|
||||
},
|
||||
[ReportableEntities.STATUS]: {
|
||||
ONE: ReasonStep,
|
||||
TWO: OtherActionsStep,
|
||||
THREE: ConfirmationStep,
|
||||
},
|
||||
[ReportableEntities.GROUP]: {
|
||||
ONE: ReasonStep,
|
||||
TWO: ConfirmationStep,
|
||||
THREE: null,
|
||||
},
|
||||
};
|
||||
|
||||
const SelectedStatus = ({ statusId }: { statusId: string }) => {
|
||||
|
@ -76,12 +95,6 @@ interface IReportModal {
|
|||
onClose: () => void
|
||||
}
|
||||
|
||||
enum ReportedEntities {
|
||||
Account = 'Account',
|
||||
Status = 'Status',
|
||||
ChatMessage = 'ChatMessage'
|
||||
}
|
||||
|
||||
const ReportModal = ({ onClose }: IReportModal) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
@ -89,27 +102,20 @@ const ReportModal = ({ onClose }: IReportModal) => {
|
|||
const accountId = useAppSelector((state) => state.reports.new.account_id);
|
||||
const account = useAccount(accountId as string);
|
||||
|
||||
const entityType = useAppSelector((state) => state.reports.new.entityType);
|
||||
const isBlocked = useAppSelector((state) => state.reports.new.block);
|
||||
const isSubmitting = useAppSelector((state) => state.reports.new.isSubmitting);
|
||||
const rules = useAppSelector((state) => state.rules.items);
|
||||
const ruleIds = useAppSelector((state) => state.reports.new.rule_ids);
|
||||
const selectedStatusIds = useAppSelector((state) => state.reports.new.status_ids);
|
||||
const selectedChatMessage = useAppSelector((state) => state.reports.new.chat_message);
|
||||
const selectedGroup = useAppSelector((state) => state.reports.new.group);
|
||||
|
||||
const shouldRequireRule = rules.length > 0;
|
||||
|
||||
const reportedEntity = useMemo(() => {
|
||||
if (selectedStatusIds.size === 0 && !selectedChatMessage) {
|
||||
return ReportedEntities.Account;
|
||||
} else if (selectedChatMessage) {
|
||||
return ReportedEntities.ChatMessage;
|
||||
} else {
|
||||
return ReportedEntities.Status;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const isReportingAccount = reportedEntity === ReportedEntities.Account;
|
||||
const isReportingStatus = reportedEntity === ReportedEntities.Status;
|
||||
const isReportingAccount = entityType === ReportableEntities.ACCOUNT;
|
||||
const isReportingStatus = entityType === ReportableEntities.STATUS;
|
||||
const isReportingGroup = entityType === ReportableEntities.GROUP;
|
||||
|
||||
const [currentStep, setCurrentStep] = useState<Steps>(Steps.ONE);
|
||||
|
||||
|
@ -160,22 +166,41 @@ const ReportModal = ({ onClose }: IReportModal) => {
|
|||
|
||||
const confirmationText = useMemo(() => {
|
||||
switch (currentStep) {
|
||||
case Steps.ONE:
|
||||
if (isReportingGroup) {
|
||||
return intl.formatMessage(messages.submit);
|
||||
} else {
|
||||
return intl.formatMessage(messages.next);
|
||||
}
|
||||
case Steps.TWO:
|
||||
return intl.formatMessage(messages.submit);
|
||||
if (isReportingGroup) {
|
||||
return intl.formatMessage(messages.done);
|
||||
} else {
|
||||
return intl.formatMessage(messages.submit);
|
||||
}
|
||||
case Steps.THREE:
|
||||
return intl.formatMessage(messages.done);
|
||||
default:
|
||||
return intl.formatMessage(messages.next);
|
||||
}
|
||||
}, [currentStep]);
|
||||
}, [currentStep, isReportingGroup]);
|
||||
|
||||
const handleNextStep = () => {
|
||||
switch (currentStep) {
|
||||
case Steps.ONE:
|
||||
setCurrentStep(Steps.TWO);
|
||||
if (isReportingGroup) {
|
||||
handleSubmit();
|
||||
} else {
|
||||
setCurrentStep(Steps.TWO);
|
||||
}
|
||||
break;
|
||||
case Steps.TWO:
|
||||
handleSubmit();
|
||||
if (isReportingGroup) {
|
||||
dispatch(submitReportSuccess());
|
||||
onClose();
|
||||
} else {
|
||||
handleSubmit();
|
||||
}
|
||||
break;
|
||||
case Steps.THREE:
|
||||
dispatch(submitReportSuccess());
|
||||
|
@ -212,19 +237,35 @@ const ReportModal = ({ onClose }: IReportModal) => {
|
|||
}
|
||||
};
|
||||
|
||||
const renderSelectedGroup = () => {
|
||||
if (selectedGroup) {
|
||||
return <GroupCard group={selectedGroup} />;
|
||||
}
|
||||
};
|
||||
|
||||
const renderSelectedEntity = () => {
|
||||
switch (reportedEntity) {
|
||||
case ReportedEntities.Status:
|
||||
switch (entityType) {
|
||||
case ReportableEntities.STATUS:
|
||||
return renderSelectedStatuses();
|
||||
case ReportedEntities.ChatMessage:
|
||||
case ReportableEntities.CHAT_MESSAGE:
|
||||
return renderSelectedChatMessage();
|
||||
case ReportableEntities.GROUP:
|
||||
if (currentStep === Steps.TWO) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return renderSelectedGroup();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const renderTitle = () => {
|
||||
switch (reportedEntity) {
|
||||
case ReportedEntities.ChatMessage:
|
||||
switch (entityType) {
|
||||
case ReportableEntities.CHAT_MESSAGE:
|
||||
return intl.formatMessage(messages.reportMessage);
|
||||
case ReportableEntities.GROUP:
|
||||
return intl.formatMessage(messages.reportGroup);
|
||||
default:
|
||||
return <FormattedMessage id='report.target' defaultMessage='Reporting {target}' values={{ target: <strong>@{account?.acct}</strong> }} />;
|
||||
}
|
||||
|
@ -252,16 +293,16 @@ const ReportModal = ({ onClose }: IReportModal) => {
|
|||
}, [currentStep]);
|
||||
|
||||
useEffect(() => {
|
||||
if (account) {
|
||||
if (account?.id) {
|
||||
dispatch(expandAccountTimeline(account.id, { withReplies: true, maxId: null }));
|
||||
}
|
||||
}, [account]);
|
||||
}, [account?.id]);
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const StepToRender = reportSteps[currentStep];
|
||||
const StepToRender = reportSteps[entityType][currentStep];
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
@ -279,7 +320,9 @@ const ReportModal = ({ onClose }: IReportModal) => {
|
|||
|
||||
{(currentStep !== Steps.THREE && !isReportingAccount) && renderSelectedEntity()}
|
||||
|
||||
<StepToRender account={account} />
|
||||
{StepToRender && (
|
||||
<StepToRender account={account} />
|
||||
)}
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { ReportableEntities } from 'soapbox/actions/reports';
|
||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||
import { Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
|
@ -8,8 +9,10 @@ import { useAppSelector } from 'soapbox/hooks';
|
|||
import type { ReducerAccount } from 'soapbox/reducers/accounts';
|
||||
|
||||
const messages = defineMessages({
|
||||
accountEntity: { id: 'report.confirmation.entity.account', defaultMessage: 'account' },
|
||||
groupEntity: { id: 'report.confirmation.entity.group', defaultMessage: 'group' },
|
||||
title: { id: 'report.confirmation.title', defaultMessage: 'Thanks for submitting your report.' },
|
||||
content: { id: 'report.confirmation.content', defaultMessage: 'If we find that this account is violating the {link} we will take further action on the matter.' },
|
||||
content: { id: 'report.confirmation.content', defaultMessage: 'If we find that this {entity} is violating the {link} we will take further action on the matter.' },
|
||||
});
|
||||
|
||||
interface IOtherActionsStep {
|
||||
|
@ -34,6 +37,11 @@ const renderTermsOfServiceLink = (href: string) => (
|
|||
const ConfirmationStep = ({ account }: IOtherActionsStep) => {
|
||||
const intl = useIntl();
|
||||
const links = useAppSelector((state) => getSoapboxConfig(state).get('links') as any);
|
||||
const entityType = useAppSelector((state) => state.reports.new.entityType);
|
||||
|
||||
const entity = entityType === ReportableEntities.GROUP
|
||||
? intl.formatMessage(messages.groupEntity)
|
||||
: intl.formatMessage(messages.accountEntity);
|
||||
|
||||
return (
|
||||
<Stack space={1}>
|
||||
|
@ -43,6 +51,7 @@ const ConfirmationStep = ({ account }: IOtherActionsStep) => {
|
|||
|
||||
<Text>
|
||||
{intl.formatMessage(messages.content, {
|
||||
entity,
|
||||
link: links.get('termsOfService') ?
|
||||
renderTermsOfServiceLink(links.get('termsOfService')) :
|
||||
termsOfServiceText,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import clsx from 'clsx';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { changeReportComment, changeReportRule } from 'soapbox/actions/reports';
|
||||
import { changeReportComment, changeReportRule, ReportableEntities } from 'soapbox/actions/reports';
|
||||
import { fetchRules } from 'soapbox/actions/rules';
|
||||
import { FormGroup, Stack, Text, Textarea } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
|
@ -29,14 +29,12 @@ const ReasonStep = (_props: IReasonStep) => {
|
|||
const [isNearBottom, setNearBottom] = useState<boolean>(false);
|
||||
const [isNearTop, setNearTop] = useState<boolean>(true);
|
||||
|
||||
const entityType = useAppSelector((state) => state.reports.new.entityType);
|
||||
const comment = useAppSelector((state) => state.reports.new.comment);
|
||||
const rules = useAppSelector((state) => state.rules.items);
|
||||
const ruleIds = useAppSelector((state) => state.reports.new.rule_ids);
|
||||
const shouldRequireRule = rules.length > 0;
|
||||
|
||||
const selectedStatusIds = useAppSelector((state) => state.reports.new.status_ids);
|
||||
const isReportingAccount = useMemo(() => selectedStatusIds.size === 0, []);
|
||||
|
||||
const handleCommentChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
dispatch(changeReportComment(event.target.value));
|
||||
};
|
||||
|
@ -60,7 +58,23 @@ const ReasonStep = (_props: IReasonStep) => {
|
|||
};
|
||||
|
||||
const filterRuleType = (rule: any) => {
|
||||
const ruleTypeToFilter = isReportingAccount ? 'account' : 'content';
|
||||
let ruleTypeToFilter = 'content';
|
||||
|
||||
switch (entityType) {
|
||||
case ReportableEntities.ACCOUNT:
|
||||
ruleTypeToFilter = 'account';
|
||||
break;
|
||||
case ReportableEntities.STATUS:
|
||||
case ReportableEntities.CHAT_MESSAGE:
|
||||
ruleTypeToFilter = 'content';
|
||||
break;
|
||||
case ReportableEntities.GROUP:
|
||||
ruleTypeToFilter = 'group';
|
||||
break;
|
||||
default:
|
||||
ruleTypeToFilter = 'content';
|
||||
break;
|
||||
}
|
||||
|
||||
if (rule.rule_type) {
|
||||
return rule.rule_type === ruleTypeToFilter;
|
||||
|
|
|
@ -1241,11 +1241,14 @@
|
|||
"report.block_hint": "Do you also want to block this account?",
|
||||
"report.chatMessage.context": "When reporting a user’s message, the five messages before and five messages after the one selected will be passed along to our moderation team for context.",
|
||||
"report.chatMessage.title": "Report message",
|
||||
"report.confirmation.content": "If we find that this account is violating the {link} we will take further action on the matter.",
|
||||
"report.confirmation.content": "If we find that this {entity} is violating the {link} we will take further action on the matter.",
|
||||
"report.confirmation.entity.account": "account",
|
||||
"report.confirmation.entity.group": "group",
|
||||
"report.confirmation.title": "Thanks for submitting your report.",
|
||||
"report.done": "Done",
|
||||
"report.forward": "Forward to {target}",
|
||||
"report.forward_hint": "The account is from another server. Send a copy of the report there as well?",
|
||||
"report.group.title": "Report Group",
|
||||
"report.next": "Next",
|
||||
"report.otherActions.addAdditional": "Would you like to add additional statuses to this report?",
|
||||
"report.otherActions.addMore": "Add more",
|
||||
|
|
|
@ -8,6 +8,8 @@ describe('reports reducer', () => {
|
|||
account_id: null,
|
||||
status_ids: [],
|
||||
chat_message: null,
|
||||
group: null,
|
||||
entityType: '',
|
||||
comment: '',
|
||||
forward: false,
|
||||
block: false,
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { Record as ImmutableRecord, Set as ImmutableSet } from 'immutable';
|
||||
|
||||
import { ChatMessage } from 'soapbox/types/entities';
|
||||
|
||||
import {
|
||||
REPORT_INIT,
|
||||
REPORT_SUBMIT_REQUEST,
|
||||
|
@ -13,15 +11,19 @@ import {
|
|||
REPORT_FORWARD_CHANGE,
|
||||
REPORT_BLOCK_CHANGE,
|
||||
REPORT_RULE_CHANGE,
|
||||
ReportableEntities,
|
||||
} from '../actions/reports';
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
import type { ChatMessage, Group } from 'soapbox/types/entities';
|
||||
|
||||
const NewReportRecord = ImmutableRecord({
|
||||
isSubmitting: false,
|
||||
entityType: '' as ReportableEntities,
|
||||
account_id: null as string | null,
|
||||
status_ids: ImmutableSet<string>(),
|
||||
chat_message: null as null | ChatMessage,
|
||||
group: null as null | Group,
|
||||
comment: '',
|
||||
forward: false,
|
||||
block: false,
|
||||
|
@ -40,11 +42,16 @@ export default function reports(state: State = ReducerRecord(), action: AnyActio
|
|||
return state.withMutations(map => {
|
||||
map.setIn(['new', 'isSubmitting'], false);
|
||||
map.setIn(['new', 'account_id'], action.account.id);
|
||||
map.setIn(['new', 'entityType'], action.entityType);
|
||||
|
||||
if (action.chatMessage) {
|
||||
map.setIn(['new', 'chat_message'], action.chatMessage);
|
||||
}
|
||||
|
||||
if (action.group) {
|
||||
map.setIn(['new', 'group'], action.group);
|
||||
}
|
||||
|
||||
if (state.new.account_id !== action.account.id) {
|
||||
map.setIn(['new', 'status_ids'], action.status ? ImmutableSet([action.status.reblog?.id || action.status.id]) : ImmutableSet());
|
||||
map.setIn(['new', 'comment'], '');
|
||||
|
|
Loading…
Reference in a new issue