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:
188
app/dashboard/admin/vendors/page.tsx
vendored
Normal file
188
app/dashboard/admin/vendors/page.tsx
vendored
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user