Files
ember-market-frontend/components/admin/InviteVendorCard.tsx
g 244014f33a
All checks were successful
Build Frontend / build (push) Successful in 1m7s
Improve admin UI and vendor invite experience
Enhanced the admin dashboard tab styling for better clarity. Refactored InviteVendorCard with improved UI, feedback, and clipboard copy functionality. Fixed vendor store ID update to send raw object instead of JSON string. Ensured product price formatting is robust against non-numeric values.
2026-01-12 07:33:16 +00:00

106 lines
3.6 KiB
TypeScript

import { useState } from "react";
import { fetchClient } from "@/lib/api-client";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Copy, Check, Ticket, Loader2, RefreshCw } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
export default function InviteVendorCard() {
const [loading, setLoading] = useState(false);
const [code, setCode] = useState<string | null>(null);
const [copied, setCopied] = useState(false);
const { toast } = useToast();
async function handleInvite() {
setLoading(true);
setCode(null);
setCopied(false);
try {
const res = await fetchClient<{ code: string }>("/admin/invitations", { method: "POST" });
setCode(res.code);
toast({
title: "Invitation Created",
description: "New vendor invitation code generated successfully.",
});
} catch (e: any) {
toast({
title: "Error",
description: e?.message || "Failed to generate invitation",
variant: "destructive",
});
} finally {
setLoading(false);
}
}
const copyToClipboard = () => {
if (!code) return;
navigator.clipboard.writeText(code);
setCopied(true);
toast({
title: "Copied",
description: "Invitation code copied to clipboard",
});
setTimeout(() => setCopied(false), 2000);
};
return (
<Card className="h-full border-border/40 bg-background/50 backdrop-blur-sm shadow-sm flex flex-col">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-base font-medium flex items-center gap-2">
<Ticket className="h-4 w-4 text-primary" />
Invite Vendor
</CardTitle>
</div>
<CardDescription>Generate a one-time invitation code.</CardDescription>
</CardHeader>
<CardContent className="flex-1 flex flex-col justify-center gap-4">
{code ? (
<div className="space-y-3 animate-in fade-in zoom-in-95 duration-300">
<div className="p-3 rounded-md bg-muted/50 border border-border/50 text-center relative group">
<span className="font-mono text-xl font-bold tracking-widest text-primary">{code}</span>
<Button
variant="ghost"
size="icon"
className="absolute right-1 top-1 h-8 w-8 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={copyToClipboard}
>
{copied ? <Check className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
</Button>
</div>
<p className="text-xs text-center text-muted-foreground">
Share this code with the new vendor. It expires in 7 days.
</p>
</div>
) : (
<div className="text-center py-2 text-sm text-muted-foreground/80">
Click generate to create a new code.
</div>
)}
</CardContent>
<CardFooter className="pt-0">
<Button
onClick={handleInvite}
disabled={loading}
className="w-full bg-primary/90 hover:bg-primary shadow-sm"
>
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Generating...
</>
) : (
<>
{code ? <RefreshCw className="mr-2 h-4 w-4" /> : <Ticket className="mr-2 h-4 w-4" />}
{code ? "Generate Another" : "Generate Code"}
</>
)}
</Button>
</CardFooter>
</Card>
);
}