Yaltopia-Ticket-Admin/src/pages/admin/security/api-keys.tsx
2026-04-08 09:28:01 +03:00

159 lines
6.7 KiB
TypeScript

import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Ban, Key, Calendar, User, Zap } from "lucide-react";
import { securityService, type ApiKey } from "@/services";
import { toast } from "sonner";
import { format } from "date-fns";
import type { ApiError } from "@/types/error.types";
import { cn } from "@/lib/utils";
export default function ApiKeysPage() {
const queryClient = useQueryClient();
const { data: apiKeys, isLoading } = useQuery({
queryKey: ["admin", "security", "api-keys"],
queryFn: () => securityService.getAllApiKeys(),
});
const revokeMutation = useMutation({
mutationFn: (id: string) => securityService.revokeApiKey(id),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["admin", "security", "api-keys"],
});
toast.success("API access credential revoked");
},
onError: (error) => {
const apiError = error as ApiError;
toast.error(
apiError.response?.data?.message || "Failed to revoke access",
);
},
});
return (
<div className="space-y-8 max-w-7xl mx-auto bg-white p-4 min-h-screen">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<h1 className="text-3xl font-bold text-gray-900 tracking-tight">
API Gateway
</h1>
<p className="text-gray-500 mt-1">
Management of system access credentials and authentication tokens.
</p>
</div>
</div>
<Card className="border shadow-none rounded-none">
<CardHeader className="border-b pb-4 flex flex-row items-center justify-between bg-gray-50/30">
<CardTitle className="text-xs font-bold uppercase tracking-widest text-gray-400">
Credential Registry
</CardTitle>
<Zap className="w-4 h-4 text-gray-300" />
</CardHeader>
<CardContent className="p-0">
<div className="overflow-x-auto">
<table className="w-full text-left">
<thead className="bg-gray-50 border-b">
<tr>
<th className="px-6 py-4 text-[10px] font-bold text-gray-400 uppercase tracking-widest">
Key Identifier
</th>
<th className="px-6 py-4 text-[10px] font-bold text-gray-400 uppercase tracking-widest">
Operator
</th>
<th className="px-6 py-4 text-[10px] font-bold text-gray-400 uppercase tracking-widest">
Last Activity
</th>
<th className="px-6 py-4 text-[10px] font-bold text-gray-400 uppercase tracking-widest">
Access Status
</th>
<th className="px-6 py-4 text-[10px] font-bold text-gray-400 uppercase tracking-widest text-right">
Actions
</th>
</tr>
</thead>
<tbody className="divide-y text-gray-600">
{isLoading ? (
<tr>
<td
colSpan={5}
className="px-6 py-20 text-center text-gray-400 animate-pulse font-medium uppercase tracking-widest text-[10px]"
>
Retrieving secure credentials...
</td>
</tr>
) : apiKeys && apiKeys.length > 0 ? (
apiKeys.map((key: ApiKey) => (
<tr
key={key.id}
className="hover:bg-gray-50 transition-colors group"
>
<td className="px-6 py-4">
<div className="flex items-center gap-2">
<Key className="w-3 h-3 text-gray-300" />
<span className="text-sm font-bold text-gray-900 tracking-tighter">
{key.name}
</span>
</div>
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-2 text-xs font-medium">
<User className="w-3 h-3 text-gray-300" />
{key.userId || "N/A"}
</div>
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-2 text-xs font-medium">
<Calendar className="w-3 h-3 text-gray-300" />
{key.lastUsed
? format(new Date(key.lastUsed), "MMM dd, yyyy")
: "Inactive"}
</div>
</td>
<td className="px-6 py-4">
<span
className={cn(
"px-2 py-0.5 text-[9px] font-bold uppercase tracking-widest border rounded-none",
key.isActive
? "bg-emerald-50 text-emerald-600 border-emerald-100"
: "bg-rose-50 text-rose-600 border-rose-100",
)}
>
{key.isActive ? "Authorized" : "Deactivated"}
</span>
</td>
<td className="px-6 py-4 text-right">
{key.isActive && (
<Button
variant="ghost"
size="sm"
className="h-8 rounded-none border border-transparent hover:border-rose-100 hover:bg-rose-50 hover:text-rose-600 transition-all font-bold uppercase tracking-widest text-[9px]"
onClick={() => revokeMutation.mutate(key.id)}
>
<Ban className="w-3 h-3 mr-2" /> Revoke Access
</Button>
)}
</td>
</tr>
))
) : (
<tr>
<td
colSpan={5}
className="px-6 py-20 text-center text-gray-400 italic font-medium uppercase tracking-widest text-[10px]"
>
No API access credentials defined.
</td>
</tr>
)}
</tbody>
</table>
</div>
</CardContent>
</Card>
</div>
);
}