Admin: preset reason options for ban/suspend/activate modals + custom option
This commit is contained in:
parent
e5fd9bcaa5
commit
1d8ef9135e
|
|
@ -28,6 +28,16 @@ export default function ModerationPage() {
|
||||||
const [statusFilter, setStatusFilter] = useState('pending');
|
const [statusFilter, setStatusFilter] = useState('pending');
|
||||||
const [reviewingId, setReviewingId] = useState<string | null>(null);
|
const [reviewingId, setReviewingId] = useState<string | null>(null);
|
||||||
const [reason, setReason] = useState('');
|
const [reason, setReason] = useState('');
|
||||||
|
const [customReason, setCustomReason] = useState(false);
|
||||||
|
|
||||||
|
const banReasons = [
|
||||||
|
'Hate speech or slurs',
|
||||||
|
'Harassment or bullying',
|
||||||
|
'Spam or scam activity',
|
||||||
|
'Posting illegal content',
|
||||||
|
'Repeated violations after warnings',
|
||||||
|
'Ban evasion (alt account)',
|
||||||
|
];
|
||||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||||
const [bulkLoading, setBulkLoading] = useState(false);
|
const [bulkLoading, setBulkLoading] = useState(false);
|
||||||
|
|
||||||
|
|
@ -198,9 +208,36 @@ export default function ModerationPage() {
|
||||||
{reviewingId === item.id && (
|
{reviewingId === item.id && (
|
||||||
<div className="mt-4 p-4 bg-red-50 border border-red-200 rounded-lg">
|
<div className="mt-4 p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||||
<p className="text-sm font-medium text-red-800 mb-2">Ban user and remove content</p>
|
<p className="text-sm font-medium text-red-800 mb-2">Ban user and remove content</p>
|
||||||
<input className="input mb-2" placeholder="Reason for ban..." value={reason} onChange={(e) => setReason(e.target.value)} />
|
<div className="space-y-1.5 mb-3">
|
||||||
|
{banReasons.map((preset) => (
|
||||||
|
<button
|
||||||
|
key={preset}
|
||||||
|
onClick={() => { setReason(preset); setCustomReason(false); }}
|
||||||
|
className={`w-full text-left px-3 py-1.5 rounded text-xs border transition-colors ${
|
||||||
|
reason === preset && !customReason
|
||||||
|
? 'border-red-400 bg-red-100 text-red-800 font-medium'
|
||||||
|
: 'border-red-200 hover:border-red-300 text-red-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{preset}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
onClick={() => { setCustomReason(true); setReason(''); }}
|
||||||
|
className={`w-full text-left px-3 py-1.5 rounded text-xs border transition-colors ${
|
||||||
|
customReason
|
||||||
|
? 'border-red-400 bg-red-100 text-red-800 font-medium'
|
||||||
|
: 'border-red-200 hover:border-red-300 text-red-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Custom reason...
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{customReason && (
|
||||||
|
<input className="input mb-2 text-sm" placeholder="Enter custom reason..." value={reason} onChange={(e) => setReason(e.target.value)} autoFocus />
|
||||||
|
)}
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button onClick={() => { setReviewingId(null); setReason(''); }} className="btn-secondary text-xs">Cancel</button>
|
<button onClick={() => { setReviewingId(null); setReason(''); setCustomReason(false); }} className="btn-secondary text-xs">Cancel</button>
|
||||||
<button onClick={() => handleReview(item.id, 'ban_user')} className="btn-danger text-xs" disabled={!reason.trim()}>Confirm Ban</button>
|
<button onClick={() => handleReview(item.id, 'ban_user')} className="btn-danger text-xs" disabled={!reason.trim()}>Confirm Ban</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,32 @@ export default function UserDetailPage() {
|
||||||
const [actionLoading, setActionLoading] = useState(false);
|
const [actionLoading, setActionLoading] = useState(false);
|
||||||
const [showModal, setShowModal] = useState<string | null>(null);
|
const [showModal, setShowModal] = useState<string | null>(null);
|
||||||
const [reason, setReason] = useState('');
|
const [reason, setReason] = useState('');
|
||||||
|
const [customReason, setCustomReason] = useState(false);
|
||||||
|
|
||||||
|
const reasonPresets: Record<string, string[]> = {
|
||||||
|
banned: [
|
||||||
|
'Hate speech or slurs',
|
||||||
|
'Harassment or bullying',
|
||||||
|
'Spam or scam activity',
|
||||||
|
'Posting illegal content',
|
||||||
|
'Impersonation',
|
||||||
|
'Repeated violations after warnings',
|
||||||
|
'Ban evasion (alt account)',
|
||||||
|
],
|
||||||
|
suspended: [
|
||||||
|
'Posting inappropriate content',
|
||||||
|
'Minor harassment',
|
||||||
|
'Spam behavior',
|
||||||
|
'Violating community guidelines',
|
||||||
|
'Cooling-off period after heated exchange',
|
||||||
|
],
|
||||||
|
active: [
|
||||||
|
'Appeal reviewed and approved',
|
||||||
|
'Ban was issued in error',
|
||||||
|
'Suspension period served',
|
||||||
|
'User agreed to follow guidelines',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const fetchUser = () => {
|
const fetchUser = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -236,21 +262,52 @@ export default function UserDetailPage() {
|
||||||
|
|
||||||
{/* Status Change Modal */}
|
{/* Status Change Modal */}
|
||||||
{showModal && (
|
{showModal && (
|
||||||
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50" onClick={() => setShowModal(null)}>
|
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50" onClick={() => { setShowModal(null); setReason(''); setCustomReason(false); }}>
|
||||||
<div className="card p-6 w-full max-w-md mx-4" onClick={(e) => e.stopPropagation()}>
|
<div className="card p-6 w-full max-w-md mx-4" onClick={(e) => e.stopPropagation()}>
|
||||||
<h3 className="text-lg font-semibold text-gray-900 mb-1">
|
<h3 className="text-lg font-semibold text-gray-900 mb-1">
|
||||||
{showModal === 'active' ? 'Activate' : showModal === 'suspended' ? 'Suspend' : 'Ban'} User
|
{showModal === 'active' ? 'Activate' : showModal === 'suspended' ? 'Suspend' : 'Ban'} User
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-gray-500 mb-4">Please provide a reason for this action.</p>
|
<p className="text-sm text-gray-500 mb-4">Select a reason for this action.</p>
|
||||||
|
|
||||||
|
<div className="space-y-2 mb-4">
|
||||||
|
{(reasonPresets[showModal] || []).map((preset) => (
|
||||||
|
<button
|
||||||
|
key={preset}
|
||||||
|
onClick={() => { setReason(preset); setCustomReason(false); }}
|
||||||
|
className={`w-full text-left px-3 py-2 rounded-lg text-sm border transition-colors ${
|
||||||
|
reason === preset && !customReason
|
||||||
|
? 'border-brand-500 bg-brand-50 text-brand-700 font-medium'
|
||||||
|
: 'border-warm-300 hover:border-gray-400 text-gray-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{preset}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
onClick={() => { setCustomReason(true); setReason(''); }}
|
||||||
|
className={`w-full text-left px-3 py-2 rounded-lg text-sm border transition-colors ${
|
||||||
|
customReason
|
||||||
|
? 'border-brand-500 bg-brand-50 text-brand-700 font-medium'
|
||||||
|
: 'border-warm-300 hover:border-gray-400 text-gray-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Custom reason...
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{customReason && (
|
||||||
<textarea
|
<textarea
|
||||||
className="input mb-4"
|
className="input mb-4"
|
||||||
rows={3}
|
rows={3}
|
||||||
placeholder="Reason..."
|
placeholder="Enter custom reason..."
|
||||||
value={reason}
|
value={reason}
|
||||||
onChange={(e) => setReason(e.target.value)}
|
onChange={(e) => setReason(e.target.value)}
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex gap-2 justify-end">
|
<div className="flex gap-2 justify-end">
|
||||||
<button onClick={() => setShowModal(null)} className="btn-secondary text-sm">Cancel</button>
|
<button onClick={() => { setShowModal(null); setReason(''); setCustomReason(false); }} className="btn-secondary text-sm">Cancel</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleStatusChange(showModal)}
|
onClick={() => handleStatusChange(showModal)}
|
||||||
className={showModal === 'banned' ? 'btn-danger text-sm' : 'btn-primary text-sm'}
|
className={showModal === 'banned' ? 'btn-danger text-sm' : 'btn-primary text-sm'}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue