2026-01-07 09:31:02 +00:00
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import { Card } from '../ui/card';
|
|
|
|
|
import { Button } from '../ui/button';
|
|
|
|
|
import { Badge } from '../ui/badge';
|
2026-02-08 23:00:21 +00:00
|
|
|
import { EmptyState } from '../ui/empty-state';
|
2026-01-07 09:31:02 +00:00
|
|
|
import { Report } from '../../types';
|
2026-01-13 18:47:57 +00:00
|
|
|
import {
|
|
|
|
|
ShieldAlert,
|
|
|
|
|
CheckCircle,
|
|
|
|
|
Ban,
|
|
|
|
|
MessageSquare,
|
|
|
|
|
Clock,
|
|
|
|
|
Loader2,
|
|
|
|
|
} from 'lucide-react';
|
2026-01-26 13:12:17 +00:00
|
|
|
import { useToast } from '../../components/feedback/ToastProvider';
|
2026-01-07 09:31:02 +00:00
|
|
|
import { adminService } from '../../services/adminService';
|
|
|
|
|
import { logger } from '@/utils/logger';
|
|
|
|
|
|
|
|
|
|
export const AdminModerationView: React.FC = () => {
|
|
|
|
|
const { addToast } = useToast();
|
|
|
|
|
const [queue, setQueue] = useState<Report[]>([]);
|
2026-01-13 18:47:57 +00:00
|
|
|
const [activeTab, setActiveTab] = useState<
|
|
|
|
|
'pending' | 'reviewed' | 'resolved'
|
|
|
|
|
>('pending');
|
2026-01-07 09:31:02 +00:00
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-01-13 18:47:57 +00:00
|
|
|
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();
|
2026-01-07 09:31:02 +00:00
|
|
|
}, []);
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
const filteredQueue = queue.filter((r) =>
|
|
|
|
|
activeTab === 'pending'
|
|
|
|
|
? r.status === 'pending'
|
|
|
|
|
: activeTab === 'reviewed'
|
|
|
|
|
? r.status === 'reviewed'
|
|
|
|
|
: r.status === 'resolved' || r.status === 'dismissed',
|
2026-01-07 09:31:02 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleAction = async (id: string, action: string) => {
|
2026-01-13 18:47:57 +00:00
|
|
|
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');
|
|
|
|
|
}
|
2026-01-07 09:31:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6 animate-fadeIn pb-20">
|
feat(ui): education, gamification, developer, admin views polish
Education:
- CourseCard: lessons count badge, progress bar, backdrop-blur on badges
- EducationView: framer-motion stagger on grid
- Filters: interactive color-coded pills (Beginner/Intermediate/Advanced)
- MyCoursesView: stagger animation, semantic token migration
Gamification:
- LeaderboardView: gold/silver/bronze podium styling with glow + accents
- AchievementCard: shine sweep animation on hover, lift effect
- AchievementsView: stagger animation with filter re-animation
- XPBar: semantic token fix
Developer dashboard:
- API key copy-to-clipboard with icon toggle
- Status indicator badges with animated pulse dot
Commerce/Admin:
- WishlistView: stagger animation, hover lift
- PurchasesView: stagger on list items
- Admin views: consistent headers, semantic tokens (text-white → text-foreground)
18 files modified, all text-white → text-foreground migrations
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 23:48:45 +00:00
|
|
|
<div className="flex items-center gap-3 border-b border-border/50 pb-6">
|
|
|
|
|
<ShieldAlert className="w-6 h-6 text-destructive" />
|
|
|
|
|
<div>
|
|
|
|
|
<h2 className="text-2xl font-display font-bold text-foreground tracking-tight">
|
|
|
|
|
MODERATION QUEUE
|
|
|
|
|
</h2>
|
|
|
|
|
<p className="text-muted-foreground font-mono text-sm">
|
|
|
|
|
Review and act on flagged content.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-02-07 18:36:27 +00:00
|
|
|
<div className="border-b border-border flex gap-6 mb-6">
|
2026-01-13 18:47:57 +00:00
|
|
|
{['pending', 'reviewed', 'resolved'].map((tab) => (
|
|
|
|
|
<button
|
|
|
|
|
key={tab}
|
|
|
|
|
onClick={() => setActiveTab(tab as any)}
|
2026-02-07 18:36:27 +00:00
|
|
|
className={`pb-3 text-sm font-bold uppercase tracking-wider border-b-2 transition-colors ${activeTab === tab ? 'border-destructive text-foreground' : 'border-transparent text-muted-foreground hover:text-foreground'}`}
|
2026-01-13 18:47:57 +00:00
|
|
|
>
|
|
|
|
|
{tab} (
|
|
|
|
|
{
|
|
|
|
|
queue.filter((r) =>
|
|
|
|
|
tab === 'pending'
|
|
|
|
|
? r.status === 'pending'
|
|
|
|
|
: tab === 'reviewed'
|
|
|
|
|
? r.status === 'reviewed'
|
|
|
|
|
: r.status === 'resolved' || r.status === 'dismissed',
|
|
|
|
|
).length
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
<div className="space-y-4">
|
|
|
|
|
{loading && (
|
aesthetic-improvements: align spacing to 8px grid (Action 11.2.1.3)
- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid
- Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px)
- Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation
- Modified files across all components to ensure consistent 8px grid alignment
- Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
2026-01-16 10:50:46 +00:00
|
|
|
<div className="flex justify-center py-24">
|
2026-02-07 18:36:27 +00:00
|
|
|
<Loader2 className="w-8 h-8 text-muted-foreground animate-spin" />
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
|
|
|
|
)}
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{!loading && filteredQueue.length === 0 && (
|
2026-02-08 23:00:21 +00:00
|
|
|
<EmptyState
|
|
|
|
|
icon={<ShieldAlert className="w-full h-full" />}
|
|
|
|
|
title="All caught up!"
|
|
|
|
|
description="No reports in this queue."
|
|
|
|
|
/>
|
2026-01-13 18:47:57 +00:00
|
|
|
)}
|
2026-01-07 09:31:02 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
{!loading &&
|
|
|
|
|
filteredQueue.map((report) => (
|
|
|
|
|
<Card
|
|
|
|
|
key={report.id}
|
|
|
|
|
variant="default"
|
2026-02-07 18:36:27 +00:00
|
|
|
className="border-l-4 border-l-destructive"
|
2026-01-13 18:47:57 +00:00
|
|
|
>
|
|
|
|
|
<div className="flex flex-col md:flex-row justify-between gap-4">
|
|
|
|
|
<div className="flex-1">
|
aesthetic-improvements: align spacing to 8px grid (Action 11.2.1.3)
- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid
- Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px)
- Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation
- Modified files across all components to ensure consistent 8px grid alignment
- Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
2026-01-16 10:50:46 +00:00
|
|
|
<div className="flex items-center gap-4 mb-2">
|
2026-01-13 18:47:57 +00:00
|
|
|
<Badge label={report.targetType} variant="terminal" />
|
feat(ui): education, gamification, developer, admin views polish
Education:
- CourseCard: lessons count badge, progress bar, backdrop-blur on badges
- EducationView: framer-motion stagger on grid
- Filters: interactive color-coded pills (Beginner/Intermediate/Advanced)
- MyCoursesView: stagger animation, semantic token migration
Gamification:
- LeaderboardView: gold/silver/bronze podium styling with glow + accents
- AchievementCard: shine sweep animation on hover, lift effect
- AchievementsView: stagger animation with filter re-animation
- XPBar: semantic token fix
Developer dashboard:
- API key copy-to-clipboard with icon toggle
- Status indicator badges with animated pulse dot
Commerce/Admin:
- WishlistView: stagger animation, hover lift
- PurchasesView: stagger on list items
- Admin views: consistent headers, semantic tokens (text-white → text-foreground)
18 files modified, all text-white → text-foreground migrations
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 23:48:45 +00:00
|
|
|
<span className="font-bold text-foreground text-lg">
|
2026-01-13 18:47:57 +00:00
|
|
|
{report.targetName}
|
|
|
|
|
</span>
|
2026-02-07 18:36:27 +00:00
|
|
|
<span className="text-xs text-muted-foreground font-mono flex items-center gap-1">
|
2026-01-13 18:47:57 +00:00
|
|
|
<Clock className="w-3 h-3" /> {report.timestamp}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
2026-02-07 18:36:27 +00:00
|
|
|
<div className="bg-muted/50 p-4 rounded border border-border mb-3">
|
|
|
|
|
<div className="text-xs font-bold text-destructive uppercase mb-1">
|
2026-01-13 18:47:57 +00:00
|
|
|
Reason: {report.reason}
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-02-07 18:36:27 +00:00
|
|
|
<p className="text-sm text-foreground">
|
2026-01-13 18:47:57 +00:00
|
|
|
{report.description}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
2026-02-07 18:36:27 +00:00
|
|
|
<div className="text-xs text-muted-foreground">
|
2026-01-13 18:47:57 +00:00
|
|
|
Reported by:{' '}
|
feat(ui): education, gamification, developer, admin views polish
Education:
- CourseCard: lessons count badge, progress bar, backdrop-blur on badges
- EducationView: framer-motion stagger on grid
- Filters: interactive color-coded pills (Beginner/Intermediate/Advanced)
- MyCoursesView: stagger animation, semantic token migration
Gamification:
- LeaderboardView: gold/silver/bronze podium styling with glow + accents
- AchievementCard: shine sweep animation on hover, lift effect
- AchievementsView: stagger animation with filter re-animation
- XPBar: semantic token fix
Developer dashboard:
- API key copy-to-clipboard with icon toggle
- Status indicator badges with animated pulse dot
Commerce/Admin:
- WishlistView: stagger animation, hover lift
- PurchasesView: stagger on list items
- Admin views: consistent headers, semantic tokens (text-white → text-foreground)
18 files modified, all text-white → text-foreground migrations
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 23:48:45 +00:00
|
|
|
<span className="text-foreground">{report.reportedBy}</span>
|
2026-01-13 18:47:57 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-02-08 21:48:37 +00:00
|
|
|
<div className="flex flex-col gap-2 justify-center min-w-36">
|
2026-01-13 18:47:57 +00:00
|
|
|
<Button
|
|
|
|
|
variant="primary"
|
|
|
|
|
size="sm"
|
2026-02-07 18:36:27 +00:00
|
|
|
className="bg-destructive hover:bg-destructive/90 border-destructive text-destructive-foreground"
|
2026-01-13 18:47:57 +00:00
|
|
|
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"
|
2026-02-07 18:36:27 +00:00
|
|
|
className="text-muted-foreground hover:text-foreground"
|
2026-01-13 18:47:57 +00:00
|
|
|
onClick={() => handleAction(report.id, 'dismissed')}
|
|
|
|
|
>
|
|
|
|
|
Dismiss
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|