"use client"; /** * HTTP 요청 노드 속성 편집 */ import { useEffect, useState, useCallback } from "react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Plus, Trash2, Globe, Key, FileJson, Settings } from "lucide-react"; import { useFlowEditorStore } from "@/lib/stores/flowEditorStore"; import type { HttpRequestActionNodeData } from "@/types/node-editor"; interface HttpRequestActionPropertiesProps { nodeId: string; data: HttpRequestActionNodeData; } export function HttpRequestActionProperties({ nodeId, data }: HttpRequestActionPropertiesProps) { const { updateNode } = useFlowEditorStore(); // 로컬 상태 const [displayName, setDisplayName] = useState(data.displayName || "HTTP 요청"); const [url, setUrl] = useState(data.url || ""); const [method, setMethod] = useState(data.method || "GET"); const [bodyType, setBodyType] = useState(data.bodyType || "none"); const [body, setBody] = useState(data.body || ""); // 인증 const [authType, setAuthType] = useState["type"]>( data.authentication?.type || "none" ); const [authUsername, setAuthUsername] = useState(data.authentication?.username || ""); const [authPassword, setAuthPassword] = useState(data.authentication?.password || ""); const [authToken, setAuthToken] = useState(data.authentication?.token || ""); const [authApiKey, setAuthApiKey] = useState(data.authentication?.apiKey || ""); const [authApiKeyName, setAuthApiKeyName] = useState(data.authentication?.apiKeyName || "X-API-Key"); const [authApiKeyLocation, setAuthApiKeyLocation] = useState<"header" | "query">( data.authentication?.apiKeyLocation || "header" ); // 옵션 const [timeout, setTimeout] = useState(data.options?.timeout?.toString() || "30000"); const [followRedirects, setFollowRedirects] = useState(data.options?.followRedirects ?? true); const [retryCount, setRetryCount] = useState(data.options?.retryCount?.toString() || "0"); const [retryDelay, setRetryDelay] = useState(data.options?.retryDelay?.toString() || "1000"); // 헤더 const [headers, setHeaders] = useState>( Object.entries(data.headers || {}).map(([key, value]) => ({ key, value })) ); // 쿼리 파라미터 const [queryParams, setQueryParams] = useState>( Object.entries(data.queryParams || {}).map(([key, value]) => ({ key, value })) ); // 응답 처리 const [extractPath, setExtractPath] = useState(data.responseHandling?.extractPath || ""); const [saveToVariable, setSaveToVariable] = useState(data.responseHandling?.saveToVariable || ""); // 데이터 변경 시 로컬 상태 동기화 useEffect(() => { setDisplayName(data.displayName || "HTTP 요청"); setUrl(data.url || ""); setMethod(data.method || "GET"); setBodyType(data.bodyType || "none"); setBody(data.body || ""); setAuthType(data.authentication?.type || "none"); setAuthUsername(data.authentication?.username || ""); setAuthPassword(data.authentication?.password || ""); setAuthToken(data.authentication?.token || ""); setAuthApiKey(data.authentication?.apiKey || ""); setAuthApiKeyName(data.authentication?.apiKeyName || "X-API-Key"); setAuthApiKeyLocation(data.authentication?.apiKeyLocation || "header"); setTimeout(data.options?.timeout?.toString() || "30000"); setFollowRedirects(data.options?.followRedirects ?? true); setRetryCount(data.options?.retryCount?.toString() || "0"); setRetryDelay(data.options?.retryDelay?.toString() || "1000"); setHeaders(Object.entries(data.headers || {}).map(([key, value]) => ({ key, value }))); setQueryParams(Object.entries(data.queryParams || {}).map(([key, value]) => ({ key, value }))); setExtractPath(data.responseHandling?.extractPath || ""); setSaveToVariable(data.responseHandling?.saveToVariable || ""); }, [data]); // 노드 업데이트 함수 const updateNodeData = useCallback( (updates: Partial) => { updateNode(nodeId, { ...data, ...updates, }); }, [nodeId, data, updateNode] ); // 표시명 변경 const handleDisplayNameChange = (value: string) => { setDisplayName(value); updateNodeData({ displayName: value }); }; // URL 업데이트 const updateUrl = () => { updateNodeData({ url }); }; // 메서드 변경 const handleMethodChange = (value: HttpRequestActionNodeData["method"]) => { setMethod(value); updateNodeData({ method: value }); }; // 바디 타입 변경 const handleBodyTypeChange = (value: HttpRequestActionNodeData["bodyType"]) => { setBodyType(value); updateNodeData({ bodyType: value }); }; // 바디 업데이트 const updateBody = () => { updateNodeData({ body }); }; // 인증 업데이트 const updateAuthentication = useCallback(() => { updateNodeData({ authentication: { type: authType, username: authUsername || undefined, password: authPassword || undefined, token: authToken || undefined, apiKey: authApiKey || undefined, apiKeyName: authApiKeyName || undefined, apiKeyLocation: authApiKeyLocation, }, }); }, [authType, authUsername, authPassword, authToken, authApiKey, authApiKeyName, authApiKeyLocation, updateNodeData]); // 옵션 업데이트 const updateOptions = useCallback(() => { updateNodeData({ options: { timeout: parseInt(timeout) || 30000, followRedirects, retryCount: parseInt(retryCount) || 0, retryDelay: parseInt(retryDelay) || 1000, }, }); }, [timeout, followRedirects, retryCount, retryDelay, updateNodeData]); // 응답 처리 업데이트 const updateResponseHandling = useCallback(() => { updateNodeData({ responseHandling: { extractPath: extractPath || undefined, saveToVariable: saveToVariable || undefined, }, }); }, [extractPath, saveToVariable, updateNodeData]); // 헤더 추가 const addHeader = () => { setHeaders([...headers, { key: "", value: "" }]); }; // 헤더 삭제 const removeHeader = (index: number) => { const newHeaders = headers.filter((_, i) => i !== index); setHeaders(newHeaders); const headersObj = Object.fromEntries(newHeaders.filter(h => h.key).map(h => [h.key, h.value])); updateNodeData({ headers: headersObj }); }; // 헤더 업데이트 const updateHeader = (index: number, field: "key" | "value", value: string) => { const newHeaders = [...headers]; newHeaders[index][field] = value; setHeaders(newHeaders); }; // 헤더 저장 const saveHeaders = () => { const headersObj = Object.fromEntries(headers.filter(h => h.key).map(h => [h.key, h.value])); updateNodeData({ headers: headersObj }); }; // 쿼리 파라미터 추가 const addQueryParam = () => { setQueryParams([...queryParams, { key: "", value: "" }]); }; // 쿼리 파라미터 삭제 const removeQueryParam = (index: number) => { const newParams = queryParams.filter((_, i) => i !== index); setQueryParams(newParams); const paramsObj = Object.fromEntries(newParams.filter(p => p.key).map(p => [p.key, p.value])); updateNodeData({ queryParams: paramsObj }); }; // 쿼리 파라미터 업데이트 const updateQueryParam = (index: number, field: "key" | "value", value: string) => { const newParams = [...queryParams]; newParams[index][field] = value; setQueryParams(newParams); }; // 쿼리 파라미터 저장 const saveQueryParams = () => { const paramsObj = Object.fromEntries(queryParams.filter(p => p.key).map(p => [p.key, p.value])); updateNodeData({ queryParams: paramsObj }); }; return (
{/* 표시명 */}
handleDisplayNameChange(e.target.value)} placeholder="HTTP 요청" className="h-8 text-sm" />
{/* URL */}
setUrl(e.target.value)} onBlur={updateUrl} placeholder="https://api.example.com/endpoint" className="h-8 text-sm" />
{/* 메서드 */}
헤더 바디 인증 옵션 {/* 헤더 탭 */} {/* 쿼리 파라미터 */}
{queryParams.map((param, index) => (
updateQueryParam(index, "key", e.target.value)} onBlur={saveQueryParams} placeholder="파라미터명" className="h-8 text-sm" /> updateQueryParam(index, "value", e.target.value)} onBlur={saveQueryParams} placeholder="값" className="h-8 text-sm" />
))}
{/* 헤더 */}
{headers.map((header, index) => (
updateHeader(index, "key", e.target.value)} onBlur={saveHeaders} placeholder="헤더명" className="h-8 text-sm" /> updateHeader(index, "value", e.target.value)} onBlur={saveHeaders} placeholder="값" className="h-8 text-sm" />
))}
{/* 바디 탭 */}
{bodyType !== "none" && (