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)}
|
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;
|