diff --git a/frontend/src/pages/settings/settings-api-tokens.tsx b/frontend/src/pages/settings/settings-api-tokens.tsx index 0b046b1b..49f4b310 100644 --- a/frontend/src/pages/settings/settings-api-tokens.tsx +++ b/frontend/src/pages/settings/settings-api-tokens.tsx @@ -16,7 +16,7 @@ import { Trash, X, } from 'lucide-react'; -import { useCallback, useMemo, useRef, useState } from 'react'; +import { useCallback, useId, useMemo, useState } from 'react'; import { toast } from 'sonner'; import type { ApiTokenFragmentFragment } from '@/graphql/types'; @@ -64,6 +64,7 @@ interface CreateFormData { } interface EditFormData { + name: string; status: TokenStatusEnum; } @@ -198,15 +199,17 @@ function SettingsAPITokens() { const [editingTokenId, setEditingTokenId] = useState(null); const [creatingToken, setCreatingToken] = useState(false); - const [editFormData, setEditFormData] = useState({ status: TokenStatusEnum.Active }); + const [editFormData, setEditFormData] = useState({ name: '', status: TokenStatusEnum.Active }); const [createFormData, setCreateFormData] = useState({ expiresAt: null, name: '' }); const [tokenSecret, setTokenSecret] = useState(null); const [showTokenDialog, setShowTokenDialog] = useState(false); const [deleteErrorMessage, setDeleteErrorMessage] = useState(null); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [deletingToken, setDeletingToken] = useState(null); - const editingInputRef = useRef(null); - const creatingInputRef = useRef(null); + // Stable per-instance ids — keep label/for and a11y warnings clean even when + // the row re-mounts due to subscription-driven refetches. + const createNameFieldId = useId(); + const editNameFieldId = useId(); const { filter, pageIndex: currentPage, setFilter, setPage: handlePageChange } = useTableState(); @@ -231,18 +234,19 @@ function SettingsAPITokens() { const handleEdit = useCallback((token: APIToken) => { setEditingTokenId(token.tokenId); setEditFormData({ + name: token.name ?? '', status: token.status, }); }, []); const handleCancelEdit = useCallback(() => { setEditingTokenId(null); - setEditFormData({ status: TokenStatusEnum.Active }); + setEditFormData({ name: '', status: TokenStatusEnum.Active }); }, []); const handleSave = useCallback( async (tokenId: string) => { - const name = editingInputRef.current?.value.trim() || null; + const name = editFormData.name.trim() || null; try { await updateAPIToken({ @@ -257,12 +261,12 @@ function SettingsAPITokens() { }); setEditingTokenId(null); - setEditFormData({ status: TokenStatusEnum.Active }); + setEditFormData({ name: '', status: TokenStatusEnum.Active }); } catch (error) { console.error('Failed to update token:', error); } }, - [editFormData.status, updateAPIToken], + [editFormData.name, editFormData.status, updateAPIToken], ); const handleCreateNew = useCallback(() => { @@ -280,7 +284,7 @@ function SettingsAPITokens() { return; } - const name = creatingInputRef.current?.value.trim() || null; + const name = createFormData.name.trim() || null; try { const ttl = calculateTTL(createFormData.expiresAt); @@ -304,7 +308,7 @@ function SettingsAPITokens() { } catch (error) { console.error('Failed to create token:', error); } - }, [createAPIToken, createFormData.expiresAt]); + }, [createAPIToken, createFormData.expiresAt, createFormData.name]); const handleDeleteDialogOpen = useCallback((token: APIToken) => { setDeletingToken(token); @@ -358,27 +362,35 @@ function SettingsAPITokens() { if (isCreating) { return ( + setCreateFormData((prev) => ({ ...prev, name: event.target.value })) + } placeholder="Token name (optional)" - ref={creatingInputRef} + value={createFormData.name} /> ); } if (isEditing) { - const tokenName = token.name || ''; - return ( + setEditFormData((prev) => ({ ...prev, name: event.target.value })) + } placeholder="Token name (optional)" - ref={editingInputRef} + value={editFormData.name} /> ); } @@ -706,8 +718,12 @@ function SettingsAPITokens() { ], [ createFormData.expiresAt, + createFormData.name, + createNameFieldId, deletingToken, + editFormData.name, editFormData.status, + editNameFieldId, editingTokenId, handleCancelCreate, handleCancelEdit,