From 5ff18403bd0f9348353906000126174a5b3b66a4 Mon Sep 17 00:00:00 2001 From: admin-valentin Date: Sun, 2 Aug 2026 13:05:45 +0000 Subject: [PATCH] feat(CC-081): add API & Developer settings page (key management with scope selection) --- src/app/dashboard/settings/api/page.tsx | 144 ++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 src/app/dashboard/settings/api/page.tsx diff --git a/src/app/dashboard/settings/api/page.tsx b/src/app/dashboard/settings/api/page.tsx new file mode 100644 index 0000000..9faff5f --- /dev/null +++ b/src/app/dashboard/settings/api/page.tsx @@ -0,0 +1,144 @@ +'use client'; + +import { useState } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { apiFetch } from '../../../lib/api'; +import { useSession } from '../../../components/session-provider'; + +interface ApiKey { id: string; name: string; keyPreview: string; scopes: string[]; lastUsedAt: string | null; expiresAt: string | null; createdAt: string; } + +const SCOPES = ['read:all', 'write:tasks', 'write:contacts', 'write:documents', 'write:observations', 'ai:query', 'export']; + +export default function ApiDeveloperPage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + const qc = useQueryClient(); + const [showCreate, setShowCreate] = useState(false); + const [form, setForm] = useState({ name: '', scopes: [] as string[], expiresAt: '' }); + const [newKey, setNewKey] = useState(null); + + const { data: keys = [], isLoading } = useQuery({ + queryKey: ['api-keys', tenantId], + queryFn: () => apiFetch('/v1/api-keys', { tenantId }), + enabled: Boolean(tenantId), staleTime: 60_000, + }); + + const createMut = useMutation({ + mutationFn: () => apiFetch('/v1/api-keys', { tenantId, method: 'POST', body: { + name: form.name, scopes: form.scopes, expiresAt: form.expiresAt || null, + }}), + onSuccess: (data: { key?: string }) => { + qc.invalidateQueries({ queryKey: ['api-keys', tenantId] }); + setNewKey(data?.key ?? null); + setShowCreate(false); + setForm({ name: '', scopes: [], expiresAt: '' }); + }, + }); + + const revokeMut = useMutation({ + mutationFn: (id: string) => apiFetch(`/v1/api-keys/${id}`, { tenantId, method: 'DELETE' }), + onSuccess: () => qc.invalidateQueries({ queryKey: ['api-keys', tenantId] }), + }); + + function toggleScope(s: string) { + setForm((p) => ({ ...p, scopes: p.scopes.includes(s) ? p.scopes.filter((x) => x !== s) : [...p.scopes, s] })); + } + + return ( +
+
+
+

API & Developer

+

Chei API pentru acces programatic la CEO OS.

+
+ +
+ + {newKey && ( +
+

✅ Cheia a fost creată. Copiaz-o acum — nu va mai fi afișată.

+ {newKey} + +
+ )} + + {showCreate && ( +
+

Cheie API nouă

+
+ setForm((p) => ({ ...p, name: e.target.value }))} + className="rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring" /> + setForm((p) => ({ ...p, expiresAt: e.target.value }))} + className="rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring" /> +
+
+

Scope-uri (principiul minimului privilegiu)

+
+ {SCOPES.map((s) => ( + + ))} +
+
+
+ + +
+
+ )} + + {/* Endpoint reference */} +
+

Endpoint API

+ https://api.boardmind.dev/v1 +
+

Header autentificare: Authorization: Bearer YOUR_KEY

+

Tenant ID: X-Tenant-ID: {tenantId || 'YOUR_TENANT_ID'}

+
+
+ + {isLoading ? ( +
Se încarcă…
+ ) : keys.length === 0 ? ( +
+

🔑

+

Nicio cheie API. Creează prima cheie de mai sus.

+
+ ) : ( +
+ {keys.map((key) => ( +
+
+

{key.name}

+ {key.keyPreview}… +
+ {(key.scopes ?? []).map((s) => ( + {s} + ))} +
+
+
+ {key.lastUsedAt ?

Folosit: {new Date(key.lastUsedAt).toLocaleDateString('ro-RO')}

:

Nefolosit

} + {key.expiresAt &&

Expiră: {new Date(key.expiresAt).toLocaleDateString('ro-RO')}

} +
+ +
+ ))} +
+ )} +
+ ); +}