Add system status and invitations cards to admin page

Introduces SystemStatusCard and InvitationsListCard components to the admin dashboard for displaying system metrics and active invitations. Refactors InviteVendorCard to generate and display invitation codes, and updates card layouts for consistent sizing. Improves admin page structure and enhances visibility of system and invitation data.
This commit is contained in:
NotII
2025-10-15 17:40:54 +01:00
parent 4fb6d3f740
commit e7c06e4352
6 changed files with 170 additions and 46 deletions

View File

@@ -3,23 +3,18 @@ import { useState } from "react";
import { fetchClient } from "@/lib/api-client";
export default function InviteVendorCard() {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState<string | null>(null);
const [code, setCode] = useState<string | null>(null);
async function handleInvite(e: React.FormEvent) {
e.preventDefault();
async function handleInvite() {
setLoading(true);
setMessage(null);
setCode(null);
try {
await fetchClient("/admin/invitations", {
method: "POST",
body: { username, email }
});
setMessage("Invitation sent");
setUsername("");
setEmail("");
const res = await fetchClient<{ code: string }>("/admin/invitations", { method: "POST" });
setMessage("Invitation created");
setCode(res.code);
} catch (e: any) {
setMessage(e?.message || "Failed to send invitation");
} finally {
@@ -28,33 +23,24 @@ export default function InviteVendorCard() {
}
return (
<div className="rounded-lg border border-border/60 bg-background p-4">
<div className="rounded-lg border border-border/60 bg-background p-4 h-full min-h-[200px]">
<h2 className="font-medium">Invite Vendor</h2>
<p className="text-sm text-muted-foreground mt-1">Generate and send an invite</p>
<form onSubmit={handleInvite} className="mt-4 space-y-3">
<input
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
placeholder="Vendor username"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
<input
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
placeholder="Email (optional)"
value={email}
onChange={(e) => setEmail(e.target.value)}
type="email"
/>
<p className="text-sm text-muted-foreground mt-1">Generate a new invitation code</p>
<div className="mt-4 space-y-3">
<button
type="submit"
onClick={handleInvite}
className="inline-flex items-center rounded-md bg-primary px-3 py-2 text-sm text-primary-foreground disabled:opacity-60"
disabled={loading}
>
{loading ? "Sending..." : "Send Invite"}
{loading ? "Generating..." : "Generate Invite Code"}
</button>
{message && <p className="text-xs text-muted-foreground">{message}</p>}
</form>
{code && (
<div className="text-sm">
Code: <span className="font-mono px-1.5 py-0.5 rounded bg-muted">{code}</span>
</div>
)}
</div>
</div>
);
}