'use client' import { useTranslations } from 'next-intl' import { useState, useEffect, useCallback } from 'react' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Badge } from '@/components/ui/badge' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Checkbox } from '@/components/ui/checkbox' import { DestructiveConfirmDialog, useDestructiveConfirm } from '@/components/ui/destructive-confirm-dialog' import { useToast } from '@/components/ui/use-toast' import { Loader2, Plus, Copy, Check, Trash2, Key, ChevronDown, AlertTriangle } from 'lucide-react' import { cn } from '@/lib/utils' import { getBranding } from '@/lib/branding/service' import { STAGING_SCOPES } from '@/lib/auth/api-keys' import type { ApiKeyScope } from '@/lib/auth/api-keys' const branding = getBranding() const connectorName = branding.appName.toLowerCase() type ScopeEntry = { scope: ApiKeyScope labelKey: string /** Number of MCP tools gated by this scope. 0 = REST-API-only scope. */ tools: number } type ScopeGroup = { domain: string labelKey: string read: ScopeEntry | null write: ScopeEntry | null } const SCOPE_GROUPS: ScopeGroup[] = [ { domain: 'transactions', labelKey: 'group_transactions', read: { scope: 'transactions:read', labelKey: 'scope_transactions_read', tools: 8 }, write: { scope: 'transactions:write', labelKey: 'scope_transactions_write', tools: 8 }, }, { domain: 'customers', labelKey: 'group_customers', read: { scope: 'customers:read', labelKey: 'scope_customers_read', tools: 1 }, write: { scope: 'customers:write', labelKey: 'scope_customers_write', tools: 1 }, }, { domain: 'invoices', labelKey: 'group_invoices', read: { scope: 'invoices:read', labelKey: 'scope_invoices_read', tools: 1 }, write: { scope: 'invoices:write', labelKey: 'scope_invoices_write', tools: 6 }, }, { domain: 'suppliers', labelKey: 'group_suppliers', read: { scope: 'suppliers:read', labelKey: 'scope_suppliers_read', tools: 2 }, write: { scope: 'suppliers:write', labelKey: 'scope_suppliers_write', tools: 3 }, }, { domain: 'reports', labelKey: 'group_reports', read: { scope: 'reports:read', labelKey: 'scope_reports_read', tools: 18 }, write: null, }, { domain: 'bookkeeping', labelKey: 'group_bookkeeping', read: null, write: { scope: 'bookkeeping:write', labelKey: 'scope_bookkeeping_write', tools: 11 }, }, { domain: 'payroll', labelKey: 'group_payroll', read: { scope: 'payroll:read', labelKey: 'scope_payroll_read', tools: 3 }, write: { scope: 'payroll:write', labelKey: 'scope_payroll_write', tools: 3 }, }, { domain: 'pending_operations', labelKey: 'group_pending_operations', read: { scope: 'pending_operations:read', labelKey: 'scope_pending_operations_read', tools: 1 }, write: { scope: 'pending_operations:approve', labelKey: 'scope_pending_operations_approve', tools: 2 }, }, { domain: 'agent', labelKey: 'group_agent', read: { scope: 'agent:read', labelKey: 'scope_agent_read', tools: 1 }, write: { scope: 'agent:write', labelKey: 'scope_agent_write', tools: 2 }, }, { domain: 'documents', labelKey: 'group_documents', read: { scope: 'documents:read', labelKey: 'scope_documents_read', tools: 0 }, write: { scope: 'documents:write', labelKey: 'scope_documents_write', tools: 0 }, }, { domain: 'companies', labelKey: 'group_companies', read: { scope: 'companies:read', labelKey: 'scope_companies_read', tools: 0 }, write: null, }, { domain: 'events', labelKey: 'group_events', read: { scope: 'events:read', labelKey: 'scope_events_read', tools: 0 }, write: null, }, { domain: 'webhooks', labelKey: 'group_webhooks', read: null, write: { scope: 'webhooks:manage', labelKey: 'scope_webhooks_manage', tools: 0 }, }, { domain: 'operations', labelKey: 'group_operations', read: { scope: 'operations:read', labelKey: 'scope_operations_read', tools: 0 }, write: null, }, { domain: 'compliance', labelKey: 'group_compliance', read: { scope: 'compliance:read', labelKey: 'scope_compliance_read', tools: 0 }, write: null, }, ] type Scope = ApiKeyScope const ALL_SCOPES: Scope[] = SCOPE_GROUPS.flatMap((g) => { const out: Scope[] = [] if (g.read) out.push(g.read.scope) if (g.write) out.push(g.write.scope) return out }) interface ApiKey { id: string key_prefix: string name: string scopes: string[] | null rate_limit_rpm: number last_used_at: string | null revoked_at: string | null created_at: string } function CopyBlock({ text, copyAriaLabel }: { text: string; copyAriaLabel: string }) { const [copied, setCopied] = useState(false) async function handleCopy() { try { await navigator.clipboard.writeText(text) setCopied(true) setTimeout(() => setCopied(false), 2000) } catch { // clipboard unavailable (insecure context) — silently ignore } } return (
{text}
{t('empty_title')}
{t('empty_help')}
{key.name}
{scopeCount === ALL_SCOPES.length ? t('all_permissions') : scopeCount === 0 ? t('no_permissions') : t('permissions_count', { count: scopeCount })}
{key.key_prefix}...
{t('created')} {formatDate(key.created_at)}
{key.last_used_at
? t('used_on', { date: formatDate(key.last_used_at) })
: t('never_used')}
Claude.ai
{t.rich('claude_ai_instructions', { connectorName, path: (chunks) => {chunks}, })}
{t('claude_code_cursor')}
{t('terminal_runs_browser_login')}
Claude Desktop
{t.rich('claude_desktop_instructions', {
code: (chunks) => {chunks},
})}
{t('claude_code_cursor')}
{t('terminal_with_api_key')}