diff --git a/frontend/src/components/ui/ReportButton.tsx b/frontend/src/components/ui/ReportButton.tsx new file mode 100644 index 0000000..26a24b4 --- /dev/null +++ b/frontend/src/components/ui/ReportButton.tsx @@ -0,0 +1,65 @@ +import { useState, useEffect, useRef, useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { IconFlagFilled } from '@tabler/icons-react' +import Select from './Select' +import * as api from '../../api' +import Tooltip from './Tooltip' + +export default function ReportButton({ contentType, contentId }: { contentType: string; contentId: string }) { + const { t } = useTranslation() + const wrapRef = useRef(null) + + const REASONS = [ + { value: '', label: t('report.selectReason') }, + { value: 'offensive', label: t('report.offensive') }, + { value: 'spam', label: t('report.spam') }, + { value: 'broken', label: t('report.broken') }, + { value: 'other', label: t('report.other') }, + ] + const [reported, setReported] = useState(false) + const [showForm, setShowForm] = useState(false) + const [reason, setReason] = useState('') + + const close = useCallback(() => setShowForm(false), []) + + useEffect(() => { + if (!showForm) return + const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') close() } + const onClick = (e: MouseEvent) => { + const target = e.target + if (wrapRef.current && (!(target instanceof Node) || !wrapRef.current.contains(target))) close() + } + document.addEventListener('keydown', onKey) + document.addEventListener('mousedown', onClick) + return () => { + document.removeEventListener('keydown', onKey) + document.removeEventListener('mousedown', onClick) + } + }, [showForm, close]) + + const submit = async () => { + try { + await api.submitReport(contentType, contentId, String(reason) || 'No reason given') + setReported(true) + setShowForm(false) + } catch { /* ignored */ } + } + + if (reported) return {t('common.reported')} + + return ( +
+ + + + {showForm && ( +
e.stopPropagation()}> +