phanpy/src/components/report-modal.jsx

314 lines
10 KiB
React
Raw Normal View History

2024-02-26 08:59:26 +03:00
import './report-modal.css';
2024-08-13 10:26:23 +03:00
import { msg, t, Trans } from '@lingui/macro';
import { useLingui } from '@lingui/react';
2024-02-26 08:59:26 +03:00
import { Fragment } from 'preact';
import { useMemo, useRef, useState } from 'preact/hooks';
import { api } from '../utils/api';
import showToast from '../utils/show-toast';
import { getCurrentInstance } from '../utils/store-utils';
import AccountBlock from './account-block';
import Icon from './icon';
import Loader from './loader';
import Status from './status';
// NOTE: `dislike` hidden for now, it's actually not used for reporting
2024-02-26 09:02:12 +03:00
// Mastodon shows another screen for unfollowing, muting or blocking instead of reporting
2024-02-26 08:59:26 +03:00
const CATEGORIES = [, /*'dislike'*/ 'spam', 'legal', 'violation', 'other'];
// `violation` will be set if there are `rule_ids[]`
const CATEGORIES_INFO = {
// dislike: {
// label: 'Dislike',
// description: 'Not something you want to see',
// },
spam: {
2024-08-13 10:26:23 +03:00
label: msg`Spam`,
description: msg`Malicious links, fake engagement, or repetitive replies`,
2024-02-26 08:59:26 +03:00
},
legal: {
2024-08-13 10:26:23 +03:00
label: msg`Illegal`,
description: msg`Violates the law of your or the server's country`,
2024-02-26 08:59:26 +03:00
},
violation: {
2024-08-13 10:26:23 +03:00
label: msg`Server rule violation`,
description: msg`Breaks specific server rules`,
stampLabel: msg`Violation`,
2024-02-26 08:59:26 +03:00
},
other: {
2024-08-13 10:26:23 +03:00
label: msg`Other`,
description: msg`Issue doesn't fit other categories`,
2024-02-26 08:59:26 +03:00
excludeStamp: true,
},
};
function ReportModal({ account, post, onClose }) {
2024-08-13 10:26:23 +03:00
const { _ } = useLingui();
2024-02-26 08:59:26 +03:00
const { masto } = api();
const [uiState, setUIState] = useState('default');
const [username, domain] = account.acct.split('@');
const [rules, currentDomain] = useMemo(() => {
const { rules, domain } = getCurrentInstance();
return [rules || [], domain];
});
const [selectedCategory, setSelectedCategory] = useState(null);
const [showRules, setShowRules] = useState(false);
const rulesRef = useRef(null);
const [hasRules, setHasRules] = useState(false);
return (
<div class="report-modal-container">
<div class="top-controls">
2024-08-13 10:26:23 +03:00
<h1>{post ? t`Report Post` : t`Report @${username}`}</h1>
2024-02-26 08:59:26 +03:00
<button
type="button"
class="plain4 small"
disabled={uiState === 'loading'}
onClick={() => onClose()}
>
2024-08-13 10:26:23 +03:00
<Icon icon="x" size="xl" alt={t`Close`} />
2024-02-26 08:59:26 +03:00
</button>
</div>
<main>
<div class="report-preview">
{post ? (
<Status status={post} size="s" previewMode />
) : (
<AccountBlock
account={account}
avatarSize="xxl"
useAvatarStatic
showStats
showActivity
/>
)}
</div>
{!!selectedCategory &&
!CATEGORIES_INFO[selectedCategory].excludeStamp && (
<span
class="rubber-stamp"
key={selectedCategory}
aria-hidden="true"
>
2024-08-13 10:26:23 +03:00
{_(
CATEGORIES_INFO[selectedCategory].stampLabel ||
_(CATEGORIES_INFO[selectedCategory].label),
)}
<small>
<Trans>Pending review</Trans>
</small>
2024-02-26 08:59:26 +03:00
</span>
)}
<form
onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.target);
const entries = Object.fromEntries(formData.entries());
console.log('ENTRIES', entries);
let { category, comment, forward } = entries;
if (!comment) comment = undefined;
if (forward === 'on') forward = true;
const ruleIds =
category === 'violation'
? Object.entries(entries)
.filter(([key]) => key.startsWith('rule_ids'))
.map(([key, value]) => value)
: undefined;
const params = {
category,
comment,
forward,
ruleIds,
};
console.log('PARAMS', params);
setUIState('loading');
(async () => {
try {
await masto.v1.reports.create({
accountId: account.id,
statusIds: post?.id ? [post.id] : undefined,
category,
comment,
ruleIds,
forward,
});
setUIState('success');
2024-08-13 10:26:23 +03:00
showToast(post ? t`Post reported` : t`Profile reported`);
2024-02-26 08:59:26 +03:00
onClose();
} catch (error) {
console.error(error);
setUIState('error');
showToast(
error?.message ||
(post
2024-08-13 10:26:23 +03:00
? t`Unable to report post`
: t`Unable to report profile`),
2024-02-26 08:59:26 +03:00
);
}
})();
}}
>
<p>
{post
2024-08-13 10:26:23 +03:00
? t`What's the issue with this post?`
: t`What's the issue with this profile?`}
2024-02-26 08:59:26 +03:00
</p>
<section class="report-categories">
{CATEGORIES.map((category) =>
category === 'violation' && !rules?.length ? null : (
<Fragment key={category}>
<label class="report-category">
<input
type="radio"
name="category"
value={category}
required
disabled={uiState === 'loading'}
onChange={(e) => {
setSelectedCategory(e.target.value);
setShowRules(e.target.value === 'violation');
}}
/>
<span>
2024-08-13 10:26:23 +03:00
{_(CATEGORIES_INFO[category].label)} &nbsp;
2024-02-26 08:59:26 +03:00
<small class="ib insignificant">
2024-08-13 10:26:23 +03:00
{_(CATEGORIES_INFO[category].description)}
2024-02-26 08:59:26 +03:00
</small>
</span>
</label>
{category === 'violation' && !!rules?.length && (
<div
class="shazam-container no-animation"
hidden={!showRules}
>
<div class="shazam-container-inner">
<div class="report-rules" ref={rulesRef}>
{rules.map((rule, i) => (
<label class="report-rule" key={rule.id}>
<input
type="checkbox"
name={`rule_ids[${i}]`}
value={rule.id}
required={showRules && !hasRules}
disabled={uiState === 'loading'}
onChange={(e) => {
const { checked } = e.target;
if (checked) {
setHasRules(true);
} else {
const checkedInputs =
rulesRef.current.querySelectorAll(
'input:checked',
);
if (!checkedInputs.length) {
setHasRules(false);
}
}
}}
/>
<span>{rule.text}</span>
</label>
))}
</div>
</div>
</div>
)}
</Fragment>
),
)}
</section>
<section class="report-comment">
<p>
2024-08-13 10:26:23 +03:00
<label for="report-comment">
<Trans>Additional info</Trans>
</label>
2024-02-26 08:59:26 +03:00
</p>
<textarea
maxlength="1000"
rows="1"
name="comment"
id="report-comment"
disabled={uiState === 'loading'}
/>
</section>
2024-03-06 17:01:13 +03:00
{!!domain && domain !== currentDomain && (
<section>
2024-02-26 08:59:26 +03:00
<p>
<label>
<input
type="checkbox"
switch
name="forward"
disabled={uiState === 'loading'}
/>{' '}
<span>
2024-08-13 10:26:23 +03:00
<Trans>
Forward to <i>{domain}</i>
</Trans>
2024-02-26 08:59:26 +03:00
</span>
</label>
</p>
2024-03-06 17:01:13 +03:00
</section>
)}
2024-02-26 08:59:26 +03:00
<footer>
<button type="submit" disabled={uiState === 'loading'}>
2024-08-13 10:26:23 +03:00
<Trans>Send Report</Trans>
2024-02-26 08:59:26 +03:00
</button>{' '}
<button
type="submit"
class="plain2"
disabled={uiState === 'loading'}
onClick={async () => {
try {
await masto.v1.accounts.$select(account.id).mute(); // Infinite duration
2024-08-13 10:26:23 +03:00
showToast(t`Muted ${username}`);
2024-02-26 08:59:26 +03:00
} catch (e) {
console.error(e);
2024-08-13 10:26:23 +03:00
showToast(t`Unable to mute ${username}`);
2024-02-26 08:59:26 +03:00
}
// onSubmit will still run
}}
>
2024-08-13 10:26:23 +03:00
<Trans>
Send Report <small class="ib">+ Mute profile</small>
</Trans>
2024-02-26 08:59:26 +03:00
</button>{' '}
<button
type="submit"
class="plain2"
disabled={uiState === 'loading'}
onClick={async () => {
try {
await masto.v1.accounts.$select(account.id).block();
2024-08-13 10:26:23 +03:00
showToast(t`Blocked ${username}`);
2024-02-26 08:59:26 +03:00
} catch (e) {
console.error(e);
2024-08-13 10:26:23 +03:00
showToast(t`Unable to block ${username}`);
2024-02-26 08:59:26 +03:00
}
// onSubmit will still run
}}
>
2024-08-13 10:26:23 +03:00
<Trans>
Send Report <small class="ib">+ Block profile</small>
</Trans>
2024-02-26 08:59:26 +03:00
</button>
<Loader hidden={uiState !== 'loading'} />
</footer>
</form>
</main>
</div>
);
}
export default ReportModal;