"use client" import { useState, useEffect, useCallback } from "react" import { authClient } from "@/lib/auth-client" import { Loader2, Key, Plus, Trash2, Copy, Check, Eye, EyeOff, Clock, AlertCircle, } from "lucide-react" import { motion, AnimatePresence } from "motion/react" // Raw API key type from Better Auth (dates are Date objects from the API) interface RawApiKey { id: string name: string | null start: string | null prefix: string | null enabled: boolean expiresAt: Date | null lastRequest: Date | null remaining: number | null createdAt: Date updatedAt: Date referenceId: string metadata: Record | null permissions: Record | null configId: string refillInterval: number | null refillAmount: number | null lastRefillAt: Date | null rateLimitEnabled: boolean rateLimitTimeWindow: number | null rateLimitMax: number | null requestCount: number } interface CreatedApiKey extends RawApiKey { key: string // Only returned on creation } export function SettingsApiKeysTab() { const [apiKeys, setApiKeys] = useState([]) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) // Create form state const [showCreateForm, setShowCreateForm] = useState(false) const [newKeyName, setNewKeyName] = useState("") const [newKeyExpiresIn, setNewKeyExpiresIn] = useState("") const [isCreating, setIsCreating] = useState(false) const [createdKey, setCreatedKey] = useState(null) const [createError, setCreateError] = useState(null) // Delete state const [deletingId, setDeletingId] = useState(null) const [message, setMessage] = useState<{ type: "success" | "error" text: string } | null>(null) // Copy state const [copied, setCopied] = useState(false) const [showKey, setShowKey] = useState(false) const fetchApiKeys = useCallback(async () => { try { const { data, error } = await authClient.apiKey.list({}) if (error) { setError(error.message || "Failed to load API keys") setApiKeys([]) } else { setApiKeys((data?.apiKeys ?? []) as RawApiKey[]) setError(null) } } catch { setError("Failed to load API keys. Please try again.") setApiKeys([]) } finally { setIsLoading(false) } }, []) useEffect(() => { fetchApiKeys() }, [fetchApiKeys]) const handleCreate = async (e: React.FormEvent) => { e.preventDefault() setCreateError(null) if (!newKeyName.trim()) { setCreateError("Name is required") return } setIsCreating(true) try { const expiresIn = newKeyExpiresIn ? parseInt(newKeyExpiresIn) * 24 * 60 * 60 : undefined const { data, error } = await authClient.apiKey.create({ name: newKeyName.trim(), expiresIn, }) if (error) { setCreateError(error.message || "Failed to create API key") } else if (data) { setCreatedKey(data as unknown as CreatedApiKey) await fetchApiKeys() setNewKeyName("") setNewKeyExpiresIn("") setShowCreateForm(false) } } catch { setCreateError("An unexpected error occurred") } finally { setIsCreating(false) } } const handleDelete = async (keyId: string) => { setDeletingId(keyId) setMessage(null) try { const { error } = await authClient.apiKey.delete({ keyId }) if (error) { setMessage({ type: "error", text: error.message || "Failed to delete API key", }) } else { setMessage({ type: "success", text: "API key deleted" }) await fetchApiKeys() } } catch { setMessage({ type: "error", text: "Failed to delete API key. Please try again.", }) } finally { setDeletingId(null) } } const formatDate = (date: Date | string | null): string => { if (!date) return "Never" try { const d = date instanceof Date ? date : new Date(date) return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", hour: "2-digit", minute: "2-digit", }) } catch { return String(date) } } const handleCopyKey = async () => { if (createdKey?.key) { try { await navigator.clipboard.writeText(createdKey.key) setCopied(true) setTimeout(() => setCopied(false), 2000) } catch { // Fallback for older browsers const textarea = document.createElement("textarea") textarea.value = createdKey.key document.body.appendChild(textarea) textarea.select() document.execCommand("copy") document.body.removeChild(textarea) setCopied(true) setTimeout(() => setCopied(false), 2000) } } } // Show the created key modal if (createdKey) { return (

API Key Created

Copy your API key now. You won't be able to see it again.

{showKey ? createdKey.key : `${createdKey.key.substring(0, 12)}${"•".repeat(Math.min(createdKey.key.length - 12, 20))}`}
{createdKey.name && (

Name: {createdKey.name}

)} {createdKey.expiresAt && (

Expires:{" "} {formatDate(createdKey.expiresAt)}

)}
) } return ( {/* Header */}

API Keys

{/* Message */} {message && (
{message.text}
)} {/* Create Form */} {showCreateForm && (
setNewKeyName(e.target.value)} className='w-full px-3 py-2 rounded-lg bg-text/5 border border-border text-sm text-text focus:outline-none focus:border-primary/60 focus:ring-1 focus:ring-primary/30 transition-colors' placeholder='e.g. Decky Loader Plugin' maxLength={32} required />
setNewKeyExpiresIn(e.target.value)} className='w-full px-3 py-2 rounded-lg bg-text/5 border border-border text-sm text-text focus:outline-none focus:border-primary/60 focus:ring-1 focus:ring-primary/30 transition-colors' placeholder='Leave empty for no expiry' min={1} max={365} />
{createError && (

{createError}

)}
)}
{/* Key List */} {error ? (

{error}

) : isLoading ? (
Loading API keys...
) : apiKeys.length === 0 ? (

No API keys created yet.

Create an API key to use with the Decky Loader plugin or other external tools.

) : (
{apiKeys.map((ak) => (

{ak.name || "Unnamed key"}

{!ak.enabled && ( Disabled )}
{ak.start && ( {ak.start}... )} {ak.expiresAt && ( Expires {formatDate(ak.expiresAt)} )} {ak.lastRequest && ( Last used {formatDate(ak.lastRequest)} )} Created {formatDate(ak.createdAt)}
))}
)} {/* Info */}

Using API keys: Pass your API key as the{" "} x-api-key header when making requests to DeckyVault's API. You can use these keys with the Decky Loader plugin or any automation tool.

) }