shinkan-jinkendo/frontend/src/components/ReportContentModal.jsx

201 lines
7.6 KiB
React
Raw Normal View History

/**
* Wiederverwendbares Melde-Modal (P-13).
* Props:
* targetType – 'media_asset' | 'exercise'
* targetId – ID des Zielobjekts
* targetLabel – Anzeigename für den Header (z.B. Dateiname oder Übungstitel)
* onClose – Callback zum Schließen
*/
import React, { useState } from 'react'
import { useAuth } from '../context/AuthContext'
import api from '../utils/api'
const REASON_OPTIONS = [
{ value: '', label: '— Grund auswählen —' },
{ value: 'copyright', label: 'Urheberrechtsverletzung' },
{ value: 'image_rights', label: 'Bildrechtsverletzung / Recht am eigenen Bild' },
{ value: 'privacy', label: 'Datenschutz / Persönlichkeitsrecht' },
{ value: 'minors', label: 'Darstellung Minderjähriger' },
{ value: 'illegal_content', label: 'Rechtswidriger Inhalt' },
{ value: 'youth_protection', label: 'Jugendschutz' },
{ value: 'offensive_content', label: 'Beleidigender / anstößiger Inhalt' },
{ value: 'other', label: 'Sonstiges' },
]
export default function ReportContentModal({ targetType, targetId, targetLabel, onClose, onSuccess }) {
const { user } = useAuth()
const [reason, setReason] = useState('')
const [description, setDescription] = useState('')
const [name, setName] = useState(user?.name || '')
const [email, setEmail] = useState(user?.email || '')
const [confirmed, setConfirmed] = useState(false)
const [saving, setSaving] = useState(false)
const [error, setError] = useState(null)
const [success, setSuccess] = useState(false)
async function handleSubmit(e) {
e.preventDefault()
if (!reason) { setError('Bitte einen Meldegrund auswählen.'); return }
if (description.trim().length < 10) { setError('Beschreibung muss mindestens 10 Zeichen haben.'); return }
if (!name.trim()) { setError('Bitte deinen Namen eingeben.'); return }
if (!email.trim()) { setError('Bitte deine E-Mail-Adresse eingeben.'); return }
if (!confirmed) { setError('Bitte bestätige die Gutglaubenserklärung.'); return }
setSaving(true)
setError(null)
try {
await api.submitContentReport({
target_type: targetType,
target_id: targetId,
report_reason: reason,
report_description: description.trim(),
reporter_name: name.trim(),
reporter_email: email.trim(),
good_faith_confirmed: true,
})
setSuccess(true)
if (onSuccess) onSuccess()
} catch (err) {
setError(err.message || String(err))
} finally {
setSaving(false)
}
}
return (
<div
style={{
position: 'fixed',
inset: 0,
background: 'rgba(0,0,0,0.55)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1200,
padding: '1rem',
overflowY: 'auto',
}}
onClick={(e) => { if (e.target === e.currentTarget) onClose() }}
>
<div
style={{
background: 'var(--surface)',
borderRadius: '12px',
padding: '1.5rem',
maxWidth: '480px',
width: '100%',
marginTop: '2rem',
marginBottom: '2rem',
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<h2 style={{ margin: 0, fontSize: '1.1rem' }}>Inhalt melden</h2>
<button type="button" className="btn btn-secondary" style={{ padding: '4px 10px' }} onClick={onClose}>✕</button>
</div>
{targetLabel && (
<p className="muted" style={{ fontSize: '0.85rem', marginBottom: '1rem', marginTop: 0 }}>
{targetType === 'media_asset' ? 'Medium' : 'Übung'}: <strong>{targetLabel}</strong>
</p>
)}
{success ? (
<div>
<p style={{ color: 'var(--accent)', fontWeight: 600 }}>Meldung eingegangen.</p>
<p className="muted" style={{ fontSize: '0.9rem' }}>
Deine Meldung wurde gespeichert und wird von unseren Moderatoren geprüft. Vielen Dank.
</p>
<button type="button" className="btn btn-primary btn-full" onClick={onClose}>
Schließen
</button>
</div>
) : (
<form onSubmit={handleSubmit}>
<div className="form-row">
<label className="form-label">Meldegrund *</label>
<select
className="form-input"
value={reason}
onChange={(e) => setReason(e.target.value)}
required
>
{REASON_OPTIONS.map((o) => (
<option key={o.value} value={o.value}>{o.label}</option>
))}
</select>
</div>
<div className="form-row">
<label className="form-label">Beschreibung * (mind. 10 Zeichen)</label>
<textarea
className="form-input"
rows={3}
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Bitte erläutere kurz, warum du diesen Inhalt meldest."
required
/>
</div>
<div className="form-row">
<label className="form-label">Dein Name *</label>
<input
type="text"
className="form-input"
value={name}
onChange={user?.name ? undefined : (e) => setName(e.target.value)}
readOnly={!!user?.name}
style={user?.name ? { background: 'var(--surface2)', color: 'var(--text2)' } : undefined}
required
/>
</div>
<div className="form-row">
<label className="form-label">Deine E-Mail-Adresse *</label>
<input
type="email"
className="form-input"
value={email}
onChange={user?.email ? undefined : (e) => setEmail(e.target.value)}
readOnly={!!user?.email}
style={user?.email ? { background: 'var(--surface2)', color: 'var(--text2)' } : undefined}
required
/>
</div>
<div className="form-row">
<label style={{ display: 'flex', gap: '0.6rem', alignItems: 'flex-start', cursor: 'pointer' }}>
<input
type="checkbox"
checked={confirmed}
onChange={(e) => setConfirmed(e.target.checked)}
style={{ marginTop: '3px', flexShrink: 0 }}
/>
<span style={{ fontSize: '0.85rem', color: 'var(--text2)' }}>
Ich melde diesen Inhalt nach bestem Wissen und Gewissen. Ich bestätige, dass meine Meldung
nicht missbräuchlich ist und ich der Überzeugung bin, dass der gemeldete Inhalt rechtswidrig
ist oder gegen die Nutzungsbedingungen verstößt.
</span>
</label>
</div>
{error && (
<p style={{ color: 'var(--danger)', fontSize: '0.88rem', margin: '0.5rem 0' }}>{error}</p>
)}
<div style={{ display: 'flex', gap: '0.5rem', marginTop: '1rem' }}>
<button type="submit" className="btn btn-primary" style={{ flex: 1 }} disabled={saving}>
{saving ? 'Wird gesendet…' : 'Meldung absenden'}
</button>
<button type="button" className="btn btn-secondary" onClick={onClose} disabled={saving}>
Abbrechen
</button>
</div>
</form>
)}
</div>
</div>
)
}