Zaptam Admin Dashboard
Complete documentation of admin features, pages, and capabilities.
Overview
The admin dashboard provides tools for platform management, user moderation, verification review, and analytics.
Tech Stack:
- React 18 + Vite
- TanStack Query (data fetching)
- Tailwind CSS
- Lucide Icons
Access Levels:
| Role | Level | Capabilities |
|---|---|---|
| USER | 1 | No admin access |
| CURATOR | 2 | View users, review verifications/reports |
| ADMIN | 3 | Everything above + suspend/ban users, process payouts |
| SUPER_ADMIN | 4 | Full access |
Dashboard Structure
/admin
├── Dashboard.tsx # Main overview with stats
├── Users.tsx # User management
├── Verifications.tsx # Review verification requests
├── Reports.tsx # Handle user reports
├── Transactions.tsx # View all transactions
└── (Other pages)Dashboard Page
File: admin/src/pages/Dashboard.tsx
Stats Overview
typescript
interface DashboardStats {
users: {
total: number;
male: number;
female: number;
active: number;
pending: number;
};
pendingVerifications: number;
pendingReports: number;
totalMatches: number;
}Stat Cards
tsx
const stats = [
{
label: 'Total Users',
value: data?.users.total || 0,
icon: Users,
color: 'bg-blue-500/10 text-blue-400',
change: '+12%',
up: true,
},
{
label: 'Active Users',
value: data?.users.active || 0,
icon: TrendingUp,
color: 'bg-green-500/10 text-green-400',
change: '+8%',
up: true,
},
{
label: 'Pending Verifications',
value: data?.pendingVerifications || 0,
icon: Shield,
color: 'bg-yellow-500/10 text-yellow-400',
change: '-5%',
up: false,
},
{
label: 'Pending Reports',
value: data?.pendingReports || 0,
icon: AlertTriangle,
color: 'bg-red-500/10 text-red-400',
change: '+2',
up: true,
},
{
label: 'Total Matches',
value: data?.totalMatches || 0,
icon: Heart,
color: 'bg-pink-500/10 text-pink-400',
change: '+15%',
up: true,
},
];User Breakdown
Visual breakdown showing:
- Male users count + percentage bar
- Female users count + percentage bar
- Pending approval count + percentage bar
Quick Actions
Links to common tasks:
- Review Verifications (with pending count)
- Handle Reports (with pending count)
API Integration
File: admin/src/services/api.ts
typescript
import axios from 'axios';
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000';
export const api = axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json',
},
});
// Add auth token to requests
api.interceptors.request.use((config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});Users Page
File: admin/src/pages/Users.tsx
Features
- Search - Filter by alias, phone, or email
- Pagination - 20 users per page
- Status Indicators - Color-coded badges
- Quick Actions - Dropdown menu per user
User Table Columns
| Column | Description |
|---|---|
| User | Alias + phone number |
| Gender | MALE (blue) / FEMALE (pink) |
| Status | ACTIVE (green), PENDING (yellow), SUSPENDED (orange), BANNED (red) |
| Trust | Trust score number |
| Verification | Level with shield icon |
| Joined | Registration date |
| Actions | Dropdown menu |
Query
typescript
const { data, isLoading } = useQuery({
queryKey: ['admin-users', page, search],
queryFn: async () => {
const params = new URLSearchParams({
page: String(page),
limit: '20'
});
if (search) params.append('search', search);
const response = await api.get(`/api/admin/users?${params}`);
return response.data;
},
});User Actions (ADMIN+ only)
- View Profile
- Suspend User
- Ban User
- Recalculate Trust Score
Verifications Page
File: admin/src/pages/Verifications.tsx
Verification Card
tsx
<div className="card">
<div className="flex items-start justify-between">
{/* User info */}
<div className="flex items-start gap-4">
{/* Type icon with color */}
<div className={`w-12 h-12 rounded-xl ${typeColor}`}>
<Shield className={`w-6 h-6 ${iconColor}`} />
</div>
<div>
{/* User name + gender badge */}
<h3>{user.alias || 'Anonymous'}</h3>
<span className="badge">{user.gender}</span>
{/* Verification type + current level */}
<p>{type} Verification - Current Level: {user.verificationLevel}</p>
{/* Submission time */}
<p>Submitted {formatDate(createdAt)}</p>
{/* Document count */}
<p>{documents.length} document(s) attached</p>
</div>
</div>
{/* Action buttons */}
<div className="flex gap-2">
<button onClick={() => handleReview(id, false)}>
<X /> {/* Reject */}
</button>
<button onClick={() => handleReview(id, true)}>
<Check /> {/* Approve */}
</button>
</div>
</div>
</div>Review Action
typescript
const reviewMutation = useMutation({
mutationFn: async ({ id, approved, notes }) => {
return api.patch(`/api/admin/verifications/${id}`, { approved, notes });
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin-verifications'] });
queryClient.invalidateQueries({ queryKey: ['dashboard'] });
},
});Type Colors
| Type | Background | Icon Color |
|---|---|---|
| IDENTITY | bg-blue-500/10 | text-blue-400 |
| WORTH | bg-green-500/10 | text-green-400 |
| VALUE | bg-purple-500/10 | text-purple-400 |
Reports Page
File: admin/src/pages/Reports.tsx
Report Card
tsx
<div className="card">
{/* Type + Status badges */}
<span className={getTypeColor(type)}>{type.replace('_', ' ')}</span>
<span className={getStatusColor(status)}>{status}</span>
{/* Reporter and Reported info */}
<div className="grid grid-cols-2">
<div>
<p>Reporter</p>
<p>{reporter.alias}</p>
<p>{reporter.phoneNumber}</p>
</div>
<div>
<p>Reported User</p>
<p>{reported.alias}</p>
<p>Trust: {reported.trustScore}</p>
</div>
</div>
{/* Description */}
<p>{description}</p>
{/* Timestamp */}
<p>Reported {formatDate(createdAt)}</p>
{/* Actions for PENDING reports */}
{status === 'PENDING' && (
<div>
<button onClick={() => handleReport(id, 'dismiss')}>
<X /> {/* Dismiss */}
</button>
<button onClick={() => handleReport(id, 'resolve')}>
<AlertTriangle /> {/* Resolve + Apply Penalty */}
</button>
</div>
)}
</div>Handle Report
typescript
const handleMutation = useMutation({
mutationFn: async ({ id, status, applyPenalty }) => {
return api.patch(`/api/admin/reports/${id}`, {
status,
applyPenalty,
penaltyAmount: applyPenalty ? 10 : 0
});
},
});
const handleReport = (id: string, action: 'resolve' | 'dismiss') => {
const status = action === 'resolve' ? 'RESOLVED' : 'DISMISSED';
const applyPenalty = action === 'resolve'; // Only apply penalty on resolve
if (confirm(`Are you sure you want to ${action} this report?`)) {
handleMutation.mutate({ id, status, applyPenalty });
}
};Type Colors
| Type | Color |
|---|---|
| BLACKMAIL | bg-red-500/10 text-red-400 |
| HARASSMENT | bg-orange-500/10 text-orange-400 |
| FAKE_PROFILE | bg-yellow-500/10 text-yellow-400 |
| INAPPROPRIATE_CONTENT | bg-purple-500/10 text-purple-400 |
| OTHER | bg-gray-500/10 text-gray-400 |
Transactions Page
File: admin/src/pages/Transactions.tsx
Transaction Table
| Column | Description |
|---|---|
| Type | Icon + label (CREDIT_PURCHASE, EARNING, WITHDRAWAL, etc.) |
| User | Alias + phone |
| Amount | +/- with color |
| Status | COMPLETED (green), PENDING (yellow), FAILED (red) |
| Date | Transaction timestamp |
Type Icons & Colors
typescript
const getTypeIcon = (type: string) => {
if (['EARNING', 'CREDIT_PURCHASE'].includes(type)) {
return <ArrowDownRight className="text-green-400" />; // Money IN
}
return <ArrowUpRight className="text-red-400" />; // Money OUT
};Amount Display
tsx
<span className={
['EARNING', 'CREDIT_PURCHASE'].includes(tx.type)
? 'text-green-400'
: 'text-red-400'
}>
{['EARNING', 'CREDIT_PURCHASE'].includes(tx.type) ? '+' : '-'}
${tx.amount} {tx.currency}
</span>Admin API Endpoints
Dashboard
GET /api/admin/dashboardReturns user counts, pending items.
Users
GET /api/admin/users
GET /api/admin/users/:id
PATCH /api/admin/users/:id # ADMIN+
POST /api/admin/users/:id/suspend # ADMIN+
POST /api/admin/users/:id/ban # ADMIN+
POST /api/admin/users/:id/recalculate-trustVerifications
GET /api/admin/verifications
PATCH /api/admin/verifications/:idReports
GET /api/admin/reports
GET /api/admin/reports/:id
PATCH /api/admin/reports/:idTransactions & Payouts
GET /api/admin/transactions
GET /api/admin/payouts
PATCH /api/admin/payouts/:id # ADMIN+Analytics
GET /api/admin/analytics?days=30Analytics Endpoint
File: api/src/controllers/admin/analytics.controller.ts
Available Data
typescript
// User growth over time
const userGrowth = await prisma.$queryRaw`
SELECT DATE(created_at) as date, COUNT(*) as count
FROM users
WHERE created_at >= ${startDate}
GROUP BY DATE(created_at)
ORDER BY date ASC
`;
// Matches over time
const matchGrowth = await prisma.$queryRaw`
SELECT DATE(created_at) as date, COUNT(*) as count
FROM interests
WHERE status = 'ACCEPTED' AND created_at >= ${startDate}
GROUP BY DATE(created_at)
ORDER BY date ASC
`;
// Verification stats by status
const verificationStats = await prisma.verification.groupBy({
by: ['status'],
_count: true,
});
// Report stats by type
const reportStats = await prisma.report.groupBy({
by: ['type'],
_count: true,
});Response
json
{
"success": true,
"data": {
"userGrowth": [
{ "date": "2024-01-01", "count": 50 },
{ "date": "2024-01-02", "count": 45 },
...
],
"matchGrowth": [
{ "date": "2024-01-01", "count": 25 },
...
],
"verificationStats": [
{ "status": "PENDING", "count": 15 },
{ "status": "APPROVED", "count": 200 },
{ "status": "REJECTED", "count": 30 }
],
"reportStats": [
{ "type": "HARASSMENT", "count": 10 },
{ "type": "FAKE_PROFILE", "count": 5 },
...
]
}
}Component Library
StatCard
tsx
<div className="card">
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl ${stat.color}`}>
<stat.icon className="w-6 h-6" />
</div>
<div>
<p className="text-sm text-gray-400">{stat.label}</p>
<p className="text-2xl font-bold">{stat.value}</p>
</div>
</div>
<div className="mt-4 flex items-center gap-1 text-sm">
{stat.up ? <TrendingUp /> : <TrendingDown />}
<span>{stat.change}</span>
<span className="text-gray-500">vs last month</span>
</div>
</div>Badge
tsx
<span className={cn(
"px-2 py-1 rounded text-sm",
status === 'ACTIVE' && 'bg-green-500/10 text-green-400',
status === 'PENDING' && 'bg-yellow-500/10 text-yellow-400',
status === 'SUSPENDED' && 'bg-orange-500/10 text-orange-400',
status === 'BANNED' && 'bg-red-500/10 text-red-400',
)}>
{status}
</span>DataTable
Loading state with skeleton:
tsx
{isLoading ? (
[...Array(5)].map((_, i) => (
<tr key={i}>
<td colSpan={7} className="p-4">
<div className="h-8 bg-dark-700 rounded animate-pulse" />
</td>
</tr>
))
) : /* data rows */}Admin Actions Summary
| Action | Min Role | Description |
|---|---|---|
| View Dashboard | CURATOR | See platform stats |
| View Users | CURATOR | Browse user list |
| View User Detail | CURATOR | See user profile + history |
| Update User | ADMIN | Change status, trust, verification |
| Suspend User | ADMIN | Set status to SUSPENDED |
| Ban User | ADMIN | Set status to BANNED, revoke tokens |
| Recalculate Trust | CURATOR | Refresh trust score |
| View Verifications | CURATOR | See pending verifications |
| Review Verification | CURATOR | Approve or reject |
| View Reports | CURATOR | See all reports |
| Handle Report | CURATOR | Resolve or dismiss |
| View Transactions | CURATOR | See all transactions |
| View Payouts | CURATOR | See withdrawal requests |
| Process Payout | ADMIN | Approve or reject withdrawals |
| View Analytics | CURATOR | See growth charts |
Security Considerations
- Role Validation - All admin routes check
requireMinRole('CURATOR') - Action Logging - Important actions include
adminIdin reference - Token Revocation - Banning a user revokes all their tokens
- Confirmation Prompts - Destructive actions require confirmation
Future Enhancements
- Activity Logs - Track all admin actions
- Bulk Actions - Select multiple users/reports
- Export Data - CSV/Excel exports
- Real-time Updates - WebSocket for live stats
- Email Templates - Send notifications to users
- Advanced Filters - Date ranges, multiple filters