diff --git a/client/src/components/Plugins/Store/PluginAuthForm.tsx b/client/src/components/Plugins/Store/PluginAuthForm.tsx
index 91f46aaf39..3bf7b20a1e 100644
--- a/client/src/components/Plugins/Store/PluginAuthForm.tsx
+++ b/client/src/components/Plugins/Store/PluginAuthForm.tsx
@@ -1,5 +1,13 @@
import { useForm, useWatch } from 'react-hook-form';
-import { HoverCard, HoverCardTrigger, SecretInput, Input, Label, Button } from '@librechat/client';
+import {
+ Input,
+ Label,
+ Button,
+ Spinner,
+ HoverCard,
+ SecretInput,
+ HoverCardTrigger,
+} from '@librechat/client';
import type { TPlugin, TPluginAuthConfig, TPluginAction } from 'librechat-data-provider';
import type { RegisterOptions } from 'react-hook-form';
import PluginTooltip from './PluginTooltip';
@@ -70,9 +78,19 @@ type TPluginAuthFormProps = {
plugin: TPlugin | undefined;
onSubmit: (installActionData: TPluginAction) => void;
isEntityTool?: boolean;
+ /** External in-flight state (e.g. a mutation) so the button reflects the real save. */
+ isSaving?: boolean;
+ /** When provided, renders a Cancel button (used when re-editing saved credentials). */
+ onCancel?: () => void;
};
-function PluginAuthForm({ plugin, onSubmit, isEntityTool }: TPluginAuthFormProps) {
+function PluginAuthForm({
+ plugin,
+ onSubmit,
+ isEntityTool,
+ isSaving,
+ onCancel,
+}: TPluginAuthFormProps) {
const {
register,
control,
@@ -84,6 +102,7 @@ function PluginAuthForm({ plugin, onSubmit, isEntityTool }: TPluginAuthFormProps
const watchedValues = useWatch({ control });
const authConfig = plugin?.authConfig ?? [];
const allFieldsOptional = authConfig.length > 0 && authConfig.every((c) => c.optional === true);
+ const saving = isSubmitting || isSaving === true;
const submit = handleSubmit((auth) =>
onSubmit({
@@ -178,14 +197,20 @@ function PluginAuthForm({ plugin, onSubmit, isEntityTool }: TPluginAuthFormProps
);
})}
-
+
+ {onCancel && (
+
+ )}
diff --git a/client/src/components/Plugins/Store/__tests__/PluginAuthForm.spec.tsx b/client/src/components/Plugins/Store/__tests__/PluginAuthForm.spec.tsx
index 99f65f7c4e..0b3c7402cc 100644
--- a/client/src/components/Plugins/Store/__tests__/PluginAuthForm.spec.tsx
+++ b/client/src/components/Plugins/Store/__tests__/PluginAuthForm.spec.tsx
@@ -1,5 +1,5 @@
-import { render, screen } from 'test/layout-test-utils';
import userEvent from '@testing-library/user-event';
+import { render, screen } from 'test/layout-test-utils';
import PluginAuthForm from '../PluginAuthForm';
describe('PluginAuthForm', () => {
@@ -76,4 +76,29 @@ describe('PluginAuthForm', () => {
},
});
});
+
+ it('reflects an external saving state as a disabled, in-progress submit button', () => {
+ //@ts-ignore - dont need all props of plugin
+ render(
);
+
+ const button = screen.getByRole('button', { name: 'Saving...' });
+ expect(button).toBeDisabled();
+ expect(screen.queryByRole('button', { name: 'Save' })).not.toBeInTheDocument();
+ });
+
+ it('renders a Cancel button when onCancel is provided and invokes it on click', async () => {
+ const onCancel = jest.fn();
+ //@ts-ignore - dont need all props of plugin
+ render(
);
+
+ await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
+ expect(onCancel).toHaveBeenCalledTimes(1);
+ expect(onSubmit).not.toHaveBeenCalled();
+ });
+
+ it('does not render a Cancel button by default', () => {
+ //@ts-ignore - dont need all props of plugin
+ render(
);
+ expect(screen.queryByRole('button', { name: 'Cancel' })).not.toBeInTheDocument();
+ });
});
diff --git a/client/src/components/SidePanel/Agents/Tools/ItemDialog/sections/ToolSection.tsx b/client/src/components/SidePanel/Agents/Tools/ItemDialog/sections/ToolSection.tsx
index 9a5745de71..c4e974ffa7 100644
--- a/client/src/components/SidePanel/Agents/Tools/ItemDialog/sections/ToolSection.tsx
+++ b/client/src/components/SidePanel/Agents/Tools/ItemDialog/sections/ToolSection.tsx
@@ -83,7 +83,15 @@ export default function ToolSection({ item }: Props) {
)}
- {showForm && }
+ {showForm && (
+ setEditing(false) : undefined}
+ onSubmit={handleSubmit}
+ />
+ )}
);
}