import React, { useState } from "react";
import { createPortal } from "react-dom";
/**
* EnvironmentEditor — Claude-Code-on-Web parity environment config modal.
*
* Allows setting name, network access level, and environment variables.
*/
export default function EnvironmentEditor({ environment, onSave, onDelete, onClose }) {
const [name, setName] = useState(environment?.name || "");
const [networkAccess, setNetworkAccess] = useState(environment?.network_access || "limited");
const [envVarsText, setEnvVarsText] = useState(
environment?.env_vars
? Object.entries(environment.env_vars)
.map(([k, v]) => `${k}=${v}`)
.join("\n")
: ""
);
const handleSave = () => {
const envVars = {};
envVarsText
.split("\n")
.map((line) => line.trim())
.filter((line) => line && line.includes("="))
.forEach((line) => {
const idx = line.indexOf("=");
const key = line.slice(0, idx).trim();
const val = line.slice(idx + 1).trim();
if (key) envVars[key] = val;
});
onSave({
id: environment?.id || null,
name: name.trim() || "Default",
network_access: networkAccess,
env_vars: envVars,
});
};
return createPortal(
{ if (e.target === e.currentTarget) onClose(); }}>
e.stopPropagation()}>
{environment?.id ? "Edit Environment" : "New Environment"}
{/* Name */}
setName(e.target.value)}
placeholder="e.g. Development, Staging, Production"
style={styles.input}
/>
{/* Network Access */}
{[
{ value: "limited", label: "Limited", desc: "Allowlisted domains only (package managers, APIs)" },
{ value: "full", label: "Full", desc: "Unrestricted internet access" },
{ value: "none", label: "None", desc: "Air-gapped — no external network" },
].map((opt) => (
))}
{/* Environment Variables */}
{onDelete && (
)}
,
document.body
);
}
const styles = {
overlay: {
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0, 0, 0, 0.6)",
zIndex: 10000,
display: "flex",
alignItems: "center",
justifyContent: "center",
},
modal: {
width: 480,
maxHeight: "80vh",
backgroundColor: "#131316",
border: "1px solid #27272A",
borderRadius: 12,
display: "flex",
flexDirection: "column",
overflow: "hidden",
},
header: {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "14px 16px",
borderBottom: "1px solid #27272A",
backgroundColor: "#18181B",
},
headerTitle: {
fontSize: 14,
fontWeight: 600,
color: "#E4E4E7",
},
closeBtn: {
width: 26,
height: 26,
borderRadius: 6,
border: "1px solid #3F3F46",
background: "transparent",
color: "#A1A1AA",
fontSize: 16,
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
},
body: {
padding: "16px",
overflowY: "auto",
flex: 1,
},
label: {
display: "block",
fontSize: 12,
fontWeight: 600,
color: "#A1A1AA",
marginBottom: 6,
marginTop: 14,
},
input: {
width: "100%",
padding: "8px 10px",
borderRadius: 6,
border: "1px solid #3F3F46",
background: "#18181B",
color: "#E4E4E7",
fontSize: 13,
outline: "none",
boxSizing: "border-box",
},
radioGroup: {
display: "flex",
flexDirection: "column",
gap: 6,
},
radioItem: {
display: "flex",
alignItems: "flex-start",
gap: 10,
padding: "8px 10px",
borderRadius: 6,
border: "1px solid #27272A",
cursor: "pointer",
transition: "border-color 0.15s, background-color 0.15s",
},
textarea: {
width: "100%",
padding: "8px 10px",
borderRadius: 6,
border: "1px solid #3F3F46",
background: "#18181B",
color: "#E4E4E7",
fontSize: 12,
fontFamily: "monospace",
outline: "none",
resize: "vertical",
boxSizing: "border-box",
},
footer: {
display: "flex",
alignItems: "center",
gap: 8,
padding: "12px 16px",
borderTop: "1px solid #27272A",
},
cancelBtn: {
padding: "6px 14px",
borderRadius: 6,
border: "1px solid #3F3F46",
background: "transparent",
color: "#A1A1AA",
fontSize: 12,
cursor: "pointer",
},
saveBtn: {
padding: "6px 14px",
borderRadius: 6,
border: "none",
background: "#3B82F6",
color: "#fff",
fontSize: 12,
fontWeight: 600,
cursor: "pointer",
},
deleteBtn: {
padding: "6px 14px",
borderRadius: 6,
border: "1px solid rgba(239, 68, 68, 0.3)",
background: "transparent",
color: "#EF4444",
fontSize: 12,
cursor: "pointer",
},
};