feat: extension add & edit in settings v2 (#1480)
This commit is contained in:
+6
-6
@@ -1,18 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Card } from '../../../../ui/card';
|
import { Card } from './ui/card';
|
||||||
|
|
||||||
interface ProviderSetupOverlayProps {
|
interface ModalProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the semi-transparent backdrop + blur for the modal.
|
* A reusable modal component that renders content with a semi-transparent backdrop and blur effect.
|
||||||
*/
|
*/
|
||||||
export default function ProviderSetupOverlay({ children }: ProviderSetupOverlayProps) {
|
export default function Modal({ children }: ModalProps) {
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black/20 dark:bg-white/20 backdrop-blur-sm transition-colors animate-[fadein_200ms_ease-in_forwards]">
|
<div className="fixed inset-0 bg-black/20 dark:bg-white/20 backdrop-blur-sm transition-colors animate-[fadein_200ms_ease-in_forwards]">
|
||||||
<Card className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] bg-bgApp rounded-xl overflow-hidden shadow-none p-[16px] pt-[24px] pb-0">
|
<Card className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] bg-bgApp rounded-xl overflow-hidden shadow-none p-6">
|
||||||
<div className="px-4 pb-0 space-y-6">{children}</div>
|
<div className="space-y-6">{children}</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import { Switch } from '../ui/switch';
|
import { Switch } from '../ui/switch';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus, X } from 'lucide-react';
|
||||||
import { Gear } from '../icons/Gear';
|
import { Gear } from '../icons/Gear';
|
||||||
import { GPSIcon } from '../ui/icons';
|
import { GPSIcon } from '../ui/icons';
|
||||||
import { useConfig } from '../ConfigContext';
|
import { useConfig } from '../ConfigContext';
|
||||||
|
import Modal from '../Modal';
|
||||||
|
import { Input } from '../ui/input';
|
||||||
|
import Select from 'react-select';
|
||||||
|
import { createDarkSelectStyles, darkSelectTheme } from '../ui/select-styles';
|
||||||
|
|
||||||
interface ExtensionConfig {
|
interface ExtensionConfig {
|
||||||
args?: string[];
|
args?: string[];
|
||||||
@@ -12,7 +16,7 @@ interface ExtensionConfig {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
envs?: Record<string, string>;
|
envs?: Record<string, string>;
|
||||||
name: string;
|
name: string;
|
||||||
type: 'stdio' | 'builtin';
|
type: 'stdio' | 'sse';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExtensionItem {
|
interface ExtensionItem {
|
||||||
@@ -24,6 +28,11 @@ interface ExtensionItem {
|
|||||||
config: ExtensionConfig;
|
config: ExtensionConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EnvVar {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to get a friendly title from extension name
|
// Helper function to get a friendly title from extension name
|
||||||
const getFriendlyTitle = (name: string): string => {
|
const getFriendlyTitle = (name: string): string => {
|
||||||
return name
|
return name
|
||||||
@@ -41,8 +50,28 @@ const getSubtitle = (config: ExtensionConfig): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function ExtensionsSection() {
|
export default function ExtensionsSection() {
|
||||||
const { config, updateExtension } = useConfig();
|
const { config, updateExtension, addExtension } = useConfig();
|
||||||
const [extensions, setExtensions] = useState<ExtensionItem[]>([]);
|
const [extensions, setExtensions] = useState<ExtensionItem[]>([]);
|
||||||
|
const [selectedExtension, setSelectedExtension] = useState<ExtensionItem | null>(null);
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
||||||
|
const [formData, setFormData] = useState<{
|
||||||
|
name: string;
|
||||||
|
type: 'stdio' | 'sse';
|
||||||
|
cmd?: string;
|
||||||
|
args?: string[];
|
||||||
|
endpoint?: string;
|
||||||
|
enabled: boolean;
|
||||||
|
envVars: EnvVar[];
|
||||||
|
}>({
|
||||||
|
name: '',
|
||||||
|
type: 'stdio',
|
||||||
|
cmd: '',
|
||||||
|
args: [],
|
||||||
|
endpoint: '',
|
||||||
|
enabled: true,
|
||||||
|
envVars: [],
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (config.extensions) {
|
if (config.extensions) {
|
||||||
@@ -63,6 +92,28 @@ export default function ExtensionsSection() {
|
|||||||
}
|
}
|
||||||
}, [config.extensions]);
|
}, [config.extensions]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedExtension) {
|
||||||
|
const envVars = selectedExtension.config.envs
|
||||||
|
? Object.entries(selectedExtension.config.envs).map(([key, value]) => ({
|
||||||
|
key,
|
||||||
|
value: value as string,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
setFormData({
|
||||||
|
name: selectedExtension.config.name,
|
||||||
|
type: selectedExtension.config.type as 'stdio' | 'sse',
|
||||||
|
cmd: selectedExtension.config.type === 'stdio' ? selectedExtension.config.cmd : undefined,
|
||||||
|
args: selectedExtension.config.args || [],
|
||||||
|
endpoint:
|
||||||
|
selectedExtension.config.type === 'sse' ? selectedExtension.config.cmd : undefined,
|
||||||
|
enabled: selectedExtension.config.enabled,
|
||||||
|
envVars,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [selectedExtension]);
|
||||||
|
|
||||||
const handleExtensionToggle = async (id: string) => {
|
const handleExtensionToggle = async (id: string) => {
|
||||||
const extension = extensions.find((ext) => ext.id === id);
|
const extension = extensions.find((ext) => ext.id === id);
|
||||||
if (extension) {
|
if (extension) {
|
||||||
@@ -75,11 +126,125 @@ export default function ExtensionsSection() {
|
|||||||
await updateExtension(id, updatedConfig);
|
await updateExtension(id, updatedConfig);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to update extension:', error);
|
console.error('Failed to update extension:', error);
|
||||||
// Here you might want to add a toast notification for error feedback
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleConfigureClick = (extension: ExtensionItem) => {
|
||||||
|
setSelectedExtension(extension);
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddExtension = async () => {
|
||||||
|
const envs = formData.envVars.reduce(
|
||||||
|
(acc, { key, value }) => {
|
||||||
|
if (key) {
|
||||||
|
acc[key] = value;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, string>
|
||||||
|
);
|
||||||
|
|
||||||
|
const extensionConfig = {
|
||||||
|
name: formData.name,
|
||||||
|
type: formData.type,
|
||||||
|
enabled: formData.enabled,
|
||||||
|
envs,
|
||||||
|
...(formData.type === 'stdio'
|
||||||
|
? {
|
||||||
|
cmd: formData.cmd,
|
||||||
|
args: formData.args,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
cmd: formData.endpoint,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await addExtension(formData.name, extensionConfig);
|
||||||
|
handleModalClose();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to add extension:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalClose = () => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
setIsAddModalOpen(false);
|
||||||
|
setSelectedExtension(null);
|
||||||
|
setFormData({
|
||||||
|
name: '',
|
||||||
|
type: 'stdio',
|
||||||
|
cmd: '',
|
||||||
|
args: [],
|
||||||
|
endpoint: '',
|
||||||
|
enabled: true,
|
||||||
|
envVars: [],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddEnvVar = () => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
envVars: [...formData.envVars, { key: '', value: '' }],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveEnvVar = (index: number) => {
|
||||||
|
const newEnvVars = [...formData.envVars];
|
||||||
|
newEnvVars.splice(index, 1);
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
envVars: newEnvVars,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEnvVarChange = (index: number, field: 'key' | 'value', value: string) => {
|
||||||
|
const newEnvVars = [...formData.envVars];
|
||||||
|
newEnvVars[index][field] = value;
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
envVars: newEnvVars,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveConfig = async () => {
|
||||||
|
if (!selectedExtension) return;
|
||||||
|
|
||||||
|
const envs = formData.envVars.reduce(
|
||||||
|
(acc, { key, value }) => {
|
||||||
|
if (key) {
|
||||||
|
acc[key] = value;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, string>
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedConfig = {
|
||||||
|
name: formData.name,
|
||||||
|
type: formData.type,
|
||||||
|
enabled: formData.enabled,
|
||||||
|
envs,
|
||||||
|
...(formData.type === 'stdio'
|
||||||
|
? {
|
||||||
|
cmd: formData.cmd,
|
||||||
|
args: formData.args,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
cmd: formData.endpoint,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateExtension(selectedExtension.id, updatedConfig);
|
||||||
|
handleModalClose();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update extension configuration:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="extensions">
|
<section id="extensions">
|
||||||
<div className="flex justify-between items-center mb-6 px-8">
|
<div className="flex justify-between items-center mb-6 px-8">
|
||||||
@@ -100,7 +265,10 @@ export default function ExtensionsSection() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{extension.canConfigure && (
|
{extension.canConfigure && (
|
||||||
<button className="text-textSubtle hover:text-textStandard">
|
<button
|
||||||
|
className="text-textSubtle hover:text-textStandard"
|
||||||
|
onClick={() => handleConfigureClick(extension)}
|
||||||
|
>
|
||||||
<Gear className="h-5 w-5" />
|
<Gear className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -116,7 +284,21 @@ export default function ExtensionsSection() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4 pt-4 w-full">
|
<div className="flex gap-4 pt-4 w-full">
|
||||||
<Button className="flex items-center gap-2 flex-1 justify-center bg-[#393838] hover:bg-subtle">
|
<Button
|
||||||
|
className="flex items-center gap-2 flex-1 justify-center bg-[#393838] hover:bg-subtle"
|
||||||
|
onClick={() => {
|
||||||
|
setFormData({
|
||||||
|
name: '',
|
||||||
|
type: 'stdio',
|
||||||
|
cmd: '',
|
||||||
|
args: [],
|
||||||
|
endpoint: '',
|
||||||
|
enabled: true,
|
||||||
|
envVars: [],
|
||||||
|
});
|
||||||
|
setIsAddModalOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
Manually Add
|
Manually Add
|
||||||
</Button>
|
</Button>
|
||||||
@@ -129,6 +311,276 @@ export default function ExtensionsSection() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isModalOpen && selectedExtension && (
|
||||||
|
<Modal>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h2 className="text-xl font-medium">Update Extension</h2>
|
||||||
|
|
||||||
|
<div className="flex justify-between gap-4">
|
||||||
|
<div className="flex-1">
|
||||||
|
<label className="text-sm font-medium mb-2 block">Extension Name</label>
|
||||||
|
<Input
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
placeholder="Enter extension name..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-[200px]">
|
||||||
|
<label className="text-sm font-medium mb-2 block">Type</label>
|
||||||
|
<Select
|
||||||
|
value={{ value: formData.type, label: formData.type.toUpperCase() }}
|
||||||
|
onChange={(option) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
type: (option?.value as 'stdio' | 'sse') || 'stdio',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
options={[
|
||||||
|
{ value: 'stdio', label: 'STDIO' },
|
||||||
|
{ value: 'sse', label: 'SSE' },
|
||||||
|
]}
|
||||||
|
styles={createDarkSelectStyles('200px')}
|
||||||
|
theme={darkSelectTheme}
|
||||||
|
isSearchable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{formData.type === 'stdio' ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Command</label>
|
||||||
|
<Input
|
||||||
|
value={formData.cmd || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({ ...formData, cmd: e.target.value, endpoint: undefined })
|
||||||
|
}
|
||||||
|
placeholder="Enter command..."
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Arguments</label>
|
||||||
|
<Input
|
||||||
|
value={formData.args?.join(' ') || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
args: e.target.value.split(' ').filter((arg) => arg.length > 0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
placeholder="Enter arguments..."
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Endpoint</label>
|
||||||
|
<Input
|
||||||
|
value={formData.endpoint || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
endpoint: e.target.value,
|
||||||
|
cmd: undefined,
|
||||||
|
args: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
placeholder="Enter endpoint URL..."
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between items-center mb-2">
|
||||||
|
<label className="text-sm font-medium">Environment Variables</label>
|
||||||
|
<Button
|
||||||
|
onClick={handleAddEnvVar}
|
||||||
|
variant="ghost"
|
||||||
|
className="text-sm hover:bg-subtle"
|
||||||
|
>
|
||||||
|
Add Variable
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
{formData.envVars.map((envVar, index) => (
|
||||||
|
<div key={index} className="flex gap-2 items-start">
|
||||||
|
<Input
|
||||||
|
value={envVar.key}
|
||||||
|
onChange={(e) => handleEnvVarChange(index, 'key', e.target.value)}
|
||||||
|
placeholder="Key"
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
value={envVar.value}
|
||||||
|
onChange={(e) => handleEnvVarChange(index, 'value', e.target.value)}
|
||||||
|
placeholder="Value"
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => handleRemoveEnvVar(index)}
|
||||||
|
variant="ghost"
|
||||||
|
className="p-2 h-auto hover:bg-subtle"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-3 pt-4">
|
||||||
|
<Button onClick={handleModalClose} variant="ghost" className="hover:bg-subtle">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSaveConfig} className="bg-[#393838] hover:bg-subtle">
|
||||||
|
Save Changes
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isAddModalOpen && (
|
||||||
|
<Modal>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h2 className="text-xl font-medium">Add New Extension</h2>
|
||||||
|
|
||||||
|
<div className="flex justify-between gap-4">
|
||||||
|
<div className="flex-1">
|
||||||
|
<label className="text-sm font-medium mb-2 block">Extension Name</label>
|
||||||
|
<Input
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
placeholder="Enter extension name..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-[200px]">
|
||||||
|
<label className="text-sm font-medium mb-2 block">Type</label>
|
||||||
|
<Select
|
||||||
|
value={{ value: formData.type, label: formData.type.toUpperCase() }}
|
||||||
|
onChange={(option) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
type: (option?.value as 'stdio' | 'sse') || 'stdio',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
options={[
|
||||||
|
{ value: 'stdio', label: 'STDIO' },
|
||||||
|
{ value: 'sse', label: 'SSE' },
|
||||||
|
]}
|
||||||
|
styles={createDarkSelectStyles('200px')}
|
||||||
|
theme={darkSelectTheme}
|
||||||
|
isSearchable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{formData.type === 'stdio' ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Command</label>
|
||||||
|
<Input
|
||||||
|
value={formData.cmd || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({ ...formData, cmd: e.target.value, endpoint: undefined })
|
||||||
|
}
|
||||||
|
placeholder="Enter command..."
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Arguments</label>
|
||||||
|
<Input
|
||||||
|
value={formData.args?.join(' ') || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
args: e.target.value.split(' ').filter((arg) => arg.length > 0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
placeholder="Enter arguments..."
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Endpoint</label>
|
||||||
|
<Input
|
||||||
|
value={formData.endpoint || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
endpoint: e.target.value,
|
||||||
|
cmd: undefined,
|
||||||
|
args: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
placeholder="Enter endpoint URL..."
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between items-center mb-2">
|
||||||
|
<label className="text-sm font-medium">Environment Variables</label>
|
||||||
|
<Button
|
||||||
|
onClick={handleAddEnvVar}
|
||||||
|
variant="ghost"
|
||||||
|
className="text-sm hover:bg-subtle"
|
||||||
|
>
|
||||||
|
Add Variable
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
{formData.envVars.map((envVar, index) => (
|
||||||
|
<div key={index} className="flex gap-2 items-start">
|
||||||
|
<Input
|
||||||
|
value={envVar.key}
|
||||||
|
onChange={(e) => handleEnvVarChange(index, 'key', e.target.value)}
|
||||||
|
placeholder="Key"
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
value={envVar.value}
|
||||||
|
onChange={(e) => handleEnvVarChange(index, 'value', e.target.value)}
|
||||||
|
placeholder="Value"
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => handleRemoveEnvVar(index)}
|
||||||
|
variant="ghost"
|
||||||
|
className="p-2 h-auto hover:bg-subtle"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-3 pt-4">
|
||||||
|
<Button onClick={handleModalClose} variant="ghost" className="hover:bg-subtle">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleAddExtension} className="bg-[#393838] hover:bg-subtle">
|
||||||
|
Add Extension
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import ProviderSetupOverlay from './subcomponents/ProviderSetupOverlay';
|
import Modal from '../../../../components/Modal';
|
||||||
import ProviderSetupHeader from './subcomponents/ProviderSetupHeader';
|
import ProviderSetupHeader from './subcomponents/ProviderSetupHeader';
|
||||||
import DefaultProviderSetupForm from './subcomponents/forms/DefaultProviderSetupForm';
|
import DefaultProviderSetupForm from './subcomponents/forms/DefaultProviderSetupForm';
|
||||||
import ProviderSetupActions from './subcomponents/ProviderSetupActions';
|
import ProviderSetupActions from './subcomponents/ProviderSetupActions';
|
||||||
import ProviderLogo from './subcomponents/ProviderLogo';
|
import ProviderLogo from './subcomponents/ProviderLogo';
|
||||||
import ProviderConfiguationModalProps from './interfaces/ProviderConfigurationModalProps';
|
|
||||||
import { useProviderModal } from './ProviderModalProvider';
|
import { useProviderModal } from './ProviderModalProvider';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
@@ -61,7 +60,7 @@ export default function ProviderConfigurationModal() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProviderSetupOverlay>
|
<Modal>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{/* Logo area - centered above title */}
|
{/* Logo area - centered above title */}
|
||||||
<ProviderLogo providerName={currentProvider.id} />
|
<ProviderLogo providerName={currentProvider.id} />
|
||||||
@@ -79,6 +78,6 @@ export default function ProviderConfigurationModal() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<ProviderSetupActions onCancel={handleCancel} />
|
<ProviderSetupActions onCancel={handleCancel} />
|
||||||
</ProviderSetupOverlay>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user