diff --git a/src/app/(dashboard)/dashboard/providers/[id]/BulkImportCodexModal.js b/src/app/(dashboard)/dashboard/providers/[id]/BulkImportCodexModal.js new file mode 100644 index 00000000..d18abefe --- /dev/null +++ b/src/app/(dashboard)/dashboard/providers/[id]/BulkImportCodexModal.js @@ -0,0 +1,150 @@ +"use client"; + +import { useState } from "react"; +import PropTypes from "prop-types"; +import { Button, Modal } from "@/shared/components"; +import { translate } from "@/i18n/runtime"; + +const PLACEHOLDER = `[ + { + "accessToken": "eyJhbGc...", + "refreshToken": "rt_...", + "idToken": "eyJhbGc...", + "email": "user@example.com" + } +]`; + +function normalizeToArray(parsed) { + if (Array.isArray(parsed)) return parsed; + if (parsed && typeof parsed === "object") { + if (Array.isArray(parsed.accounts)) return parsed.accounts; + return [parsed]; + } + return null; +} + +export default function BulkImportCodexModal({ isOpen, onClose, onSuccess }) { + const [jsonText, setJsonText] = useState(""); + const [submitting, setSubmitting] = useState(false); + const [parseError, setParseError] = useState(""); + const [result, setResult] = useState(null); + + const handleClose = () => { + if (submitting) return; + setJsonText(""); + setParseError(""); + setResult(null); + onClose(); + }; + + const handleSubmit = async () => { + setParseError(""); + setResult(null); + + const trimmed = jsonText.trim(); + if (!trimmed) return; + + let parsed; + try { + parsed = JSON.parse(trimmed); + } catch (err) { + setParseError(`${translate("Invalid JSON")}: ${err.message}`); + return; + } + + const accounts = normalizeToArray(parsed); + if (!accounts || accounts.length === 0) { + setParseError(translate("No accounts found in input")); + return; + } + + setSubmitting(true); + try { + const res = await fetch("/api/oauth/codex/bulk-import", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ accounts }), + }); + const data = await res.json(); + if (!res.ok) { + setParseError(data?.error || `Request failed: ${res.status}`); + return; + } + setResult(data); + if (data.success > 0 && typeof onSuccess === "function") { + onSuccess(); + } + } catch (err) { + setParseError(err.message || translate("Request failed")); + } finally { + setSubmitting(false); + } + }; + + const failedItems = result?.results?.filter((r) => !r.ok) || []; + + return ( + +
+

+ {translate( + "Paste an array of codex account JSON objects. Each must include accessToken (and ideally refreshToken, idToken)." + )} +

+ +