diff --git a/src/app/(dashboard)/dashboard/providers/[id]/BulkImportGrokCliModal.js b/src/app/(dashboard)/dashboard/providers/[id]/BulkImportGrokCliModal.js new file mode 100644 index 00000000..10628eb1 --- /dev/null +++ b/src/app/(dashboard)/dashboard/providers/[id]/BulkImportGrokCliModal.js @@ -0,0 +1,284 @@ +"use client"; + +import { useState, useRef } from "react"; +import { Modal, Button } from "@/shared/components"; +import { translate } from "@/i18n/runtime"; + +const PLACEHOLDER = `[ + { + "access_token": "eyJ0eXAiOiJhdCtqd3Qi...", + "refresh_token": "LZhriF9bf88pPykpXCuZ9...", + "id_token": "eyJ0eXAiOiJKV1QiLCJhbGci...", + "email": "account1@example.com" + }, + { + "access_token": "eyJ0eXAiOiJhdCtqd3Qi...", + "refresh_token": "LZhriF9bf88pPykpXCuZ9...", + "id_token": "eyJ0eXAiOiJKV1QiLCJhbGci...", + "email": "account2@example.com" + } +]`; + +function parseAccountsInput(rawText) { + const trimmed = rawText.trim(); + if (!trimmed) return []; + + let parsed; + try { + parsed = JSON.parse(trimmed); + } catch (initialErr) { + // If direct parse failed, try handling concatenated or comma-separated JSON objects + try { + let fixed = trimmed; + if (!fixed.startsWith("[")) { + fixed = fixed.replace(/\}\s*,\s*\{/g, "},{").replace(/\}\s*\{/g, "},{"); + if (fixed.endsWith(",")) fixed = fixed.slice(0, -1); + fixed = `[${fixed}]`; + } + parsed = JSON.parse(fixed); + } catch { + throw initialErr; + } + } + + if (Array.isArray(parsed)) { + return parsed; + } + if (parsed && typeof parsed === "object") { + if (Array.isArray(parsed.accounts)) return parsed.accounts; + return [parsed]; + } + + throw new Error("Input must be a JSON object or array of objects"); +} + +export default function BulkImportGrokCliModal({ isOpen, onClose, onSuccess }) { + const [jsonText, setJsonText] = useState(""); + const [submitting, setSubmitting] = useState(false); + const [parseError, setParseError] = useState(""); + const [result, setResult] = useState(null); + const [isDragging, setIsDragging] = useState(false); + const [fileCountInfo, setFileCountInfo] = useState(null); + const fileInputRef = useRef(null); + + const handleClose = () => { + if (submitting) return; + setJsonText(""); + setParseError(""); + setResult(null); + setFileCountInfo(null); + setIsDragging(false); + onClose(); + }; + + const processFiles = async (files) => { + if (!files || files.length === 0) return; + setParseError(""); + const jsonFiles = Array.from(files).filter( + (file) => file.name.endsWith(".json") || file.type === "application/json" || file.type === "" + ); + + if (jsonFiles.length === 0) { + setParseError(translate("Please select valid .json files")); + return; + } + + try { + const allAccounts = []; + for (const file of jsonFiles) { + const text = await file.text(); + const accountsFromFile = parseAccountsInput(text); + if (Array.isArray(accountsFromFile)) { + allAccounts.push(...accountsFromFile); + } else if (accountsFromFile) { + allAccounts.push(accountsFromFile); + } + } + + if (allAccounts.length === 0) { + setParseError(translate("No accounts found in selected files")); + return; + } + + setJsonText(JSON.stringify(allAccounts, null, 2)); + setFileCountInfo({ + filesCount: jsonFiles.length, + accountsCount: allAccounts.length, + }); + } catch (err) { + setParseError(`${translate("Error reading files")}: ${err.message}`); + } + }; + + const handleFileInputChange = (e) => { + processFiles(e.target.files); + if (e.target) e.target.value = ""; + }; + + const handleDragOver = (e) => { + e.preventDefault(); + setIsDragging(true); + }; + + const handleDragLeave = (e) => { + e.preventDefault(); + setIsDragging(false); + }; + + const handleDrop = (e) => { + e.preventDefault(); + setIsDragging(false); + if (e.dataTransfer?.files?.length > 0) { + processFiles(e.dataTransfer.files); + } + }; + + const handleSubmit = async () => { + setParseError(""); + setResult(null); + + let accounts; + try { + accounts = parseAccountsInput(jsonText); + } catch (err) { + setParseError(`${translate("Invalid JSON")}: ${err.message}`); + return; + } + + if (!accounts || accounts.length === 0) { + setParseError(translate("No accounts found in input")); + return; + } + + setSubmitting(true); + try { + const res = await fetch("/api/oauth/grok-cli/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("Upload multiple .json files or paste JSON array / object.")} +

+ + +
+ +
+