veza/apps/web/src/components/admin/AdminModerationView.tsx

117 lines
5.7 KiB
TypeScript
Raw Normal View History

import React, { useState, useEffect } from 'react';
import { Card } from '../ui/card';
import { Button } from '../ui/button';
import { Badge } from '../ui/badge';
import { Report } from '../../types';
import { ShieldAlert, CheckCircle, Ban, MessageSquare, Clock, Loader2 } from 'lucide-react';
import { useToast } from '../../context/ToastContext';
import { adminService } from '../../services/adminService';
import { logger } from '@/utils/logger';
export const AdminModerationView: React.FC = () => {
const { addToast } = useToast();
const [queue, setQueue] = useState<Report[]>([]);
const [activeTab, setActiveTab] = useState<'pending' | 'reviewed' | 'resolved'>('pending');
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadQueue = async () => {
setLoading(true);
try {
const data = await adminService.getModerationQueue('all');
setQueue(data);
} catch (e) {
logger.error('Error loading moderation queue', {
error: e instanceof Error ? e.message : String(e),
stack: e instanceof Error ? e.stack : undefined,
});
} finally {
setLoading(false);
}
};
loadQueue();
}, []);
const filteredQueue = queue.filter(r =>
activeTab === 'pending' ? r.status === 'pending' :
activeTab === 'reviewed' ? r.status === 'reviewed' :
r.status === 'resolved' || r.status === 'dismissed'
);
const handleAction = async (id: string, action: string) => {
try {
await adminService.resolveReport(id, action);
addToast(`Report ${action}`, 'success');
setQueue(queue.map(r => r.id === id ? { ...r, status: action === 'dismissed' ? 'dismissed' : 'resolved' } as any : r));
} catch (e) {
addToast("Action failed", "error");
}
};
return (
<div className="space-y-6 animate-fadeIn pb-20">
<h2 className="text-3xl font-display font-bold text-white mb-6">MODERATION QUEUE</h2>
<div className="border-b border-kodo-steel flex gap-6 mb-6">
{['pending', 'reviewed', 'resolved'].map(tab => (
<button
key={tab}
onClick={() => setActiveTab(tab as any)}
className={`pb-3 text-sm font-bold uppercase tracking-wider border-b-2 transition-colors ${activeTab === tab ? 'border-kodo-red text-white' : 'border-transparent text-gray-500 hover:text-gray-300'}`}
>
{tab} ({queue.filter(r => tab === 'pending' ? r.status === 'pending' : tab === 'reviewed' ? r.status === 'reviewed' : (r.status === 'resolved' || r.status === 'dismissed')).length})
</button>
))}
</div>
<div className="space-y-4">
{loading && <div className="flex justify-center py-20"><Loader2 className="w-8 h-8 text-kodo-cyan animate-spin" /></div>}
{!loading && filteredQueue.length === 0 && (
<div className="text-center py-20 text-gray-500">
<ShieldAlert className="w-12 h-12 mx-auto mb-4 opacity-30" />
<p>All caught up! No reports in this queue.</p>
</div>
)}
{!loading && filteredQueue.map(report => (
<Card key={report.id} variant="default" className="border-l-4 border-l-kodo-red">
<div className="flex flex-col md:flex-row justify-between gap-4">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<Badge label={report.targetType} variant="terminal" />
<span className="font-bold text-white text-lg">{report.targetName}</span>
<span className="text-xs text-gray-500 font-mono flex items-center gap-1">
<Clock className="w-3 h-3" /> {report.timestamp}
</span>
</div>
<div className="bg-kodo-ink p-3 rounded border border-kodo-steel/50 mb-3">
<div className="text-xs font-bold text-kodo-red uppercase mb-1">Reason: {report.reason}</div>
<p className="text-sm text-gray-300">{report.description}</p>
</div>
<div className="text-xs text-gray-500">Reported by: <span className="text-white">{report.reportedBy}</span></div>
</div>
<div className="flex flex-col gap-2 justify-center min-w-[140px]">
<Button variant="primary" size="sm" className="bg-red-600 hover:bg-red-700 border-red-500 text-white" icon={<Ban className="w-4 h-4" />} onClick={() => handleAction(report.id, 'banned')}>
Ban User
</Button>
<Button variant="secondary" size="sm" icon={<CheckCircle className="w-4 h-4" />} onClick={() => handleAction(report.id, 'resolved')}>
Resolve
</Button>
<Button variant="ghost" size="sm" icon={<MessageSquare className="w-4 h-4" />} onClick={() => addToast("Warning sent")}>
Send Warning
</Button>
<Button variant="ghost" size="sm" className="text-gray-500 hover:text-white" onClick={() => handleAction(report.id, 'dismissed')}>
Dismiss
</Button>
</div>
</div>
</Card>
))}
</div>
</div>
);
};