Add admin dashboard pages and restructure admin route

Introduces new admin dashboard pages for alerts, bans, invites, orders, settings, status, and vendors under app/dashboard/admin/. Moves the main admin page to the new dashboard structure and adds a shared admin layout. Updates sidebar configuration and adds supporting components and hooks for admin features.
This commit is contained in:
NotII
2025-10-18 15:19:10 +01:00
parent 03a2e37502
commit bfc60012cf
16 changed files with 2074 additions and 13 deletions

View File

@@ -0,0 +1,313 @@
import React from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { AlertTriangle, CheckCircle, XCircle, Clock, Bell, Shield } from "lucide-react";
export default function AdminAlertsPage() {
// Mock data for system alerts
const alerts = [
{
id: "1",
type: "security",
severity: "high",
title: "Suspicious Login Attempts",
description: "Multiple failed login attempts detected from IP 192.168.1.100",
timestamp: "2024-01-20 14:30:00",
status: "active",
affectedUsers: 3
},
{
id: "2",
type: "system",
severity: "medium",
title: "High Memory Usage",
description: "Server memory usage has exceeded 85% for the past hour",
timestamp: "2024-01-20 13:45:00",
status: "resolved",
affectedUsers: 0
},
{
id: "3",
type: "payment",
severity: "high",
title: "Payment Processing Error",
description: "Bitcoin payment gateway experiencing delays",
timestamp: "2024-01-20 12:15:00",
status: "active",
affectedUsers: 12
},
{
id: "4",
type: "user",
severity: "low",
title: "New Vendor Registration",
description: "New vendor 'tech_supplies' has registered and requires approval",
timestamp: "2024-01-20 11:20:00",
status: "pending",
affectedUsers: 1
},
{
id: "5",
type: "security",
severity: "critical",
title: "Potential Security Breach",
description: "Unusual API access patterns detected from multiple IPs",
timestamp: "2024-01-20 10:30:00",
status: "investigating",
affectedUsers: 0
}
];
const getSeverityColor = (severity: string) => {
switch (severity) {
case "critical": return "destructive";
case "high": return "destructive";
case "medium": return "secondary";
case "low": return "outline";
default: return "outline";
}
};
const getStatusColor = (status: string) => {
switch (status) {
case "active": return "destructive";
case "resolved": return "default";
case "pending": return "secondary";
case "investigating": return "outline";
default: return "outline";
}
};
const getTypeIcon = (type: string) => {
switch (type) {
case "security": return <Shield className="h-4 w-4" />;
case "system": return <AlertTriangle className="h-4 w-4" />;
case "payment": return <Bell className="h-4 w-4" />;
case "user": return <CheckCircle className="h-4 w-4" />;
default: return <AlertTriangle className="h-4 w-4" />;
}
};
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold tracking-tight">System Alerts</h1>
<p className="text-sm text-muted-foreground mt-1">Monitor system alerts and security notifications</p>
</div>
{/* Alert Summary */}
<div className="grid gap-4 md:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Active Alerts</CardTitle>
<AlertTriangle className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">8</div>
<p className="text-xs text-muted-foreground">Require attention</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Critical</CardTitle>
<XCircle className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">2</div>
<p className="text-xs text-muted-foreground">Immediate action needed</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Resolved Today</CardTitle>
<CheckCircle className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">15</div>
<p className="text-xs text-muted-foreground">Successfully resolved</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Avg Response</CardTitle>
<Clock className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">12m</div>
<p className="text-xs text-muted-foreground">Average resolution time</p>
</CardContent>
</Card>
</div>
{/* Critical Alerts */}
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
<strong>Critical Alert:</strong> Potential security breach detected. Multiple unusual API access patterns from different IP addresses. Immediate investigation required.
</AlertDescription>
</Alert>
{/* Alerts Table */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>System Alerts</CardTitle>
<CardDescription>Recent system alerts and notifications</CardDescription>
</div>
<div className="flex items-center space-x-2">
<Button variant="outline" size="sm">
Mark All Read
</Button>
<Button variant="outline" size="sm">
Clear Resolved
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Type</TableHead>
<TableHead>Alert</TableHead>
<TableHead>Severity</TableHead>
<TableHead>Status</TableHead>
<TableHead>Affected Users</TableHead>
<TableHead>Timestamp</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{alerts.map((alert) => (
<TableRow key={alert.id}>
<TableCell>
<div className="flex items-center space-x-2">
{getTypeIcon(alert.type)}
<span className="capitalize">{alert.type}</span>
</div>
</TableCell>
<TableCell>
<div>
<div className="font-medium">{alert.title}</div>
<div className="text-sm text-muted-foreground max-w-[300px] truncate">
{alert.description}
</div>
</div>
</TableCell>
<TableCell>
<Badge variant={getSeverityColor(alert.severity)}>
{alert.severity}
</Badge>
</TableCell>
<TableCell>
<Badge variant={getStatusColor(alert.status)}>
{alert.status}
</Badge>
</TableCell>
<TableCell>{alert.affectedUsers}</TableCell>
<TableCell className="text-sm text-muted-foreground">
{alert.timestamp}
</TableCell>
<TableCell className="text-right">
<div className="flex items-center justify-end space-x-2">
<Button variant="outline" size="sm">
View Details
</Button>
{alert.status === "active" && (
<Button variant="outline" size="sm">
Resolve
</Button>
)}
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
{/* Alert Categories */}
<div className="grid gap-6 md:grid-cols-3">
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Shield className="h-5 w-5 mr-2" />
Security Alerts
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-sm">Failed Logins</span>
<Badge variant="destructive">3</Badge>
</div>
<div className="flex justify-between">
<span className="text-sm">Suspicious Activity</span>
<Badge variant="destructive">1</Badge>
</div>
<div className="flex justify-between">
<span className="text-sm">API Abuse</span>
<Badge variant="secondary">0</Badge>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<AlertTriangle className="h-5 w-5 mr-2" />
System Alerts
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-sm">High CPU Usage</span>
<Badge variant="secondary">1</Badge>
</div>
<div className="flex justify-between">
<span className="text-sm">Memory Issues</span>
<Badge variant="default">0</Badge>
</div>
<div className="flex justify-between">
<span className="text-sm">Database Slow</span>
<Badge variant="secondary">0</Badge>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Bell className="h-5 w-5 mr-2" />
Payment Alerts
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-sm">Gateway Issues</span>
<Badge variant="destructive">1</Badge>
</div>
<div className="flex justify-between">
<span className="text-sm">Failed Transactions</span>
<Badge variant="secondary">2</Badge>
</div>
<div className="flex justify-between">
<span className="text-sm">High Volume</span>
<Badge variant="outline">0</Badge>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
);
}

View File

@@ -0,0 +1,280 @@
import React from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
import { UserX, Shield, Search, Ban, Unlock } from "lucide-react";
import { useState } from "react";
export default function AdminBanPage() {
const [banData, setBanData] = useState({
username: "",
reason: "",
duration: "",
description: ""
});
// Mock data for banned users
const bannedUsers = [
{
id: "1",
username: "spam_user",
email: "spam@example.com",
reason: "Spam",
bannedBy: "admin1",
banDate: "2024-01-15",
duration: "Permanent",
status: "active"
},
{
id: "2",
username: "fraud_vendor",
email: "fraud@example.com",
reason: "Fraud",
bannedBy: "admin1",
banDate: "2024-01-20",
duration: "30 days",
status: "active"
},
{
id: "3",
username: "policy_violator",
email: "violator@example.com",
reason: "Policy Violation",
bannedBy: "admin1",
banDate: "2024-01-25",
duration: "7 days",
status: "expired"
}
];
const handleBanUser = () => {
// Handle ban user logic
console.log("Banning user:", banData);
};
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold tracking-tight">Ban Users</h1>
<p className="text-sm text-muted-foreground mt-1">Manage user bans and suspensions</p>
</div>
{/* Stats Cards */}
<div className="grid gap-4 md:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Active Bans</CardTitle>
<Shield className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">12</div>
<p className="text-xs text-muted-foreground">Currently banned</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Permanent Bans</CardTitle>
<UserX className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">3</div>
<p className="text-xs text-muted-foreground">Permanent suspensions</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Temporary Bans</CardTitle>
<Ban className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">9</div>
<p className="text-xs text-muted-foreground">Time-limited bans</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Appeals Pending</CardTitle>
<Unlock className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">2</div>
<p className="text-xs text-muted-foreground">Awaiting review</p>
</CardContent>
</Card>
</div>
<div className="grid gap-6 lg:grid-cols-2">
{/* Ban User Form */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<UserX className="h-5 w-5 mr-2" />
Ban User
</CardTitle>
<CardDescription>
Enter user details and reason for banning
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="username">Username</Label>
<Input
id="username"
placeholder="Enter username to ban"
value={banData.username}
onChange={(e) => setBanData({...banData, username: e.target.value})}
/>
</div>
<div className="space-y-2">
<Label htmlFor="reason">Reason</Label>
<Select value={banData.reason} onValueChange={(value) => setBanData({...banData, reason: value})}>
<SelectTrigger>
<SelectValue placeholder="Select ban reason" />
</SelectTrigger>
<SelectContent>
<SelectItem value="spam">Spam</SelectItem>
<SelectItem value="fraud">Fraud</SelectItem>
<SelectItem value="harassment">Harassment</SelectItem>
<SelectItem value="policy_violation">Policy Violation</SelectItem>
<SelectItem value="suspicious_activity">Suspicious Activity</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="duration">Duration</Label>
<Select value={banData.duration} onValueChange={(value) => setBanData({...banData, duration: value})}>
<SelectTrigger>
<SelectValue placeholder="Select ban duration" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1_day">1 Day</SelectItem>
<SelectItem value="7_days">7 Days</SelectItem>
<SelectItem value="30_days">30 Days</SelectItem>
<SelectItem value="90_days">90 Days</SelectItem>
<SelectItem value="permanent">Permanent</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="description">Additional Details</Label>
<Textarea
id="description"
placeholder="Provide additional context for the ban..."
value={banData.description}
onChange={(e) => setBanData({...banData, description: e.target.value})}
/>
</div>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive" className="w-full">
<Ban className="h-4 w-4 mr-2" />
Ban User
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Confirm Ban</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to ban user "{banData.username}"? This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleBanUser}>
Confirm Ban
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</CardContent>
</Card>
{/* Search Banned Users */}
<Card>
<CardHeader>
<CardTitle>Search Banned Users</CardTitle>
<CardDescription>Look up existing bans and their status</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="relative">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input placeholder="Search by username or email..." className="pl-8" />
</div>
<Button variant="outline" className="w-full">
Search Bans
</Button>
</CardContent>
</Card>
</div>
{/* Banned Users Table */}
<Card>
<CardHeader>
<CardTitle>Recent Bans</CardTitle>
<CardDescription>View and manage current and past bans</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>User</TableHead>
<TableHead>Reason</TableHead>
<TableHead>Duration</TableHead>
<TableHead>Banned By</TableHead>
<TableHead>Date</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{bannedUsers.map((user) => (
<TableRow key={user.id}>
<TableCell>
<div>
<div className="font-medium">{user.username}</div>
<div className="text-sm text-muted-foreground">{user.email}</div>
</div>
</TableCell>
<TableCell>{user.reason}</TableCell>
<TableCell>{user.duration}</TableCell>
<TableCell>{user.bannedBy}</TableCell>
<TableCell>{user.banDate}</TableCell>
<TableCell>
<Badge
variant={user.status === "active" ? "destructive" : "secondary"}
>
{user.status}
</Badge>
</TableCell>
<TableCell className="text-right">
<div className="flex items-center justify-end space-x-2">
{user.status === "active" && (
<Button variant="outline" size="sm">
<Unlock className="h-4 w-4" />
</Button>
)}
<Button variant="outline" size="sm">
View Details
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,202 @@
"use client";
import React from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { UserPlus, Mail, Copy, Check } from "lucide-react";
import { useState } from "react";
export default function AdminInvitePage() {
const [inviteData, setInviteData] = useState({
email: "",
username: "",
role: "",
message: ""
});
const [inviteLink, setInviteLink] = useState("");
const [copied, setCopied] = useState(false);
const handleInvite = () => {
// Generate invite link (mock implementation)
const link = `https://ember-market.com/invite/${Math.random().toString(36).substr(2, 9)}`;
setInviteLink(link);
};
const copyToClipboard = () => {
navigator.clipboard.writeText(inviteLink);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold tracking-tight">Invite Vendor</h1>
<p className="text-sm text-muted-foreground mt-1">Send invitations to new vendors to join the platform</p>
</div>
<div className="grid gap-6 lg:grid-cols-2">
{/* Invite Form */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<UserPlus className="h-5 w-5 mr-2" />
Send Invitation
</CardTitle>
<CardDescription>
Fill out the details to send an invitation to a new vendor
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email Address</Label>
<Input
id="email"
type="email"
placeholder="vendor@example.com"
value={inviteData.email}
onChange={(e) => setInviteData({...inviteData, email: e.target.value})}
/>
</div>
<div className="space-y-2">
<Label htmlFor="username">Username</Label>
<Input
id="username"
placeholder="vendor_username"
value={inviteData.username}
onChange={(e) => setInviteData({...inviteData, username: e.target.value})}
/>
</div>
<div className="space-y-2">
<Label htmlFor="role">Role</Label>
<Select value={inviteData.role} onValueChange={(value) => setInviteData({...inviteData, role: value})}>
<SelectTrigger>
<SelectValue placeholder="Select vendor role" />
</SelectTrigger>
<SelectContent>
<SelectItem value="standard">Standard Vendor</SelectItem>
<SelectItem value="premium">Premium Vendor</SelectItem>
<SelectItem value="enterprise">Enterprise Vendor</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="message">Personal Message (Optional)</Label>
<Textarea
id="message"
placeholder="Add a personal message to the invitation..."
value={inviteData.message}
onChange={(e) => setInviteData({...inviteData, message: e.target.value})}
/>
</div>
<Button onClick={handleInvite} className="w-full">
<Mail className="h-4 w-4 mr-2" />
Send Invitation
</Button>
</CardContent>
</Card>
{/* Invite Link */}
{inviteLink && (
<Card>
<CardHeader>
<CardTitle>Invitation Link</CardTitle>
<CardDescription>
Share this link with the vendor to complete their registration
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="p-3 bg-muted rounded-md">
<code className="text-sm break-all">{inviteLink}</code>
</div>
<Button
onClick={copyToClipboard}
variant="outline"
className="w-full"
>
{copied ? (
<>
<Check className="h-4 w-4 mr-2" />
Copied!
</>
) : (
<>
<Copy className="h-4 w-4 mr-2" />
Copy Link
</>
)}
</Button>
</CardContent>
</Card>
)}
{/* Recent Invitations */}
<Card className="lg:col-span-2">
<CardHeader>
<CardTitle>Recent Invitations</CardTitle>
<CardDescription>Track the status of sent invitations</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex items-center space-x-4">
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
<Mail className="h-5 w-5 text-blue-600" />
</div>
<div>
<p className="font-medium">john.doe@example.com</p>
<p className="text-sm text-muted-foreground">Sent 2 hours ago</p>
</div>
</div>
<div className="flex items-center space-x-2">
<Badge variant="outline">Pending</Badge>
<Button variant="outline" size="sm">Resend</Button>
</div>
</div>
<div className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex items-center space-x-4">
<div className="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center">
<UserPlus className="h-5 w-5 text-green-600" />
</div>
<div>
<p className="font-medium">jane.smith@example.com</p>
<p className="text-sm text-muted-foreground">Accepted 1 day ago</p>
</div>
</div>
<div className="flex items-center space-x-2">
<Badge variant="default" className="bg-green-500">Accepted</Badge>
</div>
</div>
<div className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex items-center space-x-4">
<div className="w-10 h-10 bg-red-100 rounded-full flex items-center justify-center">
<Mail className="h-5 w-5 text-red-600" />
</div>
<div>
<p className="font-medium">bob.wilson@example.com</p>
<p className="text-sm text-muted-foreground">Expired 3 days ago</p>
</div>
</div>
<div className="flex items-center space-x-2">
<Badge variant="destructive">Expired</Badge>
<Button variant="outline" size="sm">Resend</Button>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
);
}

View File

@@ -0,0 +1,6 @@
import type React from "react"
import Layout from "@/components/layout/layout"
export default function AdminLayout({ children }: { children: React.ReactNode }) {
return <Layout>{children}</Layout>
}

View File

@@ -0,0 +1,205 @@
import React from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Package, AlertTriangle } from "lucide-react";
import { fetchServer } from "@/lib/api";
import OrdersTable from "@/components/admin/OrdersTable";
interface Order {
orderId: string | number;
userId: string;
total: number;
createdAt: string;
status: string;
items: Array<{
name: string;
quantity: number;
}>;
vendorUsername?: string;
}
interface SystemStats {
vendors: number;
orders: number;
products: number;
chats: number;
}
export default async function AdminOrdersPage() {
let orders: Order[] = [];
let systemStats: SystemStats | null = null;
let error: string | null = null;
try {
const [ordersData, statsData] = await Promise.all([
fetchServer<Order[]>("/admin/recent-orders"),
fetchServer<SystemStats>("/admin/stats")
]);
orders = ordersData;
systemStats = statsData;
} catch (err) {
console.error("Failed to fetch data:", err);
error = "Failed to load data";
}
if (error) {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold tracking-tight">Recent Orders</h1>
<p className="text-sm text-muted-foreground mt-1">Monitor and manage platform orders</p>
</div>
<Card>
<CardContent className="pt-6">
<div className="text-center text-red-500">
<p>{error}</p>
</div>
</CardContent>
</Card>
</div>
);
}
const acknowledgedOrders = orders.filter(o => o.status === 'acknowledged');
const paidOrders = orders.filter(o => o.status === 'paid');
const completedOrders = orders.filter(o => o.status === 'completed');
const cancelledOrders = orders.filter(o => o.status === 'cancelled');
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold tracking-tight">Recent Orders</h1>
<p className="text-sm text-muted-foreground mt-1">Monitor and manage platform orders</p>
</div>
{/* Stats Cards */}
<div className="grid gap-4 md:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Orders</CardTitle>
<Package className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{systemStats?.orders || 0}</div>
<p className="text-xs text-muted-foreground">All platform orders</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Acknowledged</CardTitle>
<AlertTriangle className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{acknowledgedOrders.length}</div>
<p className="text-xs text-muted-foreground">Vendor accepted</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Paid</CardTitle>
<Package className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{paidOrders.length}</div>
<p className="text-xs text-muted-foreground">Payment received</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Completed</CardTitle>
<Package className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{completedOrders.length}</div>
<p className="text-xs text-muted-foreground">Successfully delivered</p>
</CardContent>
</Card>
</div>
{/* Orders Table with Pagination */}
<OrdersTable orders={orders} />
{/* Order Analytics */}
<div className="grid gap-6 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle>Order Status Distribution</CardTitle>
<CardDescription>Breakdown of recent orders by status</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-purple-500 rounded-full"></div>
<span className="text-sm">Acknowledged</span>
</div>
<span className="text-sm font-medium">
{orders.length > 0 ? Math.round((acknowledgedOrders.length / orders.length) * 100) : 0}%
</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-emerald-500 rounded-full"></div>
<span className="text-sm">Paid</span>
</div>
<span className="text-sm font-medium">
{orders.length > 0 ? Math.round((paidOrders.length / orders.length) * 100) : 0}%
</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
<span className="text-sm">Completed</span>
</div>
<span className="text-sm font-medium">
{orders.length > 0 ? Math.round((completedOrders.length / orders.length) * 100) : 0}%
</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 bg-red-500 rounded-full"></div>
<span className="text-sm">Cancelled</span>
</div>
<span className="text-sm font-medium">
{orders.length > 0 ? Math.round((cancelledOrders.length / orders.length) * 100) : 0}%
</span>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Order Summary</CardTitle>
<CardDescription>Recent order activity breakdown</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm">Total Recent Orders</span>
<span className="text-sm font-medium">{orders.length}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm">Acknowledged</span>
<span className="text-sm font-medium">{acknowledgedOrders.length}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm">Paid</span>
<span className="text-sm font-medium">{paidOrders.length}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm">Completed</span>
<span className="text-sm font-medium">{completedOrders.length}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm">Cancelled</span>
<span className="text-sm font-medium">{cancelledOrders.length}</span>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
);
}

View File

@@ -1,5 +1,6 @@
export const dynamic = "force-dynamic";
import React from "react";
import InviteVendorCard from "@/components/admin/InviteVendorCard";
import BanUserCard from "@/components/admin/BanUserCard";
import RecentOrdersCard from "@/components/admin/RecentOrdersCard";
@@ -9,7 +10,7 @@ import VendorsCard from "@/components/admin/VendorsCard";
export default function AdminPage() {
return (
<div className="p-6 space-y-6">
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold tracking-tight">Admin</h1>
<p className="text-sm text-muted-foreground mt-1">Restricted area. Only admin1 can access.</p>

View File

@@ -0,0 +1,333 @@
import React from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { Separator } from "@/components/ui/separator";
import { Settings, Shield, Bell, Database, Globe, Key, Save } from "lucide-react";
export default function AdminSettingsPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold tracking-tight">Admin Settings</h1>
<p className="text-sm text-muted-foreground mt-1">Configure system settings and preferences</p>
</div>
<div className="grid gap-6 lg:grid-cols-2">
{/* General Settings */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Settings className="h-5 w-5 mr-2" />
General Settings
</CardTitle>
<CardDescription>
Basic platform configuration and preferences
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<Label htmlFor="siteName">Site Name</Label>
<Input id="siteName" defaultValue="Ember Market" />
</div>
<div className="space-y-2">
<Label htmlFor="siteDescription">Site Description</Label>
<Textarea
id="siteDescription"
defaultValue="A secure cryptocurrency marketplace for vendors and customers"
rows={3}
/>
</div>
<div className="space-y-2">
<Label htmlFor="defaultCurrency">Default Currency</Label>
<Select defaultValue="btc">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="btc">Bitcoin (BTC)</SelectItem>
<SelectItem value="eth">Ethereum (ETH)</SelectItem>
<SelectItem value="ltc">Litecoin (LTC)</SelectItem>
<SelectItem value="usdt">Tether (USDT)</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Maintenance Mode</Label>
<p className="text-sm text-muted-foreground">
Temporarily disable public access
</p>
</div>
<Switch />
</div>
</CardContent>
</Card>
{/* Security Settings */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Shield className="h-5 w-5 mr-2" />
Security Settings
</CardTitle>
<CardDescription>
Configure security policies and authentication
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Two-Factor Authentication</Label>
<p className="text-sm text-muted-foreground">
Require 2FA for all admin accounts
</p>
</div>
<Switch defaultChecked />
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Session Timeout</Label>
<p className="text-sm text-muted-foreground">
Auto-logout after inactivity
</p>
</div>
<Switch defaultChecked />
</div>
<div className="space-y-2">
<Label htmlFor="sessionDuration">Session Duration (minutes)</Label>
<Input id="sessionDuration" type="number" defaultValue="60" />
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>IP Whitelist</Label>
<p className="text-sm text-muted-foreground">
Restrict admin access to specific IPs
</p>
</div>
<Switch />
</div>
<div className="space-y-2">
<Label htmlFor="allowedIPs">Allowed IP Addresses</Label>
<Textarea
id="allowedIPs"
placeholder="192.168.1.100&#10;10.0.0.50"
rows={3}
/>
</div>
</CardContent>
</Card>
{/* Notification Settings */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Bell className="h-5 w-5 mr-2" />
Notification Settings
</CardTitle>
<CardDescription>
Configure alert and notification preferences
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Email Notifications</Label>
<p className="text-sm text-muted-foreground">
Send alerts via email
</p>
</div>
<Switch defaultChecked />
</div>
<div className="space-y-2">
<Label htmlFor="adminEmail">Admin Email</Label>
<Input id="adminEmail" type="email" defaultValue="admin@ember-market.com" />
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Security Alerts</Label>
<p className="text-sm text-muted-foreground">
Notify on security events
</p>
</div>
<Switch defaultChecked />
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>System Alerts</Label>
<p className="text-sm text-muted-foreground">
Notify on system issues
</p>
</div>
<Switch defaultChecked />
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Order Alerts</Label>
<p className="text-sm text-muted-foreground">
Notify on high-value orders
</p>
</div>
<Switch />
</div>
</CardContent>
</Card>
{/* Database Settings */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Database className="h-5 w-5 mr-2" />
Database Settings
</CardTitle>
<CardDescription>
Database configuration and maintenance
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<Label htmlFor="backupFrequency">Backup Frequency</Label>
<Select defaultValue="daily">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="hourly">Hourly</SelectItem>
<SelectItem value="daily">Daily</SelectItem>
<SelectItem value="weekly">Weekly</SelectItem>
<SelectItem value="monthly">Monthly</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Auto Backup</Label>
<p className="text-sm text-muted-foreground">
Automatically backup database
</p>
</div>
<Switch defaultChecked />
</div>
<div className="space-y-2">
<Label htmlFor="retentionPeriod">Backup Retention (days)</Label>
<Input id="retentionPeriod" type="number" defaultValue="30" />
</div>
<Separator />
<div className="space-y-2">
<Label>Database Actions</Label>
<div className="flex space-x-2">
<Button variant="outline" size="sm">
Create Backup
</Button>
<Button variant="outline" size="sm">
Optimize Database
</Button>
</div>
</div>
</CardContent>
</Card>
{/* API Settings */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Key className="h-5 w-5 mr-2" />
API Settings
</CardTitle>
<CardDescription>
API configuration and rate limiting
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<Label htmlFor="apiVersion">API Version</Label>
<Input id="apiVersion" defaultValue="v1" />
</div>
<div className="space-y-2">
<Label htmlFor="rateLimit">Rate Limit (requests/minute)</Label>
<Input id="rateLimit" type="number" defaultValue="1000" />
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>API Logging</Label>
<p className="text-sm text-muted-foreground">
Log all API requests
</p>
</div>
<Switch defaultChecked />
</div>
<div className="space-y-2">
<Label htmlFor="apiKey">Master API Key</Label>
<Input id="apiKey" type="password" defaultValue="••••••••••••••••" />
</div>
</CardContent>
</Card>
{/* System Information */}
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Globe className="h-5 w-5 mr-2" />
System Information
</CardTitle>
<CardDescription>
Platform version and system details
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex justify-between">
<span className="text-sm font-medium">Platform Version</span>
<span className="text-sm text-muted-foreground">v2.1.0</span>
</div>
<div className="flex justify-between">
<span className="text-sm font-medium">Database Version</span>
<span className="text-sm text-muted-foreground">MongoDB 6.0</span>
</div>
<div className="flex justify-between">
<span className="text-sm font-medium">Node.js Version</span>
<span className="text-sm text-muted-foreground">v18.17.0</span>
</div>
<div className="flex justify-between">
<span className="text-sm font-medium">Last Updated</span>
<span className="text-sm text-muted-foreground">2024-01-15</span>
</div>
<div className="flex justify-between">
<span className="text-sm font-medium">Uptime</span>
<span className="text-sm text-muted-foreground">15 days, 3 hours</span>
</div>
</CardContent>
</Card>
</div>
{/* Save Button */}
<div className="flex justify-end">
<Button className="w-full sm:w-auto">
<Save className="h-4 w-4 mr-2" />
Save All Settings
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,210 @@
import React from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Server, Database, Cpu, HardDrive, Activity } from "lucide-react";
import { fetchServer } from "@/lib/api";
interface SystemStatus {
uptimeSeconds: number;
memory: {
rss: number;
heapTotal: number;
heapUsed: number;
external: number;
arrayBuffers: number;
};
versions: Record<string, string>;
counts: {
vendors: number;
orders: number;
products: number;
chats: number;
};
}
export default async function AdminStatusPage() {
let systemStatus: SystemStatus | null = null;
let error: string | null = null;
try {
systemStatus = await fetchServer<SystemStatus>("/admin/system-status");
} catch (err) {
console.error("Failed to fetch system status:", err);
error = "Failed to load system status";
}
if (error) {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold tracking-tight">System Status</h1>
<p className="text-sm text-muted-foreground mt-1">Monitor system health and performance metrics</p>
</div>
<Card>
<CardContent className="pt-6">
<div className="text-center text-red-500">
<p>{error}</p>
</div>
</CardContent>
</Card>
</div>
);
}
const formatUptime = (seconds: number) => {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
return `${days}d ${hours}h ${minutes}m`;
};
const formatBytes = (bytes: number) => {
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
if (bytes === 0) return '0 Bytes';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
};
const memoryUsagePercent = systemStatus ?
Math.round((systemStatus.memory.heapUsed / systemStatus.memory.heapTotal) * 100) : 0;
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold tracking-tight">System Status</h1>
<p className="text-sm text-muted-foreground mt-1">Monitor system health and performance metrics</p>
</div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{/* Server Status */}
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Server Status</CardTitle>
<Server className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="flex items-center space-x-2">
<Badge variant="default" className="bg-green-500">Online</Badge>
<span className="text-sm text-muted-foreground">
{systemStatus ? formatUptime(systemStatus.uptimeSeconds) : 'N/A'}
</span>
</div>
<p className="text-xs text-muted-foreground mt-2">
Last checked: {new Date().toLocaleTimeString()}
</p>
</CardContent>
</Card>
{/* Database Status */}
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Database</CardTitle>
<Database className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="flex items-center space-x-2">
<Badge variant="default" className="bg-green-500">Connected</Badge>
<span className="text-sm text-muted-foreground">
{systemStatus ? `${systemStatus.counts.vendors + systemStatus.counts.orders + systemStatus.counts.products} records` : 'N/A'}
</span>
</div>
<p className="text-xs text-muted-foreground mt-2">
Total collections: 4
</p>
</CardContent>
</Card>
{/* Memory Usage */}
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Memory</CardTitle>
<HardDrive className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="flex items-center space-x-2">
<Badge variant={memoryUsagePercent > 80 ? "destructive" : memoryUsagePercent > 60 ? "secondary" : "outline"}>
{memoryUsagePercent}%
</Badge>
<span className="text-sm text-muted-foreground">
{systemStatus ? formatBytes(systemStatus.memory.heapUsed) : 'N/A'}
</span>
</div>
<p className="text-xs text-muted-foreground mt-2">
Total: {systemStatus ? formatBytes(systemStatus.memory.heapTotal) : 'N/A'}
</p>
</CardContent>
</Card>
{/* Platform Stats */}
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Platform Stats</CardTitle>
<Activity className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="flex items-center space-x-2">
<Badge variant="default" className="bg-green-500">Active</Badge>
<span className="text-sm text-muted-foreground">
{systemStatus ? `${systemStatus.counts.vendors} vendors` : 'N/A'}
</span>
</div>
<p className="text-xs text-muted-foreground mt-2">
{systemStatus ? `${systemStatus.counts.orders} orders, ${systemStatus.counts.products} products` : 'N/A'}
</p>
</CardContent>
</Card>
{/* Node.js Version */}
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Runtime</CardTitle>
<Activity className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="flex items-center space-x-2">
<Badge variant="outline">
{systemStatus ? `Node ${systemStatus.versions.node}` : 'N/A'}
</Badge>
<span className="text-sm text-muted-foreground">Runtime</span>
</div>
<p className="text-xs text-muted-foreground mt-2">
{systemStatus ? `V8: ${systemStatus.versions.v8}` : 'N/A'}
</p>
</CardContent>
</Card>
</div>
{/* Recent Activity */}
<Card>
<CardHeader>
<CardTitle>Recent System Activity</CardTitle>
<CardDescription>Latest system events and changes</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center space-x-4">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<div className="flex-1">
<p className="text-sm font-medium">System health check completed</p>
<p className="text-xs text-muted-foreground">2 minutes ago</p>
</div>
</div>
<div className="flex items-center space-x-4">
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
<div className="flex-1">
<p className="text-sm font-medium">Database backup completed</p>
<p className="text-xs text-muted-foreground">1 hour ago</p>
</div>
</div>
<div className="flex items-center space-x-4">
<div className="w-2 h-2 bg-yellow-500 rounded-full"></div>
<div className="flex-1">
<p className="text-sm font-medium">High memory usage detected</p>
<p className="text-xs text-muted-foreground">3 hours ago</p>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
);
}

188
app/dashboard/admin/vendors/page.tsx vendored Normal file
View File

@@ -0,0 +1,188 @@
import React from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Search, MoreHorizontal, UserCheck, UserX, Mail } from "lucide-react";
import { fetchServer } from "@/lib/api";
interface Vendor {
_id: string;
username: string;
email?: string;
storeId?: string;
pgpKey?: string;
createdAt?: string;
currentToken?: string;
isAdmin?: boolean;
}
export default async function AdminVendorsPage() {
let vendors: Vendor[] = [];
let error: string | null = null;
try {
vendors = await fetchServer<Vendor[]>("/admin/vendors");
} catch (err) {
console.error("Failed to fetch vendors:", err);
error = "Failed to load vendors";
}
if (error) {
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold tracking-tight">All Vendors</h1>
<p className="text-sm text-muted-foreground mt-1">Manage vendor accounts and permissions</p>
</div>
<Card>
<CardContent className="pt-6">
<div className="text-center text-red-500">
<p>{error}</p>
</div>
</CardContent>
</Card>
</div>
);
}
const activeVendors = vendors.filter(v => v.currentToken);
const adminVendors = vendors.filter(v => v.isAdmin);
const suspendedVendors = vendors.filter(v => !v.currentToken);
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold tracking-tight">All Vendors</h1>
<p className="text-sm text-muted-foreground mt-1">Manage vendor accounts and permissions</p>
</div>
{/* Stats Cards */}
<div className="grid gap-4 md:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Vendors</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{vendors.length}</div>
<p className="text-xs text-muted-foreground">Registered vendors</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Active Vendors</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{activeVendors.length}</div>
<p className="text-xs text-muted-foreground">
{vendors.length > 0 ? Math.round((activeVendors.length / vendors.length) * 100) : 0}% active rate
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Suspended</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{suspendedVendors.length}</div>
<p className="text-xs text-muted-foreground">
{vendors.length > 0 ? Math.round((suspendedVendors.length / vendors.length) * 100) : 0}% suspension rate
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Admin Users</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{adminVendors.length}</div>
<p className="text-xs text-muted-foreground">Administrative access</p>
</CardContent>
</Card>
</div>
{/* Search and Filters */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>Vendor Management</CardTitle>
<CardDescription>View and manage all vendor accounts</CardDescription>
</div>
<div className="flex items-center space-x-2">
<div className="relative">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input placeholder="Search vendors..." className="pl-8 w-64" />
</div>
<Button variant="outline" size="sm">
<Mail className="h-4 w-4 mr-2" />
Send Message
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Vendor</TableHead>
<TableHead>Store</TableHead>
<TableHead>Status</TableHead>
<TableHead>Join Date</TableHead>
<TableHead>Orders</TableHead>
<TableHead>Revenue</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{vendors.map((vendor) => (
<TableRow key={vendor._id}>
<TableCell>
<div>
<div className="font-medium">{vendor.username}</div>
<div className="text-sm text-muted-foreground">{vendor.email || 'No email'}</div>
</div>
</TableCell>
<TableCell>{vendor.storeId || 'No store'}</TableCell>
<TableCell>
<div className="flex flex-col space-y-1">
<Badge
variant={vendor.currentToken ? "default" : "destructive"}
>
{vendor.currentToken ? "active" : "suspended"}
</Badge>
{vendor.isAdmin && (
<Badge variant="secondary" className="text-xs">
Admin
</Badge>
)}
</div>
</TableCell>
<TableCell>
{vendor.createdAt ? new Date(vendor.createdAt).toLocaleDateString() : 'N/A'}
</TableCell>
<TableCell>N/A</TableCell>
<TableCell>N/A</TableCell>
<TableCell className="text-right">
<div className="flex items-center justify-end space-x-2">
<Button variant="outline" size="sm">
<UserCheck className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm">
<UserX className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm">
<MoreHorizontal className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
);
}