From 85dacb03a3afa1b5967d6032a309f86bac554f6d Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 7 Feb 2025 15:57:35 -0500 Subject: [PATCH 1/3] Better UX for adding new API config profiles --- .changeset/dirty-coins-exist.md | 5 + .../components/settings/ApiConfigManager.tsx | 323 ++++++++++++++---- .../__tests__/ApiConfigManager.test.tsx | 154 ++++++++- 3 files changed, 399 insertions(+), 83 deletions(-) create mode 100644 .changeset/dirty-coins-exist.md diff --git a/.changeset/dirty-coins-exist.md b/.changeset/dirty-coins-exist.md new file mode 100644 index 0000000000..d01a3ba76e --- /dev/null +++ b/.changeset/dirty-coins-exist.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Improve the user experience for adding a new configuration profile diff --git a/webview-ui/src/components/settings/ApiConfigManager.tsx b/webview-ui/src/components/settings/ApiConfigManager.tsx index b10adf4a49..652803fe76 100644 --- a/webview-ui/src/components/settings/ApiConfigManager.tsx +++ b/webview-ui/src/components/settings/ApiConfigManager.tsx @@ -1,8 +1,9 @@ import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { memo, useEffect, useRef, useState } from "react" +import { memo, useEffect, useReducer, useRef } from "react" import { ApiConfigMeta } from "../../../../src/shared/ExtensionMessage" import { Dropdown } from "vscrui" import type { DropdownOption } from "vscrui" +import { Dialog, DialogContent } from "../ui/dialog" interface ApiConfigManagerProps { currentApiConfigName?: string @@ -13,6 +14,86 @@ interface ApiConfigManagerProps { onUpsertConfig: (configName: string) => void } +type State = { + isRenaming: boolean + isCreating: boolean + inputValue: string + newProfileName: string + error: string | null +} + +type Action = + | { type: "START_RENAME"; payload: string } + | { type: "CANCEL_EDIT" } + | { type: "SET_INPUT"; payload: string } + | { type: "SET_NEW_NAME"; payload: string } + | { type: "START_CREATE" } + | { type: "CANCEL_CREATE" } + | { type: "SET_ERROR"; payload: string | null } + | { type: "RESET_STATE" } + +const initialState: State = { + isRenaming: false, + isCreating: false, + inputValue: "", + newProfileName: "", + error: null, +} + +const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "START_RENAME": + return { + ...state, + isRenaming: true, + inputValue: action.payload, + error: null, + } + case "CANCEL_EDIT": + return { + ...state, + isRenaming: false, + inputValue: "", + error: null, + } + case "SET_INPUT": + return { + ...state, + inputValue: action.payload, + error: null, + } + case "SET_NEW_NAME": + return { + ...state, + newProfileName: action.payload, + error: null, + } + case "START_CREATE": + return { + ...state, + isCreating: true, + newProfileName: "", + error: null, + } + case "CANCEL_CREATE": + return { + ...state, + isCreating: false, + newProfileName: "", + error: null, + } + case "SET_ERROR": + return { + ...state, + error: action.payload, + } + case "RESET_STATE": + return initialState + default: + return state + } +} + const ApiConfigManager = ({ currentApiConfigName = "", listApiConfigMeta = [], @@ -21,55 +102,93 @@ const ApiConfigManager = ({ onRenameConfig, onUpsertConfig, }: ApiConfigManagerProps) => { - const [editState, setEditState] = useState<"new" | "rename" | null>(null) - const [inputValue, setInputValue] = useState("") - const inputRef = useRef() + const [state, dispatch] = useReducer(reducer, initialState) + const inputRef = useRef(null) + const newProfileInputRef = useRef(null) - // Focus input when entering edit mode - useEffect(() => { - if (editState) { - setTimeout(() => inputRef.current?.focus(), 0) + const validateName = (name: string, isNewProfile: boolean): string | null => { + const trimmed = name.trim() + if (!trimmed) return "Name cannot be empty" + + const nameExists = listApiConfigMeta?.some((config) => config.name.toLowerCase() === trimmed.toLowerCase()) + + // For new profiles, any existing name is invalid + if (isNewProfile && nameExists) { + return "A profile with this name already exists" } - }, [editState]) - // Reset edit state when current profile changes + // For rename, only block if trying to rename to a different existing profile + if (!isNewProfile && nameExists && trimmed.toLowerCase() !== currentApiConfigName?.toLowerCase()) { + return "A profile with this name already exists" + } + + return null + } + + // Focus input when entering rename mode useEffect(() => { - setEditState(null) - setInputValue("") + if (state.isRenaming) { + const timeoutId = setTimeout(() => inputRef.current?.focus(), 0) + return () => clearTimeout(timeoutId) + } + }, [state.isRenaming]) + + // Focus input when opening new dialog + useEffect(() => { + if (state.isCreating) { + const timeoutId = setTimeout(() => newProfileInputRef.current?.focus(), 0) + return () => clearTimeout(timeoutId) + } + }, [state.isCreating]) + + // Reset state when current profile changes + useEffect(() => { + dispatch({ type: "RESET_STATE" }) }, [currentApiConfigName]) const handleAdd = () => { - const newConfigName = currentApiConfigName + " (copy)" - onUpsertConfig(newConfigName) + dispatch({ type: "START_CREATE" }) } const handleStartRename = () => { - setEditState("rename") - setInputValue(currentApiConfigName || "") + dispatch({ type: "START_RENAME", payload: currentApiConfigName || "" }) } const handleCancel = () => { - setEditState(null) - setInputValue("") + dispatch({ type: "CANCEL_EDIT" }) } const handleSave = () => { - const trimmedValue = inputValue.trim() - if (!trimmedValue) return + const trimmedValue = state.inputValue.trim() + const error = validateName(trimmedValue, false) - if (editState === "new") { - onUpsertConfig(trimmedValue) - } else if (editState === "rename" && currentApiConfigName) { + if (error) { + dispatch({ type: "SET_ERROR", payload: error }) + return + } + + if (state.isRenaming && currentApiConfigName) { if (currentApiConfigName === trimmedValue) { - setEditState(null) - setInputValue("") + dispatch({ type: "CANCEL_EDIT" }) return } onRenameConfig(currentApiConfigName, trimmedValue) } - setEditState(null) - setInputValue("") + dispatch({ type: "CANCEL_EDIT" }) + } + + const handleNewProfileSave = () => { + const trimmedValue = state.newProfileName.trim() + const error = validateName(trimmedValue, true) + + if (error) { + dispatch({ type: "SET_ERROR", payload: error }) + return + } + + onUpsertConfig(trimmedValue) + dispatch({ type: "CANCEL_CREATE" }) } const handleDelete = () => { @@ -93,49 +212,62 @@ const ApiConfigManager = ({ Configuration Profile - {editState ? ( -
- setInputValue(e.target.value)} - placeholder={editState === "new" ? "Enter profile name" : "Enter new name"} - style={{ flexGrow: 1 }} - onKeyDown={(e: any) => { - if (e.key === "Enter" && inputValue.trim()) { - handleSave() - } else if (e.key === "Escape") { - handleCancel() - } - }} - /> - - - - - - + {state.isRenaming ? ( +
+
+ { + const target = e as { target: { value: string } } + dispatch({ type: "SET_INPUT", payload: target.target.value }) + }} + placeholder="Enter new name" + style={{ flexGrow: 1 }} + onKeyDown={(e: unknown) => { + const event = e as { key: string } + if (event.key === "Enter" && state.inputValue.trim()) { + handleSave() + } else if (event.key === "Escape") { + handleCancel() + } + }} + /> + + + + + + +
+ {state.error && ( +

+ {state.error} +

+ )}
) : ( <> @@ -211,6 +343,57 @@ const ApiConfigManager = ({

)} + + dispatch({ type: open ? "START_CREATE" : "CANCEL_CREATE" })} + aria-labelledby="new-profile-title"> + +

+ New Configuration Profile +

+ + { + const target = e as { target: { value: string } } + dispatch({ type: "SET_NEW_NAME", payload: target.target.value }) + }} + placeholder="Enter profile name" + style={{ width: "100%" }} + onKeyDown={(e: unknown) => { + const event = e as { key: string } + if (event.key === "Enter" && state.newProfileName.trim()) { + handleNewProfileSave() + } else if (event.key === "Escape") { + dispatch({ type: "CANCEL_CREATE" }) + } + }} + /> + {state.error && ( +

+ {state.error} +

+ )} +
+ dispatch({ type: "CANCEL_CREATE" })}> + Cancel + + + Create Profile + +
+
+
) diff --git a/webview-ui/src/components/settings/__tests__/ApiConfigManager.test.tsx b/webview-ui/src/components/settings/__tests__/ApiConfigManager.test.tsx index ac6245d6d1..24e62215ec 100644 --- a/webview-ui/src/components/settings/__tests__/ApiConfigManager.test.tsx +++ b/webview-ui/src/components/settings/__tests__/ApiConfigManager.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, fireEvent } from "@testing-library/react" +import { render, screen, fireEvent, within } from "@testing-library/react" import ApiConfigManager from "../ApiConfigManager" // Mock VSCode components @@ -8,11 +8,12 @@ jest.mock("@vscode/webview-ui-toolkit/react", () => ({ {children} ), - VSCodeTextField: ({ value, onInput, placeholder }: any) => ( + VSCodeTextField: ({ value, onInput, placeholder, onKeyDown }: any) => ( onInput(e)} placeholder={placeholder} + onKeyDown={onKeyDown} ref={undefined} // Explicitly set ref to undefined to avoid warning /> ), @@ -32,6 +33,16 @@ jest.mock("vscrui", () => ({ ), })) +// Mock Dialog component +jest.mock("@/components/ui/dialog", () => ({ + Dialog: ({ children, open, onOpenChange }: any) => ( +
+ {children} +
+ ), + DialogContent: ({ children }: any) =>
{children}
, +})) + describe("ApiConfigManager", () => { const mockOnSelectConfig = jest.fn() const mockOnDeleteConfig = jest.fn() @@ -54,34 +65,74 @@ describe("ApiConfigManager", () => { jest.clearAllMocks() }) - it("immediately creates a copy when clicking add button", () => { + const getRenameForm = () => screen.getByTestId("rename-form") + const getDialogContent = () => screen.getByTestId("dialog-content") + + it("opens new profile dialog when clicking add button", () => { render() - // Find and click the add button const addButton = screen.getByTitle("Add profile") fireEvent.click(addButton) - // Verify that onUpsertConfig was called with the correct name - expect(mockOnUpsertConfig).toHaveBeenCalledTimes(1) - expect(mockOnUpsertConfig).toHaveBeenCalledWith("Default Config (copy)") + expect(screen.getByTestId("dialog")).toBeVisible() + expect(screen.getByText("New Configuration Profile")).toBeInTheDocument() }) - it("creates copy with correct name when current config has spaces", () => { - render() + it("creates new profile with entered name", () => { + render() + // Open dialog const addButton = screen.getByTitle("Add profile") fireEvent.click(addButton) - expect(mockOnUpsertConfig).toHaveBeenCalledWith("My Test Config (copy)") + // Enter new profile name + const input = screen.getByPlaceholderText("Enter profile name") + fireEvent.input(input, { target: { value: "New Profile" } }) + + // Click create button + const createButton = screen.getByText("Create Profile") + fireEvent.click(createButton) + + expect(mockOnUpsertConfig).toHaveBeenCalledWith("New Profile") }) - it("handles empty current config name gracefully", () => { - render() + it("shows error when creating profile with existing name", () => { + render() + // Open dialog const addButton = screen.getByTitle("Add profile") fireEvent.click(addButton) - expect(mockOnUpsertConfig).toHaveBeenCalledWith(" (copy)") + // Enter existing profile name + const input = screen.getByPlaceholderText("Enter profile name") + fireEvent.input(input, { target: { value: "Default Config" } }) + + // Click create button to trigger validation + const createButton = screen.getByText("Create Profile") + fireEvent.click(createButton) + + // Verify error message + const dialogContent = getDialogContent() + const errorMessage = within(dialogContent).getByTestId("error-message") + expect(errorMessage).toHaveTextContent("A profile with this name already exists") + expect(mockOnUpsertConfig).not.toHaveBeenCalled() + }) + + it("prevents creating profile with empty name", () => { + render() + + // Open dialog + const addButton = screen.getByTitle("Add profile") + fireEvent.click(addButton) + + // Enter empty name + const input = screen.getByPlaceholderText("Enter profile name") + fireEvent.input(input, { target: { value: " " } }) + + // Verify create button is disabled + const createButton = screen.getByText("Create Profile") + expect(createButton).toBeDisabled() + expect(mockOnUpsertConfig).not.toHaveBeenCalled() }) it("allows renaming the current config", () => { @@ -102,6 +153,45 @@ describe("ApiConfigManager", () => { expect(mockOnRenameConfig).toHaveBeenCalledWith("Default Config", "New Name") }) + it("shows error when renaming to existing config name", () => { + render() + + // Start rename + const renameButton = screen.getByTitle("Rename profile") + fireEvent.click(renameButton) + + // Find input and enter existing name + const input = screen.getByDisplayValue("Default Config") + fireEvent.input(input, { target: { value: "Another Config" } }) + + // Save to trigger validation + const saveButton = screen.getByTitle("Save") + fireEvent.click(saveButton) + + // Verify error message + const renameForm = getRenameForm() + const errorMessage = within(renameForm).getByTestId("error-message") + expect(errorMessage).toHaveTextContent("A profile with this name already exists") + expect(mockOnRenameConfig).not.toHaveBeenCalled() + }) + + it("prevents renaming to empty name", () => { + render() + + // Start rename + const renameButton = screen.getByTitle("Rename profile") + fireEvent.click(renameButton) + + // Find input and enter empty name + const input = screen.getByDisplayValue("Default Config") + fireEvent.input(input, { target: { value: " " } }) + + // Verify save button is disabled + const saveButton = screen.getByTitle("Save") + expect(saveButton).toBeDisabled() + expect(mockOnRenameConfig).not.toHaveBeenCalled() + }) + it("allows selecting a different config", () => { render() @@ -149,4 +239,42 @@ describe("ApiConfigManager", () => { // Verify we're back to normal view expect(screen.queryByDisplayValue("New Name")).not.toBeInTheDocument() }) + + it("handles keyboard events in new profile dialog", () => { + render() + + // Open dialog + const addButton = screen.getByTitle("Add profile") + fireEvent.click(addButton) + + const input = screen.getByPlaceholderText("Enter profile name") + + // Test Enter key + fireEvent.input(input, { target: { value: "New Profile" } }) + fireEvent.keyDown(input, { key: "Enter" }) + expect(mockOnUpsertConfig).toHaveBeenCalledWith("New Profile") + + // Test Escape key + fireEvent.keyDown(input, { key: "Escape" }) + expect(screen.getByTestId("dialog")).not.toBeVisible() + }) + + it("handles keyboard events in rename mode", () => { + render() + + // Start rename + const renameButton = screen.getByTitle("Rename profile") + fireEvent.click(renameButton) + + const input = screen.getByDisplayValue("Default Config") + + // Test Enter key + fireEvent.input(input, { target: { value: "New Name" } }) + fireEvent.keyDown(input, { key: "Enter" }) + expect(mockOnRenameConfig).toHaveBeenCalledWith("Default Config", "New Name") + + // Test Escape key + fireEvent.keyDown(input, { key: "Escape" }) + expect(screen.queryByDisplayValue("New Name")).not.toBeInTheDocument() + }) }) From b85ebbc89a02539a0167f8ea722fd03769b9e3a3 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 7 Feb 2025 20:21:15 -0500 Subject: [PATCH 2/3] Change to useState to match the rest of the codebase --- .../components/settings/ApiConfigManager.tsx | 196 +++++++----------- 1 file changed, 80 insertions(+), 116 deletions(-) diff --git a/webview-ui/src/components/settings/ApiConfigManager.tsx b/webview-ui/src/components/settings/ApiConfigManager.tsx index 652803fe76..2374b90c0a 100644 --- a/webview-ui/src/components/settings/ApiConfigManager.tsx +++ b/webview-ui/src/components/settings/ApiConfigManager.tsx @@ -1,5 +1,5 @@ import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { memo, useEffect, useReducer, useRef } from "react" +import { memo, useEffect, useRef, useState } from "react" import { ApiConfigMeta } from "../../../../src/shared/ExtensionMessage" import { Dropdown } from "vscrui" import type { DropdownOption } from "vscrui" @@ -14,86 +14,6 @@ interface ApiConfigManagerProps { onUpsertConfig: (configName: string) => void } -type State = { - isRenaming: boolean - isCreating: boolean - inputValue: string - newProfileName: string - error: string | null -} - -type Action = - | { type: "START_RENAME"; payload: string } - | { type: "CANCEL_EDIT" } - | { type: "SET_INPUT"; payload: string } - | { type: "SET_NEW_NAME"; payload: string } - | { type: "START_CREATE" } - | { type: "CANCEL_CREATE" } - | { type: "SET_ERROR"; payload: string | null } - | { type: "RESET_STATE" } - -const initialState: State = { - isRenaming: false, - isCreating: false, - inputValue: "", - newProfileName: "", - error: null, -} - -const reducer = (state: State, action: Action): State => { - switch (action.type) { - case "START_RENAME": - return { - ...state, - isRenaming: true, - inputValue: action.payload, - error: null, - } - case "CANCEL_EDIT": - return { - ...state, - isRenaming: false, - inputValue: "", - error: null, - } - case "SET_INPUT": - return { - ...state, - inputValue: action.payload, - error: null, - } - case "SET_NEW_NAME": - return { - ...state, - newProfileName: action.payload, - error: null, - } - case "START_CREATE": - return { - ...state, - isCreating: true, - newProfileName: "", - error: null, - } - case "CANCEL_CREATE": - return { - ...state, - isCreating: false, - newProfileName: "", - error: null, - } - case "SET_ERROR": - return { - ...state, - error: action.payload, - } - case "RESET_STATE": - return initialState - default: - return state - } -} - const ApiConfigManager = ({ currentApiConfigName = "", listApiConfigMeta = [], @@ -102,7 +22,11 @@ const ApiConfigManager = ({ onRenameConfig, onUpsertConfig, }: ApiConfigManagerProps) => { - const [state, dispatch] = useReducer(reducer, initialState) + const [isRenaming, setIsRenaming] = useState(false) + const [isCreating, setIsCreating] = useState(false) + const [inputValue, setInputValue] = useState("") + const [newProfileName, setNewProfileName] = useState("") + const [error, setError] = useState(null) const inputRef = useRef(null) const newProfileInputRef = useRef(null) @@ -127,68 +51,84 @@ const ApiConfigManager = ({ // Focus input when entering rename mode useEffect(() => { - if (state.isRenaming) { + if (isRenaming) { const timeoutId = setTimeout(() => inputRef.current?.focus(), 0) return () => clearTimeout(timeoutId) } - }, [state.isRenaming]) + }, [isRenaming]) // Focus input when opening new dialog useEffect(() => { - if (state.isCreating) { + if (isCreating) { const timeoutId = setTimeout(() => newProfileInputRef.current?.focus(), 0) return () => clearTimeout(timeoutId) } - }, [state.isCreating]) + }, [isCreating]) // Reset state when current profile changes useEffect(() => { - dispatch({ type: "RESET_STATE" }) + setIsRenaming(false) + setIsCreating(false) + setInputValue("") + setNewProfileName("") + setError(null) }, [currentApiConfigName]) const handleAdd = () => { - dispatch({ type: "START_CREATE" }) + setIsCreating(true) + setNewProfileName("") + setError(null) } const handleStartRename = () => { - dispatch({ type: "START_RENAME", payload: currentApiConfigName || "" }) + setIsRenaming(true) + setInputValue(currentApiConfigName || "") + setError(null) } const handleCancel = () => { - dispatch({ type: "CANCEL_EDIT" }) + setIsRenaming(false) + setInputValue("") + setError(null) } const handleSave = () => { - const trimmedValue = state.inputValue.trim() + const trimmedValue = inputValue.trim() const error = validateName(trimmedValue, false) if (error) { - dispatch({ type: "SET_ERROR", payload: error }) + setError(error) return } - if (state.isRenaming && currentApiConfigName) { + if (isRenaming && currentApiConfigName) { if (currentApiConfigName === trimmedValue) { - dispatch({ type: "CANCEL_EDIT" }) + setIsRenaming(false) + setInputValue("") + setError(null) return } onRenameConfig(currentApiConfigName, trimmedValue) } - dispatch({ type: "CANCEL_EDIT" }) + setIsRenaming(false) + setInputValue("") + setError(null) } const handleNewProfileSave = () => { - const trimmedValue = state.newProfileName.trim() + const trimmedValue = newProfileName.trim() const error = validateName(trimmedValue, true) if (error) { - dispatch({ type: "SET_ERROR", payload: error }) + setError(error) return } onUpsertConfig(trimmedValue) - dispatch({ type: "CANCEL_CREATE" }) + setIsCreating(false) + setNewProfileName("") + setError(null) } const handleDelete = () => { @@ -212,23 +152,24 @@ const ApiConfigManager = ({ Configuration Profile - {state.isRenaming ? ( + {isRenaming ? (
{ const target = e as { target: { value: string } } - dispatch({ type: "SET_INPUT", payload: target.target.value }) + setInputValue(target.target.value) + setError(null) }} placeholder="Enter new name" style={{ flexGrow: 1 }} onKeyDown={(e: unknown) => { const event = e as { key: string } - if (event.key === "Enter" && state.inputValue.trim()) { + if (event.key === "Enter" && inputValue.trim()) { handleSave() } else if (event.key === "Escape") { handleCancel() @@ -237,7 +178,7 @@ const ApiConfigManager = ({ />
- {state.error && ( + {error && (

- {state.error} + {error}

)}
@@ -345,8 +286,18 @@ const ApiConfigManager = ({ )} dispatch({ type: open ? "START_CREATE" : "CANCEL_CREATE" })} + open={isCreating} + onOpenChange={(open: boolean) => { + if (open) { + setIsCreating(true) + setNewProfileName("") + setError(null) + } else { + setIsCreating(false) + setNewProfileName("") + setError(null) + } + }} aria-labelledby="new-profile-title">

@@ -355,39 +306,52 @@ const ApiConfigManager = ({ { const target = e as { target: { value: string } } - dispatch({ type: "SET_NEW_NAME", payload: target.target.value }) + setNewProfileName(target.target.value) + setError(null) }} placeholder="Enter profile name" style={{ width: "100%" }} onKeyDown={(e: unknown) => { const event = e as { key: string } - if (event.key === "Enter" && state.newProfileName.trim()) { + if (event.key === "Enter" && newProfileName.trim()) { handleNewProfileSave() } else if (event.key === "Escape") { - dispatch({ type: "CANCEL_CREATE" }) + setIsCreating(false) + setNewProfileName("") + setError(null) } }} /> - {state.error && ( + {error && (

- {state.error} + {error}

)}
- dispatch({ type: "CANCEL_CREATE" })}> + { + setIsCreating(false) + setNewProfileName("") + setError(null) + }}> Cancel Create Profile From 69d58720db8c46b8838c781a97db47e19708a589 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 7 Feb 2025 20:28:24 -0500 Subject: [PATCH 3/3] Ellipsis feedback --- .../components/settings/ApiConfigManager.tsx | 63 +++++++------------ 1 file changed, 23 insertions(+), 40 deletions(-) diff --git a/webview-ui/src/components/settings/ApiConfigManager.tsx b/webview-ui/src/components/settings/ApiConfigManager.tsx index 2374b90c0a..62aae27642 100644 --- a/webview-ui/src/components/settings/ApiConfigManager.tsx +++ b/webview-ui/src/components/settings/ApiConfigManager.tsx @@ -49,6 +49,18 @@ const ApiConfigManager = ({ return null } + const resetCreateState = () => { + setIsCreating(false) + setNewProfileName("") + setError(null) + } + + const resetRenameState = () => { + setIsRenaming(false) + setInputValue("") + setError(null) + } + // Focus input when entering rename mode useEffect(() => { if (isRenaming) { @@ -67,17 +79,13 @@ const ApiConfigManager = ({ // Reset state when current profile changes useEffect(() => { - setIsRenaming(false) - setIsCreating(false) - setInputValue("") - setNewProfileName("") - setError(null) + resetCreateState() + resetRenameState() }, [currentApiConfigName]) const handleAdd = () => { + resetCreateState() setIsCreating(true) - setNewProfileName("") - setError(null) } const handleStartRename = () => { @@ -87,9 +95,7 @@ const ApiConfigManager = ({ } const handleCancel = () => { - setIsRenaming(false) - setInputValue("") - setError(null) + resetRenameState() } const handleSave = () => { @@ -103,17 +109,13 @@ const ApiConfigManager = ({ if (isRenaming && currentApiConfigName) { if (currentApiConfigName === trimmedValue) { - setIsRenaming(false) - setInputValue("") - setError(null) + resetRenameState() return } onRenameConfig(currentApiConfigName, trimmedValue) } - setIsRenaming(false) - setInputValue("") - setError(null) + resetRenameState() } const handleNewProfileSave = () => { @@ -126,9 +128,7 @@ const ApiConfigManager = ({ } onUpsertConfig(trimmedValue) - setIsCreating(false) - setNewProfileName("") - setError(null) + resetCreateState() } const handleDelete = () => { @@ -293,9 +293,7 @@ const ApiConfigManager = ({ setNewProfileName("") setError(null) } else { - setIsCreating(false) - setNewProfileName("") - setError(null) + resetCreateState() } }} aria-labelledby="new-profile-title"> @@ -303,14 +301,7 @@ const ApiConfigManager = ({

New Configuration Profile

- @@ -340,13 +329,7 @@ const ApiConfigManager = ({

)}
- { - setIsCreating(false) - setNewProfileName("") - setError(null) - }}> + Cancel