Introduces a new VendorsCard component for managing vendor accounts in the admin dashboard. The card displays vendor details, allows password reset token generation, and handles loading and error states.
98 lines
3.6 KiB
TypeScript
98 lines
3.6 KiB
TypeScript
"use client";
|
|
import { useEffect, useState } from "react";
|
|
import { fetchClient } from "@/lib/api-client";
|
|
|
|
interface Vendor {
|
|
_id: string;
|
|
username: string;
|
|
email?: string;
|
|
isAdmin: boolean;
|
|
createdAt: string;
|
|
lastLogin?: string;
|
|
}
|
|
|
|
export default function VendorsCard() {
|
|
const [vendors, setVendors] = useState<Vendor[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [resetTokens, setResetTokens] = useState<Record<string, string>>({});
|
|
|
|
useEffect(() => {
|
|
let mounted = true;
|
|
(async () => {
|
|
try {
|
|
const data = await fetchClient<Vendor[]>("/admin/vendors");
|
|
if (mounted) setVendors(data);
|
|
} catch (e: any) {
|
|
if (mounted) setError(e?.message || "Failed to load vendors");
|
|
} finally {
|
|
if (mounted) setLoading(false);
|
|
}
|
|
})();
|
|
return () => { mounted = false; };
|
|
}, []);
|
|
|
|
async function generateResetToken(vendorId: string) {
|
|
try {
|
|
const res = await fetchClient<{ token: string; expiresAt: string }>("/admin/password-reset-token", {
|
|
method: "POST",
|
|
body: { vendorId }
|
|
});
|
|
setResetTokens(prev => ({ ...prev, [vendorId]: res.token }));
|
|
} catch (e: any) {
|
|
setError(e?.message || "Failed to generate reset token");
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="rounded-lg border border-border/60 bg-background p-4 h-full min-h-[200px]">
|
|
<h2 className="font-medium">Vendors</h2>
|
|
<p className="text-sm text-muted-foreground mt-1">Manage vendor accounts and access</p>
|
|
|
|
{loading ? (
|
|
<p className="text-sm text-muted-foreground mt-3">Loading...</p>
|
|
) : error ? (
|
|
<p className="text-sm text-muted-foreground mt-3">{error}</p>
|
|
) : vendors.length === 0 ? (
|
|
<p className="text-sm text-muted-foreground mt-3">No vendors found</p>
|
|
) : (
|
|
<div className="mt-3 space-y-2 max-h-64 overflow-y-auto">
|
|
{vendors.map((vendor) => (
|
|
<div key={vendor._id} className="rounded border border-border/50 p-3 text-sm">
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-1">
|
|
<div className="font-medium">{vendor.username}</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
{vendor.email && `${vendor.email} · `}
|
|
Created: {new Date(vendor.createdAt).toLocaleDateString()}
|
|
{vendor.lastLogin && ` · Last login: ${new Date(vendor.lastLogin).toLocaleDateString()}`}
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{vendor.isAdmin && (
|
|
<span className="text-xs px-2 py-0.5 rounded bg-emerald-500/15 text-emerald-400">
|
|
Admin
|
|
</span>
|
|
)}
|
|
<button
|
|
onClick={() => generateResetToken(vendor._id)}
|
|
className="text-xs px-2 py-1 rounded border border-border hover:bg-muted/40"
|
|
>
|
|
Reset Password
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{resetTokens[vendor._id] && (
|
|
<div className="mt-2 p-2 rounded bg-muted/40 text-xs">
|
|
<div className="font-mono break-all">{resetTokens[vendor._id]}</div>
|
|
<div className="text-muted-foreground mt-1">Copy this token to share with the vendor</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|