216 lines
5.9 KiB
TypeScript
216 lines
5.9 KiB
TypeScript
import { createFormControl, createFormGroup } from "solid-forms";
|
|
import { For, Match, Switch } from "solid-js";
|
|
|
|
import { Trans, useLingui } from "@lingui-solid/solid/macro";
|
|
import { API, Message as MessageI, Server, User } from "stoat.js";
|
|
import { cva } from "styled-system/css";
|
|
|
|
import { Message } from "@revolt/app";
|
|
import {
|
|
Avatar,
|
|
Column,
|
|
Dialog,
|
|
DialogProps,
|
|
Form2,
|
|
Initials,
|
|
MenuItem,
|
|
} from "@revolt/ui";
|
|
|
|
import { useModals } from "..";
|
|
import { Modals } from "../types";
|
|
|
|
const CONTENT_REPORT_REASONS: API.ContentReportReason[] = [
|
|
"Illegal",
|
|
"IllegalGoods",
|
|
"IllegalExtortion",
|
|
"IllegalPornography",
|
|
"IllegalHacking",
|
|
"ExtremeViolence",
|
|
"PromotesHarm",
|
|
"UnsolicitedSpam",
|
|
"Raid",
|
|
"SpamAbuse",
|
|
"ScamsFraud",
|
|
"Malware",
|
|
"Harassment",
|
|
"NoneSpecified",
|
|
];
|
|
|
|
const USER_REPORT_REASONS: API.UserReportReason[] = [
|
|
"UnsolicitedSpam",
|
|
"SpamAbuse",
|
|
"InappropriateProfile",
|
|
"Impersonation",
|
|
"BanEvasion",
|
|
"Underage",
|
|
"NoneSpecified",
|
|
];
|
|
|
|
/**
|
|
* Modal to report content
|
|
*/
|
|
export function ReportContentModal(
|
|
props: DialogProps & Modals & { type: "report_content" },
|
|
) {
|
|
const { t } = useLingui();
|
|
const { showError } = useModals();
|
|
|
|
const strings: Record<
|
|
API.ContentReportReason | API.UserReportReason,
|
|
string
|
|
> = {
|
|
Illegal: t`Content breaks one or more laws`,
|
|
IllegalGoods: t`Drugs or illegal goods`,
|
|
IllegalExtortion: t`Extortion or blackmail`,
|
|
IllegalPornography: t`Revenge or underage pornography`,
|
|
IllegalHacking: t`Illegal hacking or cracking`,
|
|
ExtremeViolence: t`Extreme violence, gore or animal cruelty`,
|
|
PromotesHarm: t`Promotes harm`,
|
|
UnsolicitedSpam: t`Unsolicited advertising or spam`,
|
|
Raid: t`Raid or spam attack`,
|
|
SpamAbuse: t`Spam or similar platform abuse`,
|
|
ScamsFraud: t`Scams or fraud`,
|
|
Malware: t`Malware or phishing`,
|
|
Harassment: t`Harassment or cyberbullying`,
|
|
NoneSpecified: t`Other`,
|
|
|
|
InappropriateProfile: t`User's profile has inappropriate content`,
|
|
Impersonation: t`Impersonation`,
|
|
BanEvasion: t`Ban evasion`,
|
|
Underage: t`Not of minimum age to use the platform`,
|
|
};
|
|
|
|
const group = createFormGroup({
|
|
category: createFormControl("", { required: true }),
|
|
detail: createFormControl(""),
|
|
});
|
|
|
|
const reasons =
|
|
// eslint-disable-next-line solid/reactivity
|
|
props.target instanceof User ? USER_REPORT_REASONS : CONTENT_REPORT_REASONS;
|
|
|
|
async function onSubmit() {
|
|
try {
|
|
const category = group.controls.category.value;
|
|
const detail = group.controls.detail.value;
|
|
|
|
if (!category || (category === "NoneSpecified" && !detail)) {
|
|
throw new Error("NoReasonProvided");
|
|
}
|
|
|
|
await props.client.api.post("/safety/report", {
|
|
content:
|
|
props.target instanceof User
|
|
? {
|
|
type: "User",
|
|
id: props.target.id,
|
|
report_reason: category as API.UserReportReason,
|
|
message_id: props.contextMessage?.id,
|
|
}
|
|
: props.target instanceof Server
|
|
? {
|
|
type: "Server",
|
|
id: props.target.id,
|
|
report_reason: category as API.ContentReportReason,
|
|
}
|
|
: {
|
|
type: "Message",
|
|
id: props.target.id,
|
|
report_reason: category as API.ContentReportReason,
|
|
},
|
|
additional_context: detail,
|
|
});
|
|
props.onClose();
|
|
} catch (error) {
|
|
showError(error);
|
|
}
|
|
}
|
|
|
|
const submit = Form2.useSubmitHandler(group, onSubmit);
|
|
|
|
return (
|
|
<Dialog
|
|
show={props.show}
|
|
onClose={props.onClose}
|
|
title={
|
|
<Switch>
|
|
<Match when={props.target instanceof User}>
|
|
<Trans>Tell us what's wrong with this user</Trans>
|
|
</Match>
|
|
<Match when={props.target instanceof Server}>
|
|
<Trans>Tell us what's wrong with this server</Trans>
|
|
</Match>
|
|
<Match when={props.target instanceof MessageI}>
|
|
<Trans>Tell us what's wrong with this message</Trans>
|
|
</Match>
|
|
</Switch>
|
|
}
|
|
actions={[
|
|
{ text: <Trans>Cancel</Trans> },
|
|
{
|
|
text: <Trans>Report</Trans>,
|
|
onClick: () => {
|
|
onSubmit();
|
|
return false;
|
|
},
|
|
isDisabled: !Form2.canSubmit(group),
|
|
},
|
|
]}
|
|
isDisabled={group.isPending}
|
|
>
|
|
<form onSubmit={submit}>
|
|
<Column>
|
|
<div class={contentContainer()}>
|
|
{props.target instanceof User ? (
|
|
<Column align>
|
|
<Avatar src={props.target.animatedAvatarURL} size={64} />
|
|
{props.target.displayName}
|
|
</Column>
|
|
) : props.target instanceof Server ? (
|
|
<Column align>
|
|
<Avatar
|
|
src={props.target.animatedIconURL}
|
|
fallback={<Initials input={props.target.name} />}
|
|
size={64}
|
|
/>
|
|
{props.target.name}
|
|
</Column>
|
|
) : (
|
|
<Message message={props.target as never} />
|
|
)}
|
|
</div>
|
|
|
|
<Form2.TextField.Select control={group.controls.category}>
|
|
<MenuItem value="">
|
|
<Trans>Please select a reason</Trans>
|
|
</MenuItem>
|
|
<For each={reasons}>
|
|
{(value) => <MenuItem value={value}>{strings[value]}</MenuItem>}
|
|
</For>
|
|
</Form2.TextField.Select>
|
|
|
|
{/* TODO: use TextEditor? */}
|
|
<Form2.TextField
|
|
name="detail"
|
|
control={group.controls.detail}
|
|
label={t`Give us some detail`}
|
|
/>
|
|
</Column>
|
|
</form>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
const contentContainer = cva({
|
|
base: {
|
|
maxWidth: "100%",
|
|
maxHeight: "80vh",
|
|
overflowY: "hidden",
|
|
"& > div": {
|
|
marginTop: "0 !important",
|
|
pointerEvents: "none",
|
|
userSelect: "none",
|
|
},
|
|
},
|
|
});
|