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 [reviewingId, setReviewingId] = useState<string | null>(null);
|
||||
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 [bulkLoading, setBulkLoading] = useState(false);
|
||||
|
||||
|
|
@ -198,9 +208,36 @@ export default function ModerationPage() {
|
|||
{reviewingId === item.id && (
|
||||
<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>
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,32 @@ export default function UserDetailPage() {
|
|||
const [actionLoading, setActionLoading] = useState(false);
|
||||
const [showModal, setShowModal] = useState<string | null>(null);
|
||||
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 = () => {
|
||||
setLoading(true);
|
||||
|
|
@ -236,21 +262,52 @@ export default function UserDetailPage() {
|
|||
|
||||
{/* Status Change Modal */}
|
||||
{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()}>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-1">
|
||||
{showModal === 'active' ? 'Activate' : showModal === 'suspended' ? 'Suspend' : 'Ban'} User
|
||||
</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
|
||||
className="input mb-4"
|
||||
rows={3}
|
||||
placeholder="Reason..."
|
||||
placeholder="Enter custom reason..."
|
||||
value={reason}
|
||||
onChange={(e) => setReason(e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
)}
|
||||
|
||||
<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
|
||||
onClick={() => handleStatusChange(showModal)}
|
||||
className={showModal === 'banned' ? 'btn-danger text-sm' : 'btn-primary text-sm'}
|
||||
|
|
|
|||
Loading…
Reference in a new issue