Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 38 additions & 19 deletions src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,25 @@ import { planBulkAdd } from "@/shared/utils/bulkAdd";

const BULK_PLACEHOLDER = `name1|sk-key1\nname2|sk-key2\nsk-key-only-auto-named`;

const NONE_PROXY_POOL_VALUE = "__none__";

const createInitialFormData = () => ({
name: "",
apiKey: "",
defaultModel: "",
priority: 1,
proxyPoolId: NONE_PROXY_POOL_VALUE,
ollamaHostUrl: "",
});

const createInitialAzureData = () => ({
azureEndpoint: "",
apiVersion: "2024-10-01-preview",
deployment: "",
organization: "",
});

export default function AddApiKeyModal({ isOpen, provider, providerName, isCompatible, isAnthropic, authType, authHint, website, proxyPools, error, existingNames, onSave, onBulkDone, onClose }) {
const NONE_PROXY_POOL_VALUE = "__none__";
const isOllamaLocal = provider === "ollama-local";
const isCookie = authType === "cookie";
const isXaiApiKey = provider === "xai" && !isCookie;
Expand All @@ -23,20 +40,8 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
const providerRegions = AI_PROVIDERS?.[provider]?.regions || null;
const defaultRegion = AI_PROVIDERS?.[provider]?.defaultRegion || providerRegions?.[0]?.id || "";

const [formData, setFormData] = useState({
name: "",
apiKey: "",
defaultModel: "",
priority: 1,
proxyPoolId: NONE_PROXY_POOL_VALUE,
ollamaHostUrl: "",
});
const [azureData, setAzureData] = useState({
azureEndpoint: "",
apiVersion: "2024-10-01-preview",
deployment: "",
organization: "",
});
const [formData, setFormData] = useState(createInitialFormData);
const [azureData, setAzureData] = useState(createInitialAzureData);
const [cloudflareData, setCloudflareData] = useState({ accountId: "" });
const [region, setRegion] = useState(defaultRegion);
const [validating, setValidating] = useState(false);
Expand All @@ -52,6 +57,19 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
const [bulkText, setBulkText] = useState("");
const [bulkResult, setBulkResult] = useState(null); // { success, failed }

// Reset state and close
const handleClose = () => {
setFormData(createInitialFormData());
setAzureData(createInitialAzureData());
setCloudflareData({ accountId: "" });
setRegion(defaultRegion);
setValidationResult(null);
setMode("single");
setBulkText("");
setBulkResult(null);
onClose();
};

const buildProviderSpecificData = () => {
if (isOllamaLocal && formData.ollamaHostUrl.trim()) {
return { baseUrl: formData.ollamaHostUrl.trim() };
Expand Down Expand Up @@ -119,7 +137,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
setValidating(false);
}

await onSave({
const saved = await onSave({
name: formData.name || (isOllamaLocal ? "Ollama Local" : ""),
apiKey: formData.apiKey,
defaultModel: isCompatible ? formData.defaultModel.trim() : undefined,
Expand All @@ -128,6 +146,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
testStatus: isValid ? "active" : "unknown",
providerSpecificData: buildProviderSpecificData()
});
if (saved) handleClose();
} finally {
setSaving(false);
}
Expand Down Expand Up @@ -188,7 +207,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
if (!provider) return null;

return (
<Modal isOpen={isOpen} title={`Add ${providerName || provider} ${credentialLabel}`} onClose={onClose}>
<Modal isOpen={isOpen} title={`Add ${providerName || provider} ${credentialLabel}`} onClose={handleClose}>
<div className="flex flex-col gap-4">
{/* Mode switcher */}
<div className="flex gap-2">
Expand Down Expand Up @@ -221,7 +240,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
<Button onClick={handleBulkSubmit} fullWidth disabled={saving || !bulkText.trim()}>
{saving ? "Adding..." : "Add All Keys"}
</Button>
<Button onClick={onClose} variant="ghost" fullWidth>Cancel</Button>
<Button onClick={handleClose} variant="ghost" fullWidth>Cancel</Button>
</div>
</div>
)}
Expand Down Expand Up @@ -396,7 +415,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
<Button onClick={handleSubmit} fullWidth disabled={saving || (!isOllamaLocal && (!formData.name || !formData.apiKey)) || (isCompatible && !formData.defaultModel.trim()) || (isAzure && (!azureData.azureEndpoint || !azureData.deployment || !azureData.organization)) || (isCloudflareAi && !cloudflareData.accountId)}>
{saving ? "Saving..." : "Save"}
</Button>
<Button onClick={onClose} variant="ghost" fullWidth>
<Button onClick={handleClose} variant="ghost" fullWidth>
Cancel
</Button>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/app/(dashboard)/dashboard/providers/[id]/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -831,15 +831,15 @@ export default function ProviderDetailPage() {

if (res.ok) {
await fetchConnections();
setShowAddApiKeyModal(false);
return;
return true; // the modal resets and closes itself
}

setAddConnectionError(data?.error || "Failed to save connection");
} catch (error) {
console.log("Error saving connection:", error);
setAddConnectionError("Failed to save connection");
}
return false;
};

const handleUpdateConnection = async (formData) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ function AddCompatibleModal({ variant, isOpen, onClose, onCreated }) {
}
}, [config.hasApiType ? formData.apiType : isOpen]);

// Reset state and close
const handleClose = () => {
setFormData(initialFormData());
setCheckKey("");
setCheckModelId("");
setValidationResult(null);
onClose();
};

const handleSubmit = async () => {
if (!formData.name.trim() || !formData.prefix.trim() || !formData.baseUrl.trim()) return;
setSubmitting(true);
Expand All @@ -79,9 +88,7 @@ function AddCompatibleModal({ variant, isOpen, onClose, onCreated }) {
const data = await res.json();
if (res.ok) {
onCreated(data.node);
setFormData(initialFormData());
setCheckKey("");
setValidationResult(null);
handleClose();
}
} catch (error) {
console.log(`Error creating ${config.errorLabel} node:`, error);
Expand Down Expand Up @@ -134,7 +141,7 @@ function AddCompatibleModal({ variant, isOpen, onClose, onCreated }) {
};

return (
<Modal isOpen={isOpen} title={config.title} onClose={onClose}>
<Modal isOpen={isOpen} title={config.title} onClose={handleClose}>
<div className="flex flex-col gap-4">
<Input
label="Name"
Expand Down Expand Up @@ -202,7 +209,7 @@ function AddCompatibleModal({ variant, isOpen, onClose, onCreated }) {
>
{submitting ? "Creating..." : "Create"}
</Button>
<Button onClick={onClose} variant="ghost" fullWidth>
<Button onClick={handleClose} variant="ghost" fullWidth>
Cancel
</Button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,19 @@ ConnectionRow.propTypes = {
// ── AddApiKeyModal ─────────────────────────────────────────────
function AddApiKeyModal({ isOpen, provider, providerName, proxyPools, onSave, onClose }) {
const NONE = "__none__";
const [formData, setFormData] = useState({ name: "", apiKey: "", priority: 1, proxyPoolId: NONE });
const createInitialFormData = () => ({ name: "", apiKey: "", priority: 1, proxyPoolId: NONE });
const [formData, setFormData] = useState(createInitialFormData);
const [validating, setValidating] = useState(false);
const [validationResult, setValidationResult] = useState(null);
const [saving, setSaving] = useState(false);

// Reset state and close
const handleClose = () => {
setFormData(createInitialFormData());
setValidationResult(null);
onClose();
};

const handleValidate = async () => {
setValidating(true);
try {
Expand Down Expand Up @@ -232,20 +240,21 @@ function AddApiKeyModal({ isOpen, provider, providerName, proxyPools, onSave, on
setValidationResult(isValid ? "success" : "failed");
} catch { setValidationResult("failed"); }
finally { setValidating(false); }
await onSave({
const saved = await onSave({
name: formData.name,
apiKey: formData.apiKey,
priority: formData.priority,
proxyPoolId: formData.proxyPoolId === NONE ? null : formData.proxyPoolId,
testStatus: isValid ? "active" : "unknown",
});
if (saved) handleClose();
} finally { setSaving(false); }
};

if (!provider) return null;

return (
<Modal isOpen={isOpen} title={`Add ${providerName || provider} API Key`} onClose={onClose}>
<Modal isOpen={isOpen} title={`Add ${providerName || provider} API Key`} onClose={handleClose}>
<div className="flex flex-col gap-4">
<div>
<label className="text-xs text-text-muted mb-1 block">Name</label>
Expand Down Expand Up @@ -277,7 +286,7 @@ function AddApiKeyModal({ isOpen, provider, providerName, proxyPools, onSave, on
<Button onClick={handleSubmit} fullWidth disabled={!formData.name || !formData.apiKey || saving}>
{saving ? "Saving..." : "Save"}
</Button>
<Button onClick={onClose} variant="ghost" fullWidth>Cancel</Button>
<Button onClick={handleClose} variant="ghost" fullWidth>Cancel</Button>
</div>
</div>
</Modal>
Expand Down Expand Up @@ -385,8 +394,9 @@ export default function ConnectionsCard({ providerId, isOAuth }) {
const handleSaveApiKey = async (formData) => {
try {
const res = await fetch("/api/providers", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider: providerId, ...formData }) });
if (res.ok) { await fetch_(); setShowAddModal(false); }
if (res.ok) { await fetch_(); return true; } // the modal resets and closes itself
} catch (e) { console.log("save apikey error:", e); }
return false;
};

const handleUpdateConnection = async (formData) => {
Expand Down
15 changes: 12 additions & 3 deletions src/shared/components/CursorAuthModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ export default function CursorAuthModal({ isOpen, onSuccess, onClose }) {
runAutoDetect();
}, [isOpen]);

// Reset state and close
const handleClose = () => {
setAccessToken("");
setMachineId("");
setAutoDetected(false);
setError(null);
onClose();
};

const handleImportToken = async () => {
if (!accessToken.trim()) {
setError("Please enter an access token");
Expand Down Expand Up @@ -80,7 +89,7 @@ export default function CursorAuthModal({ isOpen, onSuccess, onClose }) {
}

onSuccess?.();
onClose();
handleClose();
} catch (err) {
setError(err.message);
} finally {
Expand All @@ -89,7 +98,7 @@ export default function CursorAuthModal({ isOpen, onSuccess, onClose }) {
};

return (
<Modal isOpen={isOpen} title="Connect Cursor IDE" onClose={onClose}>
<Modal isOpen={isOpen} title="Connect Cursor IDE" onClose={handleClose}>
<div className="flex flex-col gap-4">
{/* Auto-detecting state */}
{autoDetecting && (
Expand Down Expand Up @@ -194,7 +203,7 @@ export default function CursorAuthModal({ isOpen, onSuccess, onClose }) {
>
{importing ? "Importing..." : "Import Token"}
</Button>
<Button onClick={onClose} variant="ghost" fullWidth>
<Button onClick={handleClose} variant="ghost" fullWidth>
Cancel
</Button>
</div>
Expand Down
22 changes: 21 additions & 1 deletion src/shared/components/KiroAuthModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,23 @@ export default function KiroAuthModal({ isOpen, onMethodSelect, onClose }) {
setError(null);
};

// Reset credentials and return to method picker
const resetCredentials = () => {
setSelectedMethod(null);
setIdcStartUrl("");
setRefreshToken("");
setCliProxyJson("");
setApiKey("");
setIdcCredentials(null);
setAutoDetected(false);
setError(null);
};

const handleClose = () => {
resetCredentials();
onClose();
};

const handleImportToken = async () => {
if (!refreshToken.trim()) {
setError("Please enter a refresh token");
Expand All @@ -98,6 +115,7 @@ export default function KiroAuthModal({ isOpen, onMethodSelect, onClose }) {
}

// Success - notify parent to refresh connections
resetCredentials();
onMethodSelect("import");
} catch (err) {
setError(err.message);
Expand Down Expand Up @@ -128,6 +146,7 @@ export default function KiroAuthModal({ isOpen, onMethodSelect, onClose }) {
throw new Error(data.error || "CLIProxyAPI import failed");
}

resetCredentials();
onMethodSelect("import-cli-proxy");
} catch (err) {
setError(err.message);
Expand Down Expand Up @@ -170,6 +189,7 @@ export default function KiroAuthModal({ isOpen, onMethodSelect, onClose }) {
}

// Success - notify parent to refresh connections
resetCredentials();
onMethodSelect("api-key");
} catch (err) {
setError(err.message);
Expand All @@ -183,7 +203,7 @@ export default function KiroAuthModal({ isOpen, onMethodSelect, onClose }) {
};

return (
<Modal isOpen={isOpen} title="Connect Kiro" onClose={onClose} size="lg">
<Modal isOpen={isOpen} title="Connect Kiro" onClose={handleClose} size="lg">
<div className="flex flex-col gap-4">
{/* Method Selection */}
{!selectedMethod && (
Expand Down
4 changes: 2 additions & 2 deletions src/shared/components/KiroOAuthWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export default function KiroOAuthWrapper({ isOpen, providerInfo, onSuccess, onCl
// Use social login with manual callback
setAuthMethod("social");
setSocialProvider(config.provider);
} else if (method === "import" || method === "api-key") {
// Import / API-key handled in KiroAuthModal, just close
} else if (method === "import" || method === "import-cli-proxy" || method === "api-key") {
// Import / CLIProxyAPI import / API-key are completed inside KiroAuthModal, just close
onSuccess?.();
}
}, [onSuccess]);
Expand Down